diff --git a/.codecov.yml b/.codecov.yml index 155b009c9d5..4311564683e 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -11,11 +11,6 @@ coverage: threshold: 1% flags: - x6 - x6-vector: - threshold: 1% - target: 80% # will fail a Pull Request if coverage is less than 80% - flags: - - x6-vector x6-geometry: threshold: 1% flags: @@ -26,9 +21,6 @@ flags: paths: # filter the folder(s) you wish to measure by that flag - packages/x6 - x6-vector: - paths: - - packages/x6-vector x6-geometry: paths: - packages/x6-geometry diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f1fe2f254d..23795277a00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,12 +54,6 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} files: ./packages/x6/test/coverage/lcov.info flags: x6 - - name: 💡 Codecov(x6-vector) - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./packages/x6-vector/test/coverage/lcov.info - flags: x6-vector - name: 💡 Codecov(x6-geometry) uses: codecov/codecov-action@v1 with: diff --git a/examples/x6-example-features/package.json b/examples/x6-example-features/package.json index cb8be4d2325..8eb0632f7b0 100644 --- a/examples/x6-example-features/package.json +++ b/examples/x6-example-features/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@antv/x6-example-features", - "version": "2.0.2-beta.0", + "version": "2.0.0", "scripts": { "start": "umi dev", "build": "umi build", @@ -9,11 +9,9 @@ "precommit": "lint-staged" }, "dependencies": { - "@antv/x6": "^1.30.2", - "@antv/x6-next": "^2.0.6-beta.0", + "@antv/x6": "^2.0.6-beta.0", "@antv/x6-react-components": "^2.0.6-beta.0", "@antv/x6-react-shape": "^2.0.6-beta.0", - "@antv/x6-vector": "^1.3.0", "antd": "^4.4.2", "classnames": "^2.2.6", "dagre": "^0.8.5", diff --git a/examples/x6-example-features/src/pages/animation/transition.tsx b/examples/x6-example-features/src/pages/animation/transition.tsx index 61563b9b821..3993884494c 100644 --- a/examples/x6-example-features/src/pages/animation/transition.tsx +++ b/examples/x6-example-features/src/pages/animation/transition.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Graph, Cell } from '@antv/x6-next' +import { Graph, Cell } from '@antv/x6' import { Point } from '@antv/x6-geometry' import { Timing, Interp } from '@antv/x6-common' import '../index.less' diff --git a/examples/x6-example-features/src/pages/bus/index.tsx b/examples/x6-example-features/src/pages/bus/index.tsx index d6d78fef001..1b0844a7c5f 100644 --- a/examples/x6-example-features/src/pages/bus/index.tsx +++ b/examples/x6-example-features/src/pages/bus/index.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Graph, Cell } from '@antv/x6-next' +import { Graph, Cell } from '@antv/x6' import { Bus, Connector, Component, Fader, Aux } from './shapes' import '../index.less' import './index.less' diff --git a/examples/x6-example-features/src/pages/bus/shapes.ts b/examples/x6-example-features/src/pages/bus/shapes.ts index 050e47dbf81..d499b0ce04d 100644 --- a/examples/x6-example-features/src/pages/bus/shapes.ts +++ b/examples/x6-example-features/src/pages/bus/shapes.ts @@ -1,4 +1,4 @@ -import { Node, Shape } from '@antv/x6-next' +import { Node, Shape } from '@antv/x6' export class Bus extends Shape.Edge { static create(x: number, label: string, color: string) { diff --git a/examples/x6-example-features/src/pages/dnd/index.tsx b/examples/x6-example-features/src/pages/dnd/index.tsx index 3527bf1449a..4230239ba35 100644 --- a/examples/x6-example-features/src/pages/dnd/index.tsx +++ b/examples/x6-example-features/src/pages/dnd/index.tsx @@ -1,238 +1,238 @@ -import React from 'react' -import { Button } from 'antd' -import { Graph, Dom } from '@antv/x6' -import { Dnd } from '@antv/x6/es/addon/dnd' -import '../index.less' -import './index.less' - -export default class Example extends React.Component { - private graph: Graph - private dnd: Dnd - private container: HTMLDivElement - - componentDidMount() { - const graph = (this.graph = new Graph({ - container: this.container, - width: 800, - height: 800, - history: true, - snapline: { - enabled: true, - sharp: true, - }, - grid: { - visible: true, - }, - scroller: { - enabled: true, - width: 600, - height: 400, - pageVisible: true, - pageBreak: false, - pannable: true, - }, - embedding: { - enabled: true, - findParent({ node }) { - const bbox = node.getBBox() - return this.getNodes().filter((parent) => { - const targetBBox = parent.getBBox() - return targetBBox.containsRect(bbox) - }) - }, - }, - })) - - const source = graph.addNode({ - x: 130, - y: 30, - width: 200, - height: 80, - attrs: { - label: { - text: 'Hello', - fill: '#6a6c8a', - }, - body: { - stroke: '#31d0c6', - strokeWidth: 2, - }, - }, - }) - - const target = graph.addNode({ - x: 320, - y: 240, - width: 100, - height: 40, - attrs: { - label: { - text: 'World', - fill: '#6a6c8a', - }, - body: { - stroke: '#31d0c6', - strokeWidth: 2, - }, - }, - }) - - graph.addEdge({ source, target }) - - graph.on('node:change:parent', (args) => { - console.log('node:change:parent', args) - }) - - graph.on('node:added', (args) => { - console.log('node:added', args) - }) - - graph.centerContent() - - this.dnd = new Dnd({ - target: graph, - animation: true, - getDragNode(sourceNode, options) { - console.log('getDragNode', sourceNode, options) - return sourceNode.clone() - }, - getDropNode(draggingNode, options) { - console.log('getDropNode', draggingNode, options) - return draggingNode.clone() - }, - validateNode(droppingNode, options) { - console.log('validateNode', droppingNode, options) - - return droppingNode.shape === 'html' - ? new Promise((resolve) => { - const { draggingNode, draggingGraph } = options - const view = draggingGraph.findView(draggingNode) - const contentElem = view.findOne('foreignObject > body > div') - Dom.addClass(contentElem, 'validating') - setTimeout(() => { - Dom.removeClass(contentElem, 'validating') - resolve(true) - }, 3000) - }) - : true - }, - }) - this.graph = graph - } - - onUndo = () => { - this.graph.undo() - } - - onRedo = () => { - this.graph.redo() - } - - refContainer = (container: HTMLDivElement) => { - this.container = container - } - - startDrag = (e: React.MouseEvent) => { - const target = e.currentTarget - const type = target.getAttribute('data-type') - const node = - type === 'rect' - ? this.graph.createNode({ - width: 100, - height: 40, - attrs: { - label: { - text: 'Rect', - fill: '#6a6c8a', - }, - body: { - stroke: '#31d0c6', - strokeWidth: 2, - }, - }, - }) - : this.graph.createNode({ - width: 60, - height: 60, - shape: 'html', - html: () => { - const wrap = document.createElement('div') - wrap.style.width = '100%' - wrap.style.height = '100%' - wrap.style.display = 'flex' - wrap.style.alignItems = 'center' - wrap.style.justifyContent = 'center' - wrap.style.border = '2px solid rgb(49, 208, 198)' - wrap.style.background = '#fff' - wrap.style.borderRadius = '100%' - wrap.innerText = 'Circle' - return wrap - }, - }) - - this.dnd.start(node, e.nativeEvent as any) - } - - render() { - return ( -
-

Dnd

-
-
- Rect -
-
- Circle -
-
- -
- - - - -
-
-
- ) - } -} +// import React from 'react' +// import { Button } from 'antd' +// import { Graph, Dom } from '@antv/x6' +// import { Dnd } from '@antv/x6/es/addon/dnd' +// import '../index.less' +// import './index.less' + +// export default class Example extends React.Component { +// private graph: Graph +// private dnd: Dnd +// private container: HTMLDivElement + +// componentDidMount() { +// const graph = (this.graph = new Graph({ +// container: this.container, +// width: 800, +// height: 800, +// history: true, +// snapline: { +// enabled: true, +// sharp: true, +// }, +// grid: { +// visible: true, +// }, +// scroller: { +// enabled: true, +// width: 600, +// height: 400, +// pageVisible: true, +// pageBreak: false, +// pannable: true, +// }, +// embedding: { +// enabled: true, +// findParent({ node }) { +// const bbox = node.getBBox() +// return this.getNodes().filter((parent) => { +// const targetBBox = parent.getBBox() +// return targetBBox.containsRect(bbox) +// }) +// }, +// }, +// })) + +// const source = graph.addNode({ +// x: 130, +// y: 30, +// width: 200, +// height: 80, +// attrs: { +// label: { +// text: 'Hello', +// fill: '#6a6c8a', +// }, +// body: { +// stroke: '#31d0c6', +// strokeWidth: 2, +// }, +// }, +// }) + +// const target = graph.addNode({ +// x: 320, +// y: 240, +// width: 100, +// height: 40, +// attrs: { +// label: { +// text: 'World', +// fill: '#6a6c8a', +// }, +// body: { +// stroke: '#31d0c6', +// strokeWidth: 2, +// }, +// }, +// }) + +// graph.addEdge({ source, target }) + +// graph.on('node:change:parent', (args) => { +// console.log('node:change:parent', args) +// }) + +// graph.on('node:added', (args) => { +// console.log('node:added', args) +// }) + +// graph.centerContent() + +// this.dnd = new Dnd({ +// target: graph, +// animation: true, +// getDragNode(sourceNode, options) { +// console.log('getDragNode', sourceNode, options) +// return sourceNode.clone() +// }, +// getDropNode(draggingNode, options) { +// console.log('getDropNode', draggingNode, options) +// return draggingNode.clone() +// }, +// validateNode(droppingNode, options) { +// console.log('validateNode', droppingNode, options) + +// return droppingNode.shape === 'html' +// ? new Promise((resolve) => { +// const { draggingNode, draggingGraph } = options +// const view = draggingGraph.findView(draggingNode) +// const contentElem = view.findOne('foreignObject > body > div') +// Dom.addClass(contentElem, 'validating') +// setTimeout(() => { +// Dom.removeClass(contentElem, 'validating') +// resolve(true) +// }, 3000) +// }) +// : true +// }, +// }) +// this.graph = graph +// } + +// onUndo = () => { +// this.graph.undo() +// } + +// onRedo = () => { +// this.graph.redo() +// } + +// refContainer = (container: HTMLDivElement) => { +// this.container = container +// } + +// startDrag = (e: React.MouseEvent) => { +// const target = e.currentTarget +// const type = target.getAttribute('data-type') +// const node = +// type === 'rect' +// ? this.graph.createNode({ +// width: 100, +// height: 40, +// attrs: { +// label: { +// text: 'Rect', +// fill: '#6a6c8a', +// }, +// body: { +// stroke: '#31d0c6', +// strokeWidth: 2, +// }, +// }, +// }) +// : this.graph.createNode({ +// width: 60, +// height: 60, +// shape: 'html', +// html: () => { +// const wrap = document.createElement('div') +// wrap.style.width = '100%' +// wrap.style.height = '100%' +// wrap.style.display = 'flex' +// wrap.style.alignItems = 'center' +// wrap.style.justifyContent = 'center' +// wrap.style.border = '2px solid rgb(49, 208, 198)' +// wrap.style.background = '#fff' +// wrap.style.borderRadius = '100%' +// wrap.innerText = 'Circle' +// return wrap +// }, +// }) + +// this.dnd.start(node, e.nativeEvent as any) +// } + +// render() { +// return ( +//
+//

Dnd

+//
+//
+// Rect +//
+//
+// Circle +//
+//
+ +//
+// +// +// +// +//
+//
+//
+// ) +// } +// } diff --git a/examples/x6-example-features/src/pages/halo/index.tsx b/examples/x6-example-features/src/pages/halo/index.tsx index 724591dd482..65c914da043 100644 --- a/examples/x6-example-features/src/pages/halo/index.tsx +++ b/examples/x6-example-features/src/pages/halo/index.tsx @@ -1,91 +1,91 @@ -import React from 'react' -import { Graph } from '@antv/x6' -import { Halo } from '@antv/x6/es/addon/halo' -import '../index.less' -import '../../../../../packages/x6/src/addon/halo/index.less' +// import React from 'react' +// import { Graph } from '@antv/x6' +// import { Halo } from '@antv/x6/es/addon/halo' +// import '../index.less' +// import '../../../../../packages/x6/src/addon/halo/index.less' -export default class Example extends React.Component { - private container: HTMLDivElement +// export default class Example extends React.Component { +// private container: HTMLDivElement - componentDidMount() { - const graph = new Graph({ - container: this.container, - width: 800, - height: 600, - grid: 1, - }) +// componentDidMount() { +// const graph = new Graph({ +// container: this.container, +// width: 800, +// height: 600, +// grid: 1, +// }) - graph.addNode({ - shape: 'rect', - x: 50, - y: 50, - width: 100, - height: 40, - attrs: { label: { text: 'A' } }, - }) +// graph.addNode({ +// shape: 'rect', +// x: 50, +// y: 50, +// width: 100, +// height: 40, +// attrs: { label: { text: 'A' } }, +// }) - graph.addNode({ - shape: 'circle', - x: 250, - y: 80, - width: 50, - height: 50, - attrs: { label: { text: 'B' } }, - }) +// graph.addNode({ +// shape: 'circle', +// x: 250, +// y: 80, +// width: 50, +// height: 50, +// attrs: { label: { text: 'B' } }, +// }) - graph.addNode({ - shape: 'ellipse', - x: 350, - y: 150, - width: 80, - height: 40, - attrs: { label: { text: 'C' } }, - }) +// graph.addNode({ +// shape: 'ellipse', +// x: 350, +// y: 150, +// width: 80, +// height: 40, +// attrs: { label: { text: 'C' } }, +// }) - graph.on('node:resize', (args) => { - console.log('node:resize', args) - }) +// graph.on('node:resize', (args) => { +// console.log('node:resize', args) +// }) - graph.on('node:resizing', (args) => { - console.log('node:resizing', args) - }) +// graph.on('node:resizing', (args) => { +// console.log('node:resizing', args) +// }) - graph.on('node:resized', (args) => { - console.log('node:resized', args) - }) +// graph.on('node:resized', (args) => { +// console.log('node:resized', args) +// }) - graph.on('node:rotate', (args) => { - console.log('node:rotate', args) - }) +// graph.on('node:rotate', (args) => { +// console.log('node:rotate', args) +// }) - graph.on('node:rotating', (args) => { - console.log('node:rotating', args) - }) +// graph.on('node:rotating', (args) => { +// console.log('node:rotating', args) +// }) - graph.on('node:rotated', (args) => { - console.log('node:rotated', args) - }) +// graph.on('node:rotated', (args) => { +// console.log('node:rotated', args) +// }) - graph.on('node:mouseup', ({ view }) => { - const halo = new Halo({ - view, - // type: 'toolbar', - // pie: { sliceAngle: 360 / 7 }, - }) +// graph.on('node:mouseup', ({ view }) => { +// const halo = new Halo({ +// view, +// // type: 'toolbar', +// // pie: { sliceAngle: 360 / 7 }, +// }) - console.log(halo) - }) - } +// console.log(halo) +// }) +// } - refContainer = (container: HTMLDivElement) => { - this.container = container - } +// refContainer = (container: HTMLDivElement) => { +// this.container = container +// } - render() { - return ( -
-
-
- ) - } -} +// render() { +// return ( +//
+//
+//
+// ) +// } +// } diff --git a/examples/x6-example-features/src/pages/keyboard/index.tsx b/examples/x6-example-features/src/pages/keyboard/index.tsx index 93e2480a636..3d3e267456f 100644 --- a/examples/x6-example-features/src/pages/keyboard/index.tsx +++ b/examples/x6-example-features/src/pages/keyboard/index.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Graph } from '@antv/x6-next' +import { Graph } from '@antv/x6' import { Keyboard } from '@antv/x6-plugin-keyboard' import '../index.less' diff --git a/examples/x6-example-features/src/pages/ports/connected.tsx b/examples/x6-example-features/src/pages/ports/connected.tsx index 4a96ef5f811..9760dd2fd31 100644 --- a/examples/x6-example-features/src/pages/ports/connected.tsx +++ b/examples/x6-example-features/src/pages/ports/connected.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Graph, Node } from '@antv/x6-next' +import { Graph, Node } from '@antv/x6' import { Path } from '@antv/x6-geometry' import '../index.less' diff --git a/examples/x6-example-features/src/pages/react/extends.tsx b/examples/x6-example-features/src/pages/react/extends.tsx index 01242b9e0b1..958f839ab52 100644 --- a/examples/x6-example-features/src/pages/react/extends.tsx +++ b/examples/x6-example-features/src/pages/react/extends.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Graph, Node } from '@antv/x6-next' +import { Graph, Node } from '@antv/x6' import { ReactShape, register } from '@antv/x6-react-shape' import '../index.less' import './index.less' diff --git a/examples/x6-example-features/src/pages/react/index.tsx b/examples/x6-example-features/src/pages/react/index.tsx index 7ca1398d8e5..147dff83fa9 100644 --- a/examples/x6-example-features/src/pages/react/index.tsx +++ b/examples/x6-example-features/src/pages/react/index.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Graph, Node } from '@antv/x6-next' +import { Graph, Node } from '@antv/x6' import { register } from '@antv/x6-react-shape' import '../index.less' import './index.less' diff --git a/examples/x6-example-features/src/pages/react/portal.tsx b/examples/x6-example-features/src/pages/react/portal.tsx index d54d28f0964..97dca003de6 100644 --- a/examples/x6-example-features/src/pages/react/portal.tsx +++ b/examples/x6-example-features/src/pages/react/portal.tsx @@ -1,5 +1,5 @@ import React, { useContext } from 'react' -import { Graph } from '@antv/x6-next' +import { Graph } from '@antv/x6' import { register, Portal } from '@antv/x6-react-shape' import { Button } from 'antd' import '../index.less' diff --git a/examples/x6-example-features/src/pages/scroller/test.tsx b/examples/x6-example-features/src/pages/scroller/test.tsx index 3e9d58c0f06..2db5d1d45de 100644 --- a/examples/x6-example-features/src/pages/scroller/test.tsx +++ b/examples/x6-example-features/src/pages/scroller/test.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Graph } from '@antv/x6-next' +import { Graph } from '@antv/x6' import { Scroller } from '@antv/x6-plugin-scroller' import '../index.less' import './index.less' diff --git a/examples/x6-example-features/src/pages/v2/graph-v2.tsx b/examples/x6-example-features/src/pages/v2/graph-v2.tsx index 742b982f071..8f33d722bda 100644 --- a/examples/x6-example-features/src/pages/v2/graph-v2.tsx +++ b/examples/x6-example-features/src/pages/v2/graph-v2.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Graph } from '@antv/x6-next' +import { Graph } from '@antv/x6' import { Path } from '@antv/x6-geometry' import data from './data' diff --git a/examples/x6-example-features/src/pages/v2/react.tsx b/examples/x6-example-features/src/pages/v2/react.tsx index 25e9b814885..da257aff7fc 100644 --- a/examples/x6-example-features/src/pages/v2/react.tsx +++ b/examples/x6-example-features/src/pages/v2/react.tsx @@ -1,5 +1,5 @@ // import React from 'react' -// import { Graph } from '@antv/x6-next' +// import { Graph } from '@antv/x6' // import { Path } from '@antv/x6-geometry' // import data from './data' // import './index.less' diff --git a/lerna.json b/lerna.json index e0e0affdc18..cc67d9fd82c 100644 --- a/lerna.json +++ b/lerna.json @@ -28,7 +28,7 @@ "packages": [ "packages/x6-common", "packages/x6-geometry", - "packages/x6-next", + "packages/x6", "packages/x6-react-shape", "packages/x6-vue-shape" ] diff --git a/packages/x6-core/README.md b/packages/x6-core/README.md deleted file mode 100644 index a7659046355..00000000000 --- a/packages/x6-core/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# `x6-core` - -> TODO: description - -## Usage \ No newline at end of file diff --git a/packages/x6-core/__tests__/common/disposable.test.ts b/packages/x6-core/__tests__/common/disposable.test.ts deleted file mode 100644 index c57b6e0818d..00000000000 --- a/packages/x6-core/__tests__/common/disposable.test.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { - Disposable, - IDisposable, - DisposableSet, - DisposableDelegate, -} from '../../src/common' - -class TestDisposable implements IDisposable { - count = 0 - - get disposed(): boolean { - return this.count > 0 - } - - dispose(): void { - this.count += 1 - } -} - -class AOPTest extends Disposable { - a = 1 - - @Disposable.dispose() - dispose() { - this.a = 0 - } -} - -describe('disposable', () => { - describe('Disablable', () => { - it('should be `false` before object is disposed', () => { - const obj = new Disposable() - expect(obj.disposed).toBe(false) - }) - - it('should be `true` after object is disposed', () => { - const obj = new Disposable() - obj.dispose() - expect(obj.disposed).toBe(true) - }) - - // it('should add `unload` listener for ie', () => { - // const tmp = Platform as any - // tmp.IS_IE = true - // const obj = new Disposable() - // expect(obj.disposed).toBe(false) - // tmp.IS_IE = false - // window.dispatchEvent(new Event('unload')) - // expect(obj.disposed).toBe(true) - // }) - - it('shoule work with `aop`', () => { - const obj = new AOPTest() - expect(obj.disposed).toBe(false) - expect(obj.a).toBe(1) - obj.dispose() - expect(obj.disposed).toBe(true) - expect(obj.a).toBe(0) - obj.dispose() - expect(obj.disposed).toBe(true) - expect(obj.a).toBe(0) - }) - }) - - describe('DisposableDelegate', () => { - describe('#constructor', () => { - it('should accept a callback', () => { - const delegate = new DisposableDelegate(() => {}) - expect(delegate instanceof DisposableDelegate).toBeTruthy() - }) - }) - - describe('#disposed', () => { - it('should be `false` before object is disposed', () => { - const delegate = new DisposableDelegate(() => {}) - expect(delegate.disposed).toBe(false) - }) - - it('should be `true` after object is disposed', () => { - const delegate = new DisposableDelegate(() => {}) - delegate.dispose() - expect(delegate.disposed).toBe(true) - }) - }) - - describe('#dispose', () => { - it('should invoke a callback when disposed', () => { - let called = false - const delegate = new DisposableDelegate(() => (called = true)) - expect(called).toBe(false) - delegate.dispose() - expect(called).toBe(true) - }) - - it('should ignore multiple calls to `dispose`', () => { - let count = 0 - const delegate = new DisposableDelegate(() => (count += 1)) - expect(count).toBe(0) - delegate.dispose() - delegate.dispose() - delegate.dispose() - expect(count).toBe(1) - }) - }) - }) - - describe('DisposableSet', () => { - describe('#constructor', () => { - it('should accept no arguments', () => { - const set = new DisposableSet() - expect(set instanceof DisposableSet).toBeTruthy() - }) - }) - - describe('#disposed', () => { - it('should be `false` before object is disposed', () => { - const set = new DisposableSet() - expect(set.disposed).toBe(false) - }) - - it('should be `true` after object is disposed', () => { - const set = new DisposableSet() - set.dispose() - expect(set.disposed).toBe(true) - }) - }) - - describe('#dispose', () => { - it('should dispose all items in the set', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = DisposableSet.from([item1, item2, item3]) - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - set.dispose() - expect(item1.count).toBe(1) - expect(item2.count).toBe(1) - expect(item3.count).toBe(1) - }) - - it('should dipose items in the order they were added', () => { - const values: number[] = [] - const item1 = new DisposableDelegate(() => values.push(0)) - const item2 = new DisposableDelegate(() => values.push(1)) - const item3 = new DisposableDelegate(() => values.push(2)) - const set = DisposableSet.from([item1, item2, item3]) - expect(values).toEqual([]) - set.dispose() - expect(values).toEqual([0, 1, 2]) - }) - - it('should ignore multiple calls to `dispose`', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = DisposableSet.from([item1, item2, item3]) - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - set.dispose() - set.dispose() - set.dispose() - expect(item1.count).toBe(1) - expect(item2.count).toBe(1) - expect(item3.count).toBe(1) - }) - }) - - describe('#add', () => { - it('should add items to the set', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = new DisposableSet() - set.add(item1) - set.add(item2) - set.add(item3) - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - set.dispose() - expect(item1.count).toBe(1) - expect(item2.count).toBe(1) - expect(item3.count).toBe(1) - }) - - it('should maintain insertion order', () => { - const values: number[] = [] - const item1 = new DisposableDelegate(() => values.push(0)) - const item2 = new DisposableDelegate(() => values.push(1)) - const item3 = new DisposableDelegate(() => values.push(2)) - const set = DisposableSet.from([item1]) - set.add(item2) - set.add(item3) - expect(values).toEqual([]) - set.dispose() - expect(values).toEqual([0, 1, 2]) - }) - - it('should ignore duplicate items', () => { - const values: number[] = [] - const item1 = new DisposableDelegate(() => values.push(0)) - const item2 = new DisposableDelegate(() => values.push(1)) - const item3 = new DisposableDelegate(() => values.push(2)) - const set = DisposableSet.from([item1]) - set.add(item2) - set.add(item3) - set.add(item3) - set.add(item2) - set.add(item1) - expect(values).toEqual([]) - set.dispose() - expect(values).toEqual([0, 1, 2]) - }) - }) - - describe('#contains', () => { - it('should remove all items from the set after disposed', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = new DisposableSet() - set.add(item1) - set.add(item2) - set.add(item3) - expect(set.contains(item1)).toBe(true) - expect(set.contains(item2)).toBe(true) - expect(set.contains(item3)).toBe(true) - - set.dispose() - - expect(set.contains(item1)).toBe(false) - expect(set.contains(item2)).toBe(false) - expect(set.contains(item3)).toBe(false) - }) - }) - - describe('#remove', () => { - it('should remove items from the set', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = DisposableSet.from([item1, item2, item3]) - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - set.remove(item2) - set.dispose() - expect(item1.count).toBe(1) - expect(item2.count).toBe(0) - expect(item3.count).toBe(1) - }) - - it('should maintain insertion order', () => { - const values: number[] = [] - const item1 = new DisposableDelegate(() => values.push(0)) - const item2 = new DisposableDelegate(() => values.push(1)) - const item3 = new DisposableDelegate(() => values.push(2)) - const set = DisposableSet.from([item1, item2, item3]) - expect(values).toEqual([]) - set.remove(item1) - set.dispose() - expect(values).toEqual([1, 2]) - }) - - it('should ignore missing items', () => { - const values: number[] = [] - const item1 = new DisposableDelegate(() => values.push(0)) - const item2 = new DisposableDelegate(() => values.push(1)) - const item3 = new DisposableDelegate(() => values.push(2)) - const set = DisposableSet.from([item1, item2]) - expect(values).toEqual([]) - set.remove(item3) - set.dispose() - expect(values).toEqual([0, 1]) - }) - }) - - describe('#clear', () => { - it('should remove all items from the set', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = DisposableSet.from([item1, item2, item3]) - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - set.clear() - set.dispose() - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - }) - }) - - describe('#from', () => { - it('should accept an iterable of disposable items', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = DisposableSet.from([item1, item2, item3]) - expect(set instanceof DisposableSet).toBeTruthy() - }) - }) - }) -}) diff --git a/packages/x6-core/karma.conf.js b/packages/x6-core/karma.conf.js deleted file mode 100644 index dc0f564c9bc..00000000000 --- a/packages/x6-core/karma.conf.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = (config) => - require('../../configs/karma-config.js')(config, { - files: [{ pattern: './src/**/*.ts' }, { pattern: '__tests__/**/*.ts' }], - }) diff --git a/packages/x6-core/package.json b/packages/x6-core/package.json deleted file mode 100644 index 1d4f07ca11e..00000000000 --- a/packages/x6-core/package.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "private": true, - "name": "@antv/x6-core", - "version": "2.0.3-beta.0", - "description": "A lightweight graphic render library.", - "main": "lib/index.js", - "module": "es/index.js", - "unpkg": "dist/x6-core.js", - "jsdelivr": "dist/x6-core.js", - "types": "lib/index.d.ts", - "files": [ - "dist", - "es", - "lib" - ], - "keywords": [ - "core", - "render", - "x6", - "antv" - ], - "scripts": { - "clean:build": "rimraf dist es lib", - "clean:coverage": "rimraf ./test/coverage", - "clean": "run-p clean:build clean:coverage", - "lint": "eslint 'src/**/*.{js,ts}?(x)' --fix", - "build:esm": "tsc --module esnext --target es2015 --outDir ./es", - "build:cjs": "tsc --module commonjs --target es2015 --outDir ./lib", - "build:umd": "rollup -c", - "build:dev": "run-p build:cjs build:esm", - "build:watch": "yarn build:esm --w", - "build:watch:esm": "yarn build:esm --w", - "build:watch:cjs": "yarn build:cjs --w", - "build": "run-p build:dev build:umd", - "prebuild": "run-s lint clean", - "test": "karma start", - "coveralls": "cat ./test/coverage/lcov.info | coveralls", - "pretest": "run-p clean:coverage", - "prepare": "run-s test build", - "precommit": "lint-staged" - }, - "lint-staged": { - "src/**/*.ts": [ - "eslint --fix" - ] - }, - "inherits": [ - "@antv/x6-package-json/cli.json", - "@antv/x6-package-json/karma.json", - "@antv/x6-package-json/eslint.json", - "@antv/x6-package-json/rollup.json" - ], - "dependencies": { - "@antv/x6-common": "^2.0.6-beta.0", - "@antv/x6-geometry": "^2.0.6-beta.0" - }, - "devDependencies": { - "@rollup/plugin-commonjs": "^20.0.0", - "@rollup/plugin-node-resolve": "^13.0.4", - "@rollup/plugin-replace": "^3.0.0", - "@rollup/plugin-typescript": "^8.2.5", - "@types/jasmine": "^3.9.0", - "@types/lodash-es": "^4.17.4", - "@types/node": "^16.9.1", - "@types/resize-observer-browser": "^0.1.5", - "@types/sinon": "^10.0.2", - "@typescript-eslint/eslint-plugin": "^4.31.0", - "@typescript-eslint/parser": "^4.31.0", - "coveralls": "^3.1.1", - "eslint": "^7.32.0", - "eslint-config-airbnb-base": "^14.2.1", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-jest": "^24.4.0", - "eslint-plugin-jsx-a11y": "^6.4.1", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-promise": "^5.1.0", - "eslint-plugin-react": "^7.25.1", - "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-unicorn": "^36.0.0", - "fs-extra": "^10.0.0", - "jasmine-core": "^3.9.0", - "karma": "^6.3.4", - "karma-chrome-launcher": "^3.1.0", - "karma-cli": "^2.0.0", - "karma-jasmine": "^4.0.1", - "karma-spec-reporter": "^0.0.32", - "karma-typescript": "5.3.0", - "karma-typescript-es6-transform": "5.3.0", - "lint-staged": "^11.1.2", - "npm-run-all": "^4.1.5", - "postcss": "^8.3.6", - "prettier": "^2.4.0", - "pretty-quick": "^3.1.1", - "rimraf": "^3.0.2", - "rollup": "^2.56.3", - "rollup-plugin-auto-external": "^2.0.0", - "rollup-plugin-filesize": "^9.1.1", - "rollup-plugin-postcss": "^4.0.1", - "rollup-plugin-progress": "^1.1.2", - "rollup-plugin-terser": "^7.0.2", - "sinon": "^11.1.2", - "ts-node": "^10.2.1", - "tslib": "^2.3.1", - "typescript": "^4.4.3", - "utility-types": "^3.10.0" - }, - "author": { - "name": "newbyvector", - "email": "vectorse@126.com" - }, - "contributors": [], - "license": "MIT", - "homepage": "https://github.com/antvis/x6", - "bugs": { - "url": "https://github.com/antvis/x6/issues" - }, - "repository": { - "type": "git", - "url": "ssh://git@github.com/antvis/x6.git", - "directory": "packages/x6-core" - }, - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org" - }, - "gitHead": "576fa342fa65a6867ead29f6801a30dcb31bcdb5" -} diff --git a/packages/x6-core/rollup.config.js b/packages/x6-core/rollup.config.js deleted file mode 100644 index a4f2b09ea06..00000000000 --- a/packages/x6-core/rollup.config.js +++ /dev/null @@ -1,13 +0,0 @@ -import config from '../../configs/rollup-config' - -export default config({ - output: [ - { - name: 'X6Core', - format: 'umd', - file: 'dist/x6-core.js', - sourcemap: true, - }, - ], - context: 'window', -}) diff --git a/packages/x6-core/src/animation/index.ts b/packages/x6-core/src/animation/index.ts deleted file mode 100644 index 43bceebae1c..00000000000 --- a/packages/x6-core/src/animation/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './timing' -export * from './interp' diff --git a/packages/x6-core/src/animation/interp.ts b/packages/x6-core/src/animation/interp.ts deleted file mode 100644 index b83a0b4e566..00000000000 --- a/packages/x6-core/src/animation/interp.ts +++ /dev/null @@ -1,62 +0,0 @@ -export namespace Interp { - export type Definition = (from: T, to: T) => (time: number) => T -} - -export namespace Interp { - export const number: Definition = (a, b) => { - const d = b - a - return (t: number) => { - return a + d * t - } - } - - export const object: Definition<{ [key: string]: number }> = (a, b) => { - const keys = Object.keys(a) - return (t) => { - const ret: { [key: string]: number } = {} - for (let i = keys.length - 1; i !== -1; i -= 1) { - const key = keys[i] - ret[key] = a[key] + (b[key] - a[key]) * t - } - return ret - } - } - - export const unit: Definition = (a, b) => { - const reg = /(-?[0-9]*.[0-9]*)(px|em|cm|mm|in|pt|pc|%)/ - const ma = reg.exec(a) - const mb = reg.exec(b) - - const pb = mb ? mb[1] : '' - const aa = ma ? +ma[1] : 0 - const bb = mb ? +mb[1] : 0 - - const index = pb.indexOf('.') - const precision = index > 0 ? pb[1].length - index - 1 : 0 - - const d = bb - aa - const u = ma ? ma[2] : '' - - return (t) => { - return (aa + d * t).toFixed(precision) + u - } - } - - export const color: Definition = (a, b) => { - const ca = parseInt(a.slice(1), 16) - const cb = parseInt(b.slice(1), 16) - const ra = ca & 0x0000ff - const rd = (cb & 0x0000ff) - ra - const ga = ca & 0x00ff00 - const gd = (cb & 0x00ff00) - ga - const ba = ca & 0xff0000 - const bd = (cb & 0xff0000) - ba - - return (t) => { - const r = (ra + rd * t) & 0x000000ff - const g = (ga + gd * t) & 0x0000ff00 - const b = (ba + bd * t) & 0x00ff0000 - return `#${((1 << 24) | r | g | b).toString(16).slice(1)}` - } - } -} diff --git a/packages/x6-core/src/animation/timing.ts b/packages/x6-core/src/animation/timing.ts deleted file mode 100644 index 343cca12bbe..00000000000 --- a/packages/x6-core/src/animation/timing.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { FunctionKeys } from 'utility-types' - -export namespace Timing { - export type Definition = (t: number) => number - export type Names = FunctionKeys -} - -export namespace Timing { - export const linear: Definition = (t) => t - export const quad: Definition = (t) => t * t - export const cubic: Definition = (t) => t * t * t - export const inout: Definition = (t) => { - if (t <= 0) { - return 0 - } - - if (t >= 1) { - return 1 - } - - const t2 = t * t - const t3 = t2 * t - return 4 * (t < 0.5 ? t3 : 3 * (t - t2) + t3 - 0.75) - } - - export const exponential: Definition = (t) => { - return Math.pow(2, 10 * (t - 1)) // eslint-disable-line - } - - export const bounce = ((t: number) => { - // eslint-disable-next-line - for (let a = 0, b = 1; 1; a += b, b /= 2) { - if (t >= (7 - 4 * a) / 11) { - const q = (11 - 6 * a - 11 * t) / 4 - return -q * q + b * b - } - } - }) as Definition -} - -export namespace Timing { - export const decorators = { - reverse(f: Definition): Definition { - return (t) => 1 - f(1 - t) - }, - reflect(f: Definition): Definition { - return (t) => 0.5 * (t < 0.5 ? f(2 * t) : 2 - f(2 - 2 * t)) - }, - clamp(f: Definition, n = 0, x = 1): Definition { - return (t) => { - const r = f(t) - return r < n ? n : r > x ? x : r - } - }, - back(s = 1.70158): Definition { - return (t) => t * t * ((s + 1) * t - s) - }, - elastic(x = 1.5): Definition { - return (t) => - Math.pow(2, 10 * (t - 1)) * Math.cos(((20 * Math.PI * x) / 3) * t) // eslint-disable-line - }, - } -} - -export namespace Timing { - // Slight acceleration from zero to full speed - export function easeInSine(t: number) { - return -1 * Math.cos(t * (Math.PI / 2)) + 1 - } - - // Slight deceleration at the end - export function easeOutSine(t: number) { - return Math.sin(t * (Math.PI / 2)) - } - - // Slight acceleration at beginning and slight deceleration at end - export function easeInOutSine(t: number) { - return -0.5 * (Math.cos(Math.PI * t) - 1) - } - - // Accelerating from zero velocity - export function easeInQuad(t: number) { - return t * t - } - - // Decelerating to zero velocity - export function easeOutQuad(t: number) { - return t * (2 - t) - } - - // Acceleration until halfway, then deceleration - export function easeInOutQuad(t: number) { - return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t - } - - // Accelerating from zero velocity - export function easeInCubic(t: number) { - return t * t * t - } - - // Decelerating to zero velocity - export function easeOutCubic(t: number) { - const t1 = t - 1 - return t1 * t1 * t1 + 1 - } - - // Acceleration until halfway, then deceleration - export function easeInOutCubic(t: number) { - return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1 - } - - // Accelerating from zero velocity - export function easeInQuart(t: number) { - return t * t * t * t - } - - // Decelerating to zero velocity - export function easeOutQuart(t: number) { - const t1 = t - 1 - return 1 - t1 * t1 * t1 * t1 - } - - // Acceleration until halfway, then deceleration - export function easeInOutQuart(t: number) { - const t1 = t - 1 - return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * t1 * t1 * t1 * t1 - } - - // Accelerating from zero velocity - export function easeInQuint(t: number) { - return t * t * t * t * t - } - - // Decelerating to zero velocity - export function easeOutQuint(t: number) { - const t1 = t - 1 - return 1 + t1 * t1 * t1 * t1 * t1 - } - - // Acceleration until halfway, then deceleration - export function easeInOutQuint(t: number) { - const t1 = t - 1 - return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * t1 * t1 * t1 * t1 * t1 - } - - // Accelerate exponentially until finish - export function easeInExpo(t: number) { - if (t === 0) { - return 0 - } - - return Math.pow(2, 10 * (t - 1)) // eslint-disable-line - } - - // Initial exponential acceleration slowing to stop - export function easeOutExpo(t: number) { - if (t === 1) { - return 1 - } - - return -Math.pow(2, -10 * t) + 1 // eslint-disable-line - } - - // Exponential acceleration and deceleration - export function easeInOutExpo(t: number) { - if (t === 0 || t === 1) { - return t - } - - const scaledTime = t * 2 - const scaledTime1 = scaledTime - 1 - - if (scaledTime < 1) { - return 0.5 * Math.pow(2, 10 * scaledTime1) // eslint-disable-line - } - - return 0.5 * (-Math.pow(2, -10 * scaledTime1) + 2) // eslint-disable-line - } - - // Increasing velocity until stop - export function easeInCirc(t: number) { - const scaledTime = t / 1 - return -1 * (Math.sqrt(1 - scaledTime * t) - 1) - } - - // Start fast, decreasing velocity until stop - export function easeOutCirc(t: number) { - const t1 = t - 1 - return Math.sqrt(1 - t1 * t1) - } - - // Fast increase in velocity, fast decrease in velocity - export function easeInOutCirc(t: number) { - const scaledTime = t * 2 - const scaledTime1 = scaledTime - 2 - - if (scaledTime < 1) { - return -0.5 * (Math.sqrt(1 - scaledTime * scaledTime) - 1) - } - - return 0.5 * (Math.sqrt(1 - scaledTime1 * scaledTime1) + 1) - } - - // Slow movement backwards then fast snap to finish - export function easeInBack(t: number, magnitude = 1.70158) { - return t * t * ((magnitude + 1) * t - magnitude) - } - - // Fast snap to backwards point then slow resolve to finish - export function easeOutBack(t: number, magnitude = 1.70158) { - const scaledTime = t / 1 - 1 - - return ( - scaledTime * scaledTime * ((magnitude + 1) * scaledTime + magnitude) + 1 - ) - } - - // Slow movement backwards, fast snap to past finish, slow resolve to finish - export function easeInOutBack(t: number, magnitude = 1.70158) { - const scaledTime = t * 2 - const scaledTime2 = scaledTime - 2 - - const s = magnitude * 1.525 - - if (scaledTime < 1) { - return 0.5 * scaledTime * scaledTime * ((s + 1) * scaledTime - s) - } - - return 0.5 * (scaledTime2 * scaledTime2 * ((s + 1) * scaledTime2 + s) + 2) - } - - // Bounces slowly then quickly to finish - export function easeInElastic(t: number, magnitude = 0.7) { - if (t === 0 || t === 1) { - return t - } - - const scaledTime = t / 1 - const scaledTime1 = scaledTime - 1 - - const p = 1 - magnitude - const s = (p / (2 * Math.PI)) * Math.asin(1) - - return -( - Math.pow(2, 10 * scaledTime1) * // eslint-disable-line - Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p) - ) - } - - // Fast acceleration, bounces to zero - export function easeOutElastic(t: number, magnitude = 0.7) { - const p = 1 - magnitude - const scaledTime = t * 2 - - if (t === 0 || t === 1) { - return t - } - - const s = (p / (2 * Math.PI)) * Math.asin(1) - return ( - Math.pow(2, -10 * scaledTime) * // eslint-disable-line - Math.sin(((scaledTime - s) * (2 * Math.PI)) / p) + - 1 - ) - } - - // Slow start and end, two bounces sandwich a fast motion - export function easeInOutElastic(t: number, magnitude = 0.65) { - const p = 1 - magnitude - - if (t === 0 || t === 1) { - return t - } - - const scaledTime = t * 2 - const scaledTime1 = scaledTime - 1 - - const s = (p / (2 * Math.PI)) * Math.asin(1) - - if (scaledTime < 1) { - return ( - -0.5 * - (Math.pow(2, 10 * scaledTime1) * // eslint-disable-line - Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p)) - ) - } - - return ( - Math.pow(2, -10 * scaledTime1) * // eslint-disable-line - Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p) * - 0.5 + - 1 - ) - } - - // Bounce to completion - export function easeOutBounce(t: number) { - const scaledTime = t / 1 - - if (scaledTime < 1 / 2.75) { - return 7.5625 * scaledTime * scaledTime - } - if (scaledTime < 2 / 2.75) { - const scaledTime2 = scaledTime - 1.5 / 2.75 - return 7.5625 * scaledTime2 * scaledTime2 + 0.75 - } - if (scaledTime < 2.5 / 2.75) { - const scaledTime2 = scaledTime - 2.25 / 2.75 - return 7.5625 * scaledTime2 * scaledTime2 + 0.9375 - } - { - const scaledTime2 = scaledTime - 2.625 / 2.75 - return 7.5625 * scaledTime2 * scaledTime2 + 0.984375 - } - } - - // Bounce increasing in velocity until completion - export function easeInBounce(t: number) { - return 1 - easeOutBounce(1 - t) - } - - // Bounce in and bounce out - export function easeInOutBounce(t: number) { - if (t < 0.5) { - return easeInBounce(t * 2) * 0.5 - } - - return easeOutBounce(t * 2 - 1) * 0.5 + 0.5 - } -} diff --git a/packages/x6-core/src/common/basecoat.ts b/packages/x6-core/src/common/basecoat.ts deleted file mode 100644 index c2a6d86cc32..00000000000 --- a/packages/x6-core/src/common/basecoat.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ObjectExt, Events } from '@antv/x6-common' -import { Disposable } from './disposable' - -export class Basecoat extends Events {} - -export interface Basecoat extends Disposable {} - -export namespace Basecoat { - export const dispose = Disposable.dispose -} - -ObjectExt.applyMixins(Basecoat, Disposable) diff --git a/packages/x6-core/src/common/disposable.ts b/packages/x6-core/src/common/disposable.ts deleted file mode 100644 index d0a80a7221c..00000000000 --- a/packages/x6-core/src/common/disposable.ts +++ /dev/null @@ -1,196 +0,0 @@ -/* eslint-disable no-underscore-dangle */ - -/** - * An object which implements the disposable pattern. - */ -export interface IDisposable { - /** - * Test whether the object has been disposed. - * - * #### Notes - * This property is always safe to access. - */ - readonly disposed: boolean - - /** - * Dispose of the resources held by the object. - * - * #### Notes - * If the object's `dispose` method is called more than once, all - * calls made after the first will be a no-op. - * - * #### Undefined Behavior - * It is undefined behavior to use any functionality of the object - * after it has been disposed unless otherwise explicitly noted. - */ - dispose(): void -} - -export class Disposable implements IDisposable { - // constructor() { - // if (Platform.IS_IE) { - // DomEvent.addListener(window, 'unload', () => { - // this.dispose() - // }) - // } - // } - - // eslint-disable-next-line - private _disposed?: boolean - - get disposed() { - return this._disposed === true - } - - public dispose() { - this._disposed = true - } -} - -export namespace Disposable { - export function dispose() { - return ( - target: any, - methodName: string, - descriptor: PropertyDescriptor, - ) => { - const raw = descriptor.value - const proto = target.__proto__ as IDisposable // eslint-disable-line - descriptor.value = function (this: IDisposable) { - if (this.disposed) { - return - } - raw.call(this) - proto.dispose.call(this) - } - } - } -} - -/** - * A disposable object which delegates to a callback function. - */ -export class DisposableDelegate implements IDisposable { - private callback: (() => void) | null - - /** - * Construct a new disposable delegate. - * - * @param callback - The callback function to invoke on dispose. - */ - constructor(callback: () => void) { - this.callback = callback - } - - /** - * Test whether the delegate has been disposed. - */ - get disposed(): boolean { - return !this.callback - } - - /** - * Dispose of the delegate and invoke the callback function. - */ - dispose(): void { - if (!this.callback) { - return - } - const callback = this.callback - this.callback = null - callback() - } -} - -/** - * An object which manages a collection of disposable items. - */ -export class DisposableSet implements IDisposable { - private isDisposed = false // eslint-disable-line:variable-name - - private items = new Set() - - /** - * Test whether the set has been disposed. - */ - get disposed(): boolean { - return this.isDisposed - } - - /** - * Dispose of the set and the items it contains. - * - * #### Notes - * Items are disposed in the order they are added to the set. - */ - dispose(): void { - if (this.isDisposed) { - return - } - this.isDisposed = true - - this.items.forEach((item) => { - item.dispose() - }) - this.items.clear() - } - - /** - * Test whether the set contains a specific item. - * - * @param item - The item of interest. - * - * @returns `true` if the set contains the item, `false` otherwise. - */ - contains(item: IDisposable): boolean { - return this.items.has(item) - } - - /** - * Add a disposable item to the set. - * - * @param item - The item to add to the set. - * - * #### Notes - * If the item is already contained in the set, this is a no-op. - */ - add(item: IDisposable): void { - this.items.add(item) - } - - /** - * Remove a disposable item from the set. - * - * @param item - The item to remove from the set. - * - * #### Notes - * If the item is not contained in the set, this is a no-op. - */ - remove(item: IDisposable): void { - this.items.delete(item) - } - - /** - * Remove all items from the set. - */ - clear(): void { - this.items.clear() - } -} - -export namespace DisposableSet { - /** - * Create a disposable set from an iterable of items. - * - * @param items - The iterable or array-like object of interest. - * - * @returns A new disposable initialized with the given items. - */ - export function from(items: IDisposable[]): DisposableSet { - const set = new DisposableSet() - items.forEach((item) => { - set.add(item) - }) - return set - } -} diff --git a/packages/x6-core/src/common/index.ts b/packages/x6-core/src/common/index.ts deleted file mode 100644 index 960a796555a..00000000000 --- a/packages/x6-core/src/common/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './basecoat' -export * from './disposable' -export * from './config' diff --git a/packages/x6-core/src/index.ts b/packages/x6-core/src/index.ts deleted file mode 100644 index 96080391b9d..00000000000 --- a/packages/x6-core/src/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as Registry from './registry' - -export * from './common' -export * from './model' -export * from './view' -export * from './renderer' -export * from './util' - -export { Registry } diff --git a/packages/x6-core/src/model/animation.ts b/packages/x6-core/src/model/animation.ts deleted file mode 100644 index a32b6253afb..00000000000 --- a/packages/x6-core/src/model/animation.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { ObjectExt, Dom, KeyValue } from '@antv/x6-common' -import { Timing, Interp } from '../animation' -import { Cell } from './cell' - -export class Animation { - protected readonly ids: { [path: string]: number } = {} - protected readonly cache: { - [path: string]: { - startValue: any - targetValue: any - options: Animation.StartOptions - } - } = {} - - constructor(protected readonly cell: Cell) {} - - get() { - return Object.keys(this.ids) - } - - start( - path: string | string[], - targetValue: T, - options: Animation.StartOptions = {}, - delim = '/', - ): () => void { - const startValue = this.cell.getPropByPath(path) - const localOptions = ObjectExt.defaults(options, Animation.defaultOptions) - const timing = this.getTiming(localOptions.timing) - const interpolate = this.getInterp( - localOptions.interp, - startValue, - targetValue, - ) - - let startTime = 0 - const key = Array.isArray(path) ? path.join(delim) : path - const paths = Array.isArray(path) ? path : path.split(delim) - const iterate = () => { - const now = new Date().getTime() - if (startTime === 0) { - startTime = now - } - - const elaspe = now - startTime - let progress = elaspe / localOptions.duration - if (progress < 1) { - this.ids[key] = Dom.requestAnimationFrame(iterate) - } else { - progress = 1 - } - - const currentValue = interpolate(timing(progress)) as T - this.cell.setPropByPath(paths, currentValue) - - if (options.progress) { - options.progress({ progress, currentValue, ...this.getArgs(key) }) - } - - if (progress === 1) { - // TODO: remove in the next major version - this.cell.notify('transition:end', this.getArgs(key)) - this.cell.notify('transition:complete', this.getArgs(key)) - options.complete && options.complete(this.getArgs(key)) - - this.cell.notify('transition:finish', this.getArgs(key)) - options.finish && options.finish(this.getArgs(key)) - this.clean(key) - } - } - - setTimeout(() => { - this.stop(path, undefined, delim) - this.cache[key] = { startValue, targetValue, options: localOptions } - this.ids[key] = Dom.requestAnimationFrame(iterate) - - // TODO: remove in the next major version - this.cell.notify('transition:begin', this.getArgs(key)) - this.cell.notify('transition:start', this.getArgs(key)) - options.start && options.start(this.getArgs(key)) - }, options.delay) - - return this.stop.bind(this, path, delim, options) - } - - stop( - path: string | string[], - options: Animation.StopOptions = {}, - delim = '/', - ) { - const paths = Array.isArray(path) ? path : path.split(delim) - Object.keys(this.ids) - .filter((key) => - ObjectExt.isEqual(paths, key.split(delim).slice(0, paths.length)), - ) - .forEach((key) => { - Dom.cancelAnimationFrame(this.ids[key]) - const data = this.cache[key] - const commonArgs = this.getArgs(key) - const localOptions = { ...data.options, ...options } - const jumpedToEnd = localOptions.jumpedToEnd - if (jumpedToEnd && data.targetValue != null) { - this.cell.setPropByPath(key, data.targetValue) - - this.cell.notify('transition:end', { ...commonArgs }) - this.cell.notify('transition:complete', { ...commonArgs }) - localOptions.complete && localOptions.complete({ ...commonArgs }) - } - - const stopArgs = { jumpedToEnd, ...commonArgs } - this.cell.notify('transition:stop', { ...stopArgs }) - localOptions.stop && localOptions.stop({ ...stopArgs }) - - this.cell.notify('transition:finish', { ...commonArgs }) - localOptions.finish && localOptions.finish({ ...commonArgs }) - - this.clean(key) - }) - - return this - } - - private clean(key: string) { - delete this.ids[key] - delete this.cache[key] - } - - private getTiming(timing: Timing.Names | Timing.Definition) { - return typeof timing === 'string' ? Timing[timing] : timing - } - - private getInterp( - interp: Interp.Definition | undefined, - startValue: T, - targetValue: T, - ) { - if (interp) { - return interp(startValue, targetValue) - } - - if (typeof targetValue === 'number') { - return Interp.number(startValue as number, targetValue) - } - - if (typeof targetValue === 'string') { - if (targetValue[0] === '#') { - return Interp.color(startValue as string, targetValue) - } - - return Interp.unit(startValue as string, targetValue) - } - - return Interp.object( - startValue as KeyValue, - targetValue as KeyValue, - ) - } - - private getArgs( - key: string, - ): Animation.CallbackArgs { - const data = this.cache[key] - return { - path: key, - startValue: data.startValue, - targetValue: data.targetValue, - cell: this.cell, - } - } -} - -export namespace Animation { - export interface BaseOptions { - delay: number - duration: number - timing: Timing.Names | Timing.Definition - } - - export type TargetValue = string | number | KeyValue - - export interface CallbackArgs { - cell: Cell - path: string - startValue: T - targetValue: T - } - - export interface ProgressArgs extends CallbackArgs { - progress: number - currentValue: T - } - - export interface StopArgs extends CallbackArgs { - jumpedToEnd?: boolean - } - - export interface StartOptions - extends Partial, - StopOptions { - interp?: Interp.Definition - /** - * A function to call when the animation begins. - */ - start?: (options: CallbackArgs) => void - /** - * A function to be called after each step of the animation, only once per - * animated element regardless of the number of animated properties. - */ - progress?: (options: ProgressArgs) => void - } - - export interface StopOptions { - /** - * A Boolean indicating whether to complete the animation immediately. - * Defaults to `false`. - */ - jumpedToEnd?: boolean - /** - * A function that is called once the animation completes. - */ - complete?: (options: CallbackArgs) => void - /** - * A function to be called when the animation stops. - */ - stop?: (options: StopArgs) => void - /** - * A function to be called when the animation completes or stops. - */ - finish?: (options: CallbackArgs) => void - } - - export const defaultOptions: BaseOptions = { - delay: 10, - duration: 100, - timing: 'linear', - } -} diff --git a/packages/x6-core/src/model/cell.ts b/packages/x6-core/src/model/cell.ts deleted file mode 100644 index 273cd003c4e..00000000000 --- a/packages/x6-core/src/model/cell.ts +++ /dev/null @@ -1,1865 +0,0 @@ -/* eslint-disable no-underscore-dangle */ - -import { - ArrayExt, - StringExt, - ObjectExt, - FunctionExt, - KeyValue, - Size, -} from '@antv/x6-common' -import { Rectangle, Point } from '@antv/x6-geometry' -import { NonUndefined } from 'utility-types' -import { Basecoat } from '../common' -import { Attr } from '../registry' -import { Model } from './model' -import { PortManager } from './port' -import { Store } from './store' -import { Edge } from './edge' -import { Animation } from './animation' -import { CellView, Markup } from '../view' -import { Node } from './node' -import { Renderer } from '../renderer' - -export class Cell< - Properties extends Cell.Properties = Cell.Properties, -> extends Basecoat { - // #region static - - protected static markup: Markup - protected static defaults: Cell.Defaults = {} - protected static attrHooks: Attr.Definitions = {} - protected static propHooks: Cell.PropHook[] = [] - - public static config(presets: C) { - const { markup, propHooks, attrHooks, ...others } = presets - - if (markup != null) { - this.markup = markup - } - - if (propHooks) { - this.propHooks = this.propHooks.slice() - if (Array.isArray(propHooks)) { - this.propHooks.push(...propHooks) - } else if (typeof propHooks === 'function') { - this.propHooks.push(propHooks) - } else { - Object.keys(propHooks).forEach((name) => { - const hook = propHooks[name] - if (typeof hook === 'function') { - this.propHooks.push(hook) - } - }) - } - } - - if (attrHooks) { - this.attrHooks = { ...this.attrHooks, ...attrHooks } - } - - this.defaults = ObjectExt.merge({}, this.defaults, others) - } - - public static getMarkup() { - return this.markup - } - - public static getDefaults( - raw?: boolean, - ): T { - return (raw ? this.defaults : ObjectExt.cloneDeep(this.defaults)) as T - } - - public static getAttrHooks() { - return this.attrHooks - } - - public static applyPropHooks( - cell: Cell, - metadata: Cell.Metadata, - ): Cell.Metadata { - return this.propHooks.reduce((memo, hook) => { - return hook ? FunctionExt.call(hook, cell, memo) : memo - }, metadata) - } - - // #endregion - - protected get [Symbol.toStringTag]() { - return Cell.toStringTag - } - - public readonly id: string - protected readonly store: Store - protected readonly animation: Animation - protected _model: Model | null // eslint-disable-line - protected _parent: Cell | null // eslint-disable-line - protected _children: Cell[] | null // eslint-disable-line - - constructor(metadata: Cell.Metadata = {}) { - super() - - const ctor = this.constructor as typeof Cell - const defaults = ctor.getDefaults(true) - const props = ObjectExt.merge( - {}, - this.preprocess(defaults), - this.preprocess(metadata), - ) - - this.id = props.id || StringExt.uuid() - this.store = new Store(props) - this.animation = new Animation(this) - this.setup() - this.init() - this.postprocess(metadata) - } - - init() {} - - // #region model - - get model() { - return this._model - } - - set model(model: Model | null) { - if (this._model !== model) { - this._model = model - } - } - - // #endregion - - protected preprocess( - metadata: Cell.Metadata, - ignoreIdCheck?: boolean, - ): Properties { - const id = metadata.id - const ctor = this.constructor as typeof Cell - const props = ctor.applyPropHooks(this, metadata) - - if (id == null && ignoreIdCheck !== true) { - props.id = StringExt.uuid() - } - - return props as Properties - } - - protected postprocess(metadata: Cell.Metadata) {} // eslint-disable-line - - protected setup() { - this.store.on('change:*', (metadata) => { - const { key, current, previous, options } = metadata - - this.notify('change:*', { - key, - options, - current, - previous, - cell: this, - }) - - this.notify(`change:${key}` as keyof Cell.EventArgs, { - options, - current, - previous, - cell: this, - }) - - const type = key as Edge.TerminalType - if (type === 'source' || type === 'target') { - this.notify(`change:terminal`, { - type, - current, - previous, - options, - cell: this, - }) - } - }) - - this.store.on('changed', ({ options }) => - this.notify('changed', { options, cell: this }), - ) - } - - notify( - name: Key, - args: Cell.EventArgs[Key], - ): this - notify(name: Exclude, args: any): this - notify( - name: Key, - args: Cell.EventArgs[Key], - ) { - this.trigger(name, args) - const model = this.model - if (model) { - model.notify(`cell:${name}`, args) - if (this.isNode()) { - model.notify(`node:${name}`, { ...args, node: this }) - } else if (this.isEdge()) { - model.notify(`edge:${name}`, { ...args, edge: this }) - } - } - return this - } - - isNode(): this is Node { - return false - } - - isEdge(): this is Edge { - return false - } - - isSameStore(cell: Cell) { - return this.store === cell.store - } - - get view() { - return this.store.get('view') - } - - get shape() { - return this.store.get('shape', '') - } - - // #region get/set - - getProp(): Properties - getProp(key: K): Properties[K] - getProp( - key: K, - defaultValue: Properties[K], - ): NonUndefined - getProp(key: string): T - getProp(key: string, defaultValue: T): T - getProp(key?: string, defaultValue?: any) { - if (key == null) { - return this.store.get() - } - - return this.store.get(key, defaultValue) - } - - setProp( - key: K, - value: Properties[K] | null | undefined | void, - options?: Cell.SetOptions, - ): this - setProp(key: string, value: any, options?: Cell.SetOptions): this - setProp(props: Partial, options?: Cell.SetOptions): this - setProp( - key: string | Partial, - value?: any, - options?: Cell.SetOptions, - ) { - if (typeof key === 'string') { - this.store.set(key, value, options) - } else { - const props = this.preprocess(key, true) - this.store.set(ObjectExt.merge({}, this.getProp(), props), value) - this.postprocess(key) - } - return this - } - - removeProp( - key: K | K[], - options?: Cell.SetOptions, - ): this - removeProp(key: string | string[], options?: Cell.SetOptions): this - removeProp(options?: Cell.SetOptions): this - removeProp( - key?: string | string[] | Cell.SetOptions, - options?: Cell.SetOptions, - ) { - if (typeof key === 'string' || Array.isArray(key)) { - this.store.removeByPath(key, options) - } else { - this.store.remove(options) - } - return this - } - - hasChanged(): boolean - hasChanged(key: K | null): boolean - hasChanged(key: string | null): boolean - hasChanged(key?: string | null) { - return key == null ? this.store.hasChanged() : this.store.hasChanged(key) - } - - getPropByPath(path: string | string[]) { - return this.store.getByPath(path) - } - - setPropByPath( - path: string | string[], - value: any, - options: Cell.SetByPathOptions = {}, - ) { - if (this.model) { - // update inner reference - if (path === 'children') { - this._children = value - ? value - .map((id: string) => this.model!.getCell(id)) - .filter((child: Cell) => child != null) - : null - } else if (path === 'parent') { - this._parent = value ? this.model.getCell(value) : null - } - } - - this.store.setByPath(path, value, options) - return this - } - - removePropByPath(path: string | string[], options: Cell.SetOptions = {}) { - const paths = Array.isArray(path) ? path : path.split('/') - // Once a property is removed from the `attrs` the CellView will - // recognize a `dirty` flag and re-render itself in order to remove - // the attribute from SVGElement. - if (paths[0] === 'attrs') { - options.dirty = true - } - this.store.removeByPath(paths, options) - return this - } - - prop(): Properties - prop(key: K): Properties[K] - prop(key: string): T - prop(path: string[]): T - prop( - key: K, - value: Properties[K] | null | undefined | void, - options?: Cell.SetOptions, - ): this - prop(key: string, value: any, options?: Cell.SetOptions): this - prop(path: string[], value: any, options?: Cell.SetOptions): this - prop(props: Partial, options?: Cell.SetOptions): this - prop( - key?: string | string[] | Partial, - value?: any, - options?: Cell.SetOptions, - ) { - if (key == null) { - return this.getProp() - } - - if (typeof key === 'string' || Array.isArray(key)) { - if (arguments.length === 1) { - return this.getPropByPath(key) - } - - if (value == null) { - return this.removePropByPath(key, options || {}) - } - - return this.setPropByPath(key, value, options || {}) - } - - return this.setProp(key, value || {}) - } - - previous(name: K): Properties[K] | undefined - previous(name: string): T | undefined - previous(name: string) { - return this.store.getPrevious(name as keyof Cell.Properties) - } - - // #endregion - - // #region zIndex - - get zIndex() { - return this.getZIndex() - } - - set zIndex(z: number | undefined | null) { - if (z == null) { - this.removeZIndex() - } else { - this.setZIndex(z) - } - } - - getZIndex() { - return this.store.get('zIndex') - } - - setZIndex(z: number, options: Cell.SetOptions = {}) { - this.store.set('zIndex', z, options) - return this - } - - removeZIndex(options: Cell.SetOptions = {}) { - this.store.remove('zIndex', options) - return this - } - - toFront(options: Cell.ToFrontOptions = {}) { - const model = this.model - if (model) { - let z = model.getMaxZIndex() - let cells: Cell[] - if (options.deep) { - cells = this.getDescendants({ deep: true, breadthFirst: true }) - cells.unshift(this) - } else { - cells = [this] - } - - z = z - cells.length + 1 - - const count = model.total() - let changed = model.indexOf(this) !== count - cells.length - if (!changed) { - changed = cells.some((cell, index) => cell.getZIndex() !== z + index) - } - - if (changed) { - this.batchUpdate('to-front', () => { - z += cells.length - cells.forEach((cell, index) => { - cell.setZIndex(z + index, options) - }) - }) - } - } - - return this - } - - toBack(options: Cell.ToBackOptions = {}) { - const model = this.model - if (model) { - let z = model.getMinZIndex() - let cells: Cell[] - - if (options.deep) { - cells = this.getDescendants({ deep: true, breadthFirst: true }) - cells.unshift(this) - } else { - cells = [this] - } - - let changed = model.indexOf(this) !== 0 - if (!changed) { - changed = cells.some((cell, index) => cell.getZIndex() !== z + index) - } - - if (changed) { - this.batchUpdate('to-back', () => { - z -= cells.length - cells.forEach((cell, index) => { - cell.setZIndex(z + index, options) - }) - }) - } - } - - return this - } - - // #endregion - - // #region markup - - get markup() { - return this.getMarkup() - } - - set markup(value: Markup | undefined | null) { - if (value == null) { - this.removeMarkup() - } else { - this.setMarkup(value) - } - } - - getMarkup() { - let markup = this.store.get('markup') - if (markup == null) { - const ctor = this.constructor as typeof Cell - markup = ctor.getMarkup() - } - return markup - } - - setMarkup(markup: Markup, options: Cell.SetOptions = {}) { - this.store.set('markup', markup, options) - return this - } - - removeMarkup(options: Cell.SetOptions = {}) { - this.store.remove('markup', options) - return this - } - - // #endregion - - // #region attrs - - get attrs() { - return this.getAttrs() - } - - set attrs(value: Attr.CellAttrs | null | undefined) { - if (value == null) { - this.removeAttrs() - } else { - this.setAttrs(value) - } - } - - getAttrs() { - const result = this.store.get('attrs') - return result ? { ...result } : {} - } - - setAttrs( - attrs: Attr.CellAttrs | null | undefined, - options: Cell.SetAttrOptions = {}, - ) { - if (attrs == null) { - this.removeAttrs(options) - } else { - const set = (attrs: Attr.CellAttrs) => - this.store.set('attrs', attrs, options) - - if (options.overwrite === true) { - set(attrs) - } else { - const prev = this.getAttrs() - if (options.deep === false) { - set({ ...prev, ...attrs }) - } else { - set(ObjectExt.merge({}, prev, attrs)) - } - } - } - - return this - } - - replaceAttrs(attrs: Attr.CellAttrs, options: Cell.SetOptions = {}) { - return this.setAttrs(attrs, { ...options, overwrite: true }) - } - - updateAttrs(attrs: Attr.CellAttrs, options: Cell.SetOptions = {}) { - return this.setAttrs(attrs, { ...options, deep: false }) - } - - removeAttrs(options: Cell.SetOptions = {}) { - this.store.remove('attrs', options) - return this - } - - getAttrDefinition(attrName: string) { - if (!attrName) { - return null - } - - const ctor = this.constructor as typeof Cell - const hooks = ctor.getAttrHooks() || {} - let definition = hooks[attrName] || Attr.registry.get(attrName) - if (!definition) { - const name = StringExt.camelCase(attrName) - definition = hooks[name] || Attr.registry.get(name) - } - - return definition || null - } - - getAttrByPath(): Attr.CellAttrs - getAttrByPath(path: string | string[]): T - getAttrByPath(path?: string | string[]) { - if (path == null || path === '') { - return this.getAttrs() - } - return this.getPropByPath(this.prefixAttrPath(path)) - } - - setAttrByPath( - path: string | string[], - value: Attr.ComplexAttrValue, - options: Cell.SetOptions = {}, - ) { - this.setPropByPath(this.prefixAttrPath(path), value, options) - return this - } - - removeAttrByPath(path: string | string[], options: Cell.SetOptions = {}) { - this.removePropByPath(this.prefixAttrPath(path), options) - return this - } - - protected prefixAttrPath(path: string | string[]) { - return Array.isArray(path) ? ['attrs'].concat(path) : `attrs/${path}` - } - - attr(): Attr.CellAttrs - attr(path: string | string[]): T - attr( - path: string | string[], - value: Attr.ComplexAttrValue | null, - options?: Cell.SetOptions, - ): this - attr(attrs: Attr.CellAttrs, options?: Cell.SetAttrOptions): this - attr( - path?: string | string[] | Attr.CellAttrs, - value?: Attr.ComplexAttrValue | Cell.SetOptions, - options?: Cell.SetOptions, - ) { - if (path == null) { - return this.getAttrByPath() - } - - if (typeof path === 'string' || Array.isArray(path)) { - if (arguments.length === 1) { - return this.getAttrByPath(path) - } - if (value == null) { - return this.removeAttrByPath(path, options || {}) - } - return this.setAttrByPath( - path, - value as Attr.ComplexAttrValue, - options || {}, - ) - } - - return this.setAttrs(path, (value || {}) as Cell.SetOptions) - } - - // #endregion - - // #region visible - - get visible() { - return this.isVisible() - } - - set visible(value: boolean) { - this.setVisible(value) - } - - setVisible(visible: boolean, options: Cell.SetOptions = {}) { - this.store.set('visible', visible, options) - return this - } - - isVisible() { - return this.store.get('visible') !== false - } - - show(options: Cell.SetOptions = {}) { - if (!this.isVisible()) { - this.setVisible(true, options) - } - return this - } - - hide(options: Cell.SetOptions = {}) { - if (this.isVisible()) { - this.setVisible(false, options) - } - return this - } - - toggleVisible(visible: boolean, options?: Cell.SetOptions): this - toggleVisible(options?: Cell.SetOptions): this - toggleVisible( - isVisible?: boolean | Cell.SetOptions, - options: Cell.SetOptions = {}, - ) { - const visible = - typeof isVisible === 'boolean' ? isVisible : !this.isVisible() - const localOptions = typeof isVisible === 'boolean' ? options : isVisible - if (visible) { - this.show(localOptions) - } else { - this.hide(localOptions) - } - return this - } - - // #endregion - - // #region data - - get data(): Properties['data'] { - return this.getData() - } - - set data(val: Properties['data']) { - this.setData(val) - } - - getData(): T { - return this.store.get('data') - } - - setData(data: T, options: Cell.SetDataOptions = {}) { - if (data == null) { - this.removeData(options) - } else { - const set = (data: T) => this.store.set('data', data, options) - - if (options.overwrite === true) { - set(data) - } else { - const prev = this.getData>() - if (options.deep === false) { - set(typeof data === 'object' ? { ...prev, ...data } : data) - } else { - set(ObjectExt.merge({}, prev, data)) - } - } - } - - return this - } - - replaceData(data: T, options: Cell.SetOptions = {}) { - return this.setData(data, { ...options, overwrite: true }) - } - - updateData(data: T, options: Cell.SetOptions = {}) { - return this.setData(data, { ...options, deep: false }) - } - - removeData(options: Cell.SetOptions = {}) { - this.store.remove('data', options) - return this - } - - // #endregion - - // #region parent children - - get parent(): Cell | null { - return this.getParent() - } - - get children() { - return this.getChildren() - } - - getParentId() { - return this.store.get('parent') - } - - getParent(): T | null { - const parentId = this.getParentId() - if (parentId && this.model) { - const parent = this.model.getCell(parentId) - this._parent = parent - return parent - } - return null - } - - getChildren() { - const childrenIds = this.store.get('children') - if (childrenIds && childrenIds.length && this.model) { - const children = childrenIds - .map((id) => this.model?.getCell(id)) - .filter((cell) => cell != null) as Cell[] - this._children = children - return [...children] - } - return null - } - - hasParent() { - return this.parent != null - } - - isParentOf(child: Cell | null): boolean { - return child != null && child.getParent() === this - } - - isChildOf(parent: Cell | null): boolean { - return parent != null && this.getParent() === parent - } - - eachChild( - iterator: (child: Cell, index: number, children: Cell[]) => void, - context?: any, - ) { - if (this.children) { - this.children.forEach(iterator, context) - } - return this - } - - filterChild( - filter: (cell: Cell, index: number, arr: Cell[]) => boolean, - context?: any, - ): Cell[] { - return this.children ? this.children.filter(filter, context) : [] - } - - getChildCount() { - return this.children == null ? 0 : this.children.length - } - - getChildIndex(child: Cell) { - return this.children == null ? -1 : this.children.indexOf(child) - } - - getChildAt(index: number) { - return this.children != null && index >= 0 ? this.children[index] : null - } - - getAncestors(options: { deep?: boolean } = {}): Cell[] { - const ancestors: Cell[] = [] - let parent = this.getParent() - while (parent) { - ancestors.push(parent) - parent = options.deep !== false ? parent.getParent() : null - } - return ancestors - } - - getDescendants(options: Cell.GetDescendantsOptions = {}): Cell[] { - if (options.deep !== false) { - // breadth first - if (options.breadthFirst) { - const cells = [] - const queue = this.getChildren() || [] - - while (queue.length > 0) { - const parent = queue.shift()! - const children = parent.getChildren() - cells.push(parent) - if (children) { - queue.push(...children) - } - } - return cells - } - - // depth first - { - const cells = this.getChildren() || [] - cells.forEach((cell) => { - cells.push(...cell.getDescendants(options)) - }) - return cells - } - } - - return this.getChildren() || [] - } - - isDescendantOf( - ancestor: Cell | null, - options: { deep?: boolean } = {}, - ): boolean { - if (ancestor == null) { - return false - } - - if (options.deep !== false) { - let current = this.getParent() - while (current) { - if (current === ancestor) { - return true - } - current = current.getParent() - } - - return false - } - - return this.isChildOf(ancestor) - } - - isAncestorOf( - descendant: Cell | null, - options: { deep?: boolean } = {}, - ): boolean { - if (descendant == null) { - return false - } - - return descendant.isDescendantOf(this, options) - } - - contains(cell: Cell | null) { - return this.isAncestorOf(cell) - } - - getCommonAncestor(...cells: (Cell | null | undefined)[]): Cell | null { - return Cell.getCommonAncestor(this, ...cells) - } - - setParent(parent: Cell | null, options: Cell.SetOptions = {}) { - this._parent = parent - if (parent) { - this.store.set('parent', parent.id, options) - } else { - this.store.remove('parent', options) - } - return this - } - - setChildren(children: Cell[] | null, options: Cell.SetOptions = {}) { - this._children = children - if (children != null) { - this.store.set( - 'children', - children.map((child) => child.id), - options, - ) - } else { - this.store.remove('children', options) - } - return this - } - - unembed(child: Cell, options: Cell.SetOptions = {}) { - const children = this.children - if (children != null && child != null) { - const index = this.getChildIndex(child) - if (index !== -1) { - children.splice(index, 1) - child.setParent(null, options) - this.setChildren(children, options) - } - } - return this - } - - embed(child: Cell, options: Cell.SetOptions = {}) { - child.addTo(this, options) - return this - } - - addTo(model: Model, options?: Cell.SetOptions): this - addTo(parent: Cell, options?: Cell.SetOptions): this - addTo(target: Model | Cell, options: Cell.SetOptions = {}) { - if (Cell.isCell(target)) { - target.addChild(this, options) - } else { - target.addCell(this, options) - } - return this - } - - insertTo(parent: Cell, index?: number, options: Cell.SetOptions = {}) { - parent.insertChild(this, index, options) - return this - } - - addChild(child: Cell | null, options: Cell.SetOptions = {}) { - return this.insertChild(child, undefined, options) - } - - insertChild( - child: Cell | null, - index?: number, - options: Cell.SetOptions = {}, - ): this { - if (child != null && child !== this) { - const oldParent = child.getParent() - const changed = this !== oldParent - - let pos = index - if (pos == null) { - pos = this.getChildCount() - if (!changed) { - pos -= 1 - } - } - - // remove from old parent - if (oldParent) { - const children = oldParent.getChildren() - if (children) { - const index = children.indexOf(child) - if (index >= 0) { - child.setParent(null, options) - children.splice(index, 1) - oldParent.setChildren(children, options) - } - } - } - - let children = this.children - if (children == null) { - children = [] - children.push(child) - } else { - children.splice(pos, 0, child) - } - - child.setParent(this, options) - this.setChildren(children, options) - - if (changed && this.model) { - const incomings = this.model.getIncomingEdges(this) - const outgoings = this.model.getOutgoingEdges(this) - - if (incomings) { - incomings.forEach((edge) => edge.updateParent(options)) - } - - if (outgoings) { - outgoings.forEach((edge) => edge.updateParent(options)) - } - } - - if (this.model) { - this.model.addCell(child, options) - } - } - - return this - } - - removeFromParent(options: Cell.RemoveOptions = {}) { - const parent = this.getParent() - if (parent != null) { - const index = parent.getChildIndex(this) - parent.removeChildAt(index, options) - } - return this - } - - removeChild(child: Cell, options: Cell.RemoveOptions = {}) { - const index = this.getChildIndex(child) - return this.removeChildAt(index, options) - } - - removeChildAt(index: number, options: Cell.RemoveOptions = {}) { - const child = this.getChildAt(index) - const children = this.children - - if (children != null && child != null) { - this.unembed(child, options) - child.remove(options) - } - - return child - } - - remove(options: Cell.RemoveOptions = {}) { - this.batchUpdate('remove', () => { - const parent = this.getParent() - if (parent) { - parent.removeChild(this, options) - } - - if (options.deep !== false) { - this.eachChild((child) => child.remove(options)) - } - - if (this.model) { - this.model.removeCell(this, options) - } - }) - return this - } - - // #endregion - - // #region transition - - transition( - path: K, - target: Properties[K], - options?: Animation.StartOptions, - delim?: string, - ): () => void - transition( - path: string | string[], - target: T, - options?: Animation.StartOptions, - delim?: string, - ): () => void - transition( - path: string | string[], - target: T, - options: Animation.StartOptions = {}, - delim = '/', - ) { - return this.animation.start(path, target, options, delim) - } - - stopTransition( - path: string | string[], - options?: Animation.StopOptions, - delim = '/', - ) { - this.animation.stop(path, options, delim) - return this - } - - getTransitions() { - return this.animation.get() - } - - // #endregion - - // #region transform - - // eslint-disable-next-line - translate(tx: number, ty: number, options?: Cell.TranslateOptions) { - return this - } - - scale( - sx: number, // eslint-disable-line - sy: number, // eslint-disable-line - origin?: Point | Point.PointLike, // eslint-disable-line - options?: Node.SetOptions, // eslint-disable-line - ) { - return this - } - - // #endregion - - // #region tools - - addTools( - items: Cell.ToolItem | Cell.ToolItem[], - options?: Cell.AddToolOptions, - ): void - addTools( - items: Cell.ToolItem | Cell.ToolItem[], - name: string, - options?: Cell.AddToolOptions, - ): void - addTools( - items: Cell.ToolItem | Cell.ToolItem[], - obj?: string | Cell.AddToolOptions, - options?: Cell.AddToolOptions, - ) { - const toolItems = Array.isArray(items) ? items : [items] - const name = typeof obj === 'string' ? obj : null - const config = - typeof obj === 'object' ? obj : typeof options === 'object' ? options : {} - - if (config.reset) { - return this.setTools( - { name, items: toolItems, local: config.local }, - config, - ) - } - let tools = ObjectExt.cloneDeep(this.getTools()) - if (tools == null || name == null || tools.name === name) { - if (tools == null) { - tools = {} as Cell.Tools - } - - if (!tools.items) { - tools.items = [] - } - - tools.name = name - tools.items = [...tools.items, ...toolItems] - - return this.setTools({ ...tools }, config) - } - } - - setTools(tools?: Cell.ToolsLoose | null, options: Cell.SetOptions = {}) { - if (tools == null) { - this.removeTools() - } else { - this.store.set('tools', Cell.normalizeTools(tools), options) - } - return this - } - - getTools(): Cell.Tools | null { - return this.store.get('tools') - } - - removeTools(options: Cell.SetOptions = {}) { - this.store.remove('tools', options) - return this - } - - hasTools(name?: string) { - const tools = this.getTools() - if (tools == null) { - return false - } - - if (name == null) { - return true - } - - return tools.name === name - } - - hasTool(name: string) { - const tools = this.getTools() - if (tools == null) { - return false - } - return tools.items.some((item) => - typeof item === 'string' ? item === name : item.name === name, - ) - } - - removeTool(name: string, options?: Cell.SetOptions): this - removeTool(index: number, options?: Cell.SetOptions): this - removeTool(nameOrIndex: string | number, options: Cell.SetOptions = {}) { - const tools = ObjectExt.cloneDeep(this.getTools()) - if (tools) { - let updated = false - const items = tools.items.slice() - const remove = (index: number) => { - items.splice(index, 1) - updated = true - } - - if (typeof nameOrIndex === 'number') { - remove(nameOrIndex) - } else { - for (let i = items.length - 1; i >= 0; i -= 1) { - const item = items[i] - const exist = - typeof item === 'string' - ? item === nameOrIndex - : item.name === nameOrIndex - if (exist) { - remove(i) - } - } - } - - if (updated) { - tools.items = items - this.setTools(tools, options) - } - } - return this - } - - // #endregion - - // #region common - - // eslint-disable-next-line - getBBox(options?: { deep?: boolean }) { - return new Rectangle() - } - - // eslint-disable-next-line - getConnectionPoint(edge: Edge, type: Edge.TerminalType) { - return new Point() - } - - toJSON( - options: Cell.ToJSONOptions = {}, - ): this extends Node - ? Node.Properties - : this extends Edge - ? Edge.Properties - : Properties { - const props = { ...this.store.get() } - const toString = Object.prototype.toString - const cellType = this.isNode() ? 'node' : this.isEdge() ? 'edge' : 'cell' - - if (!props.shape) { - const ctor = this.constructor - throw new Error( - `Unable to serialize ${cellType} missing "shape" prop, check the ${cellType} "${ - ctor.name || toString.call(ctor) - }"`, - ) - } - - const ctor = this.constructor as typeof Cell - const diff = options.diff === true - const attrs = props.attrs || {} - const presets = ctor.getDefaults(true) as Properties - // When `options.diff` is `true`, we should process the custom options, - // such as `width`, `height` etc. to ensure the comparing work correctly. - const defaults = diff ? this.preprocess(presets, true) : presets - const defaultAttrs = defaults.attrs || {} - const finalAttrs: Attr.CellAttrs = {} - - Object.keys(props).forEach((key) => { - const val = props[key] - if ( - val != null && - !Array.isArray(val) && - typeof val === 'object' && - !ObjectExt.isPlainObject(val) - ) { - throw new Error( - `Can only serialize ${cellType} with plain-object props, but got a "${toString.call( - val, - )}" type of key "${key}" on ${cellType} "${this.id}"`, - ) - } - - if (key !== 'attrs' && key !== 'shape' && diff) { - const preset = defaults[key] - if (ObjectExt.isEqual(val, preset)) { - delete props[key] - } - } - }) - - Object.keys(attrs).forEach((key) => { - const attr = attrs[key] - const defaultAttr = defaultAttrs[key] - - Object.keys(attr).forEach((name) => { - const value = attr[name] as KeyValue - const defaultValue = defaultAttr ? defaultAttr[name] : null - - if ( - value != null && - typeof value === 'object' && - !Array.isArray(value) - ) { - Object.keys(value).forEach((subName) => { - const subValue = value[subName] - if ( - defaultAttr == null || - defaultValue == null || - !ObjectExt.isObject(defaultValue) || - !ObjectExt.isEqual(defaultValue[subName], subValue) - ) { - if (finalAttrs[key] == null) { - finalAttrs[key] = {} - } - if (finalAttrs[key][name] == null) { - finalAttrs[key][name] = {} - } - const tmp = finalAttrs[key][name] as KeyValue - tmp[subName] = subValue - } - }) - } else if ( - defaultAttr == null || - !ObjectExt.isEqual(defaultValue, value) - ) { - // `value` is not an object, default attribute with `key` does not - // exist or it is different than the attribute value set on the cell. - if (finalAttrs[key] == null) { - finalAttrs[key] = {} - } - finalAttrs[key][name] = value as any - } - }) - }) - - const finalProps = { - ...props, - attrs: ObjectExt.isEmpty(finalAttrs) ? undefined : finalAttrs, - } - - if (finalProps.attrs == null) { - delete finalProps.attrs - } - - const ret = finalProps as any - if (ret.angle === 0) { - delete ret.angle - } - - return ObjectExt.cloneDeep(ret) - } - - clone( - options: Cell.CloneOptions = {}, - ): this extends Node ? Node : this extends Edge ? Edge : Cell { - if (!options.deep) { - const data = { ...this.store.get() } - if (!options.keepId) { - delete data.id - } - delete data.parent - delete data.children - const ctor = this.constructor as typeof Cell - return new ctor(data) as any // eslint-disable-line new-cap - } - - // Deep cloning. Clone the cell itself and all its children. - const map = Cell.deepClone(this) - return map[this.id] as any - } - - findView(renderer: Renderer): CellView | null { - return renderer.findViewByCell(this) - } - - // #endregion - - // #region batch - - startBatch( - name: Model.BatchName, - data: KeyValue = {}, - model: Model | null = this.model, - ) { - this.notify('batch:start', { name, data, cell: this }) - - if (model) { - model.startBatch(name, { ...data, cell: this }) - } - - return this - } - - stopBatch( - name: Model.BatchName, - data: KeyValue = {}, - model: Model | null = this.model, - ) { - if (model) { - model.stopBatch(name, { ...data, cell: this }) - } - - this.notify('batch:stop', { name, data, cell: this }) - return this - } - - batchUpdate(name: Model.BatchName, execute: () => T, data?: KeyValue): T { - // The model is null after cell was removed(remove batch). - // So we should temp save model to trigger pairing batch event. - const model = this.model - this.startBatch(name, data, model) - const result = execute() - this.stopBatch(name, data, model) - return result - } - - // #endregion - - // #region IDisposable - - @Basecoat.dispose() - dispose() { - this.removeFromParent() - this.store.dispose() - } - - // #endregion -} - -export namespace Cell { - export interface Common { - view?: string - shape?: string - markup?: Markup - attrs?: Attr.CellAttrs - zIndex?: number - visible?: boolean - data?: any - } - - export interface Defaults extends Common {} - - export interface Metadata extends Common, KeyValue { - id?: string - tools?: ToolsLoose - } - - export interface Properties extends Defaults, Metadata { - parent?: string - children?: string[] - tools?: Tools - } -} - -export namespace Cell { - export type ToolItem = - | string - | { - name: string - args?: any - } - - export interface Tools { - name?: string | null - local?: boolean - items: ToolItem[] - } - - export type ToolsLoose = ToolItem | ToolItem[] | Tools - - export function normalizeTools(raw: ToolsLoose): Tools { - if (typeof raw === 'string') { - return { items: [raw] } - } - - if (Array.isArray(raw)) { - return { items: raw } - } - - if ((raw as Tools).items) { - return raw as Tools - } - - return { - items: [raw as ToolItem], - } - } -} - -export namespace Cell { - export interface SetOptions extends Store.SetOptions {} - - export interface MutateOptions extends Store.MutateOptions {} - - export interface RemoveOptions extends SetOptions { - deep?: boolean - } - - export interface SetAttrOptions extends SetOptions { - deep?: boolean - overwrite?: boolean - } - - export interface SetDataOptions extends SetOptions { - deep?: boolean - overwrite?: boolean - } - - export interface SetByPathOptions extends Store.SetByPathOptions {} - - export interface ToFrontOptions extends SetOptions { - deep?: boolean - } - - export interface ToBackOptions extends ToFrontOptions {} - - export interface TranslateOptions extends SetOptions { - tx?: number - ty?: number - translateBy?: string | number - } - - export interface AddToolOptions extends SetOptions { - reset?: boolean - local?: boolean - } - - export interface GetDescendantsOptions { - deep?: boolean - breadthFirst?: boolean - } - - export interface ToJSONOptions { - diff?: boolean - } - - export interface CloneOptions { - deep?: boolean - keepId?: boolean - } -} - -export namespace Cell { - export interface EventArgs { - 'transition:start': Animation.CallbackArgs - 'transition:progress': Animation.ProgressArgs - 'transition:complete': Animation.CallbackArgs - 'transition:stop': Animation.StopArgs - 'transition:finish': Animation.CallbackArgs - - // common - 'change:*': ChangeAnyKeyArgs - 'change:attrs': ChangeArgs - 'change:zIndex': ChangeArgs - 'change:markup': ChangeArgs - 'change:visible': ChangeArgs - 'change:parent': ChangeArgs - 'change:children': ChangeArgs - 'change:tools': ChangeArgs - 'change:view': ChangeArgs - 'change:data': ChangeArgs - - // node - 'change:size': NodeChangeArgs - 'change:angle': NodeChangeArgs - 'change:position': NodeChangeArgs - 'change:ports': NodeChangeArgs - 'change:portMarkup': NodeChangeArgs - 'change:portLabelMarkup': NodeChangeArgs - 'change:portContainerMarkup': NodeChangeArgs - 'ports:removed': { - cell: Cell - node: Node - removed: PortManager.Port[] - } - 'ports:added': { - cell: Cell - node: Node - added: PortManager.Port[] - } - - // edge - 'change:source': EdgeChangeArgs - 'change:target': EdgeChangeArgs - 'change:terminal': EdgeChangeArgs & { - type: Edge.TerminalType - } - 'change:router': EdgeChangeArgs - 'change:connector': EdgeChangeArgs - 'change:vertices': EdgeChangeArgs - 'change:labels': EdgeChangeArgs - 'change:defaultLabel': EdgeChangeArgs - 'change:vertexMarkup': EdgeChangeArgs - 'change:arrowheadMarkup': EdgeChangeArgs - 'vertexs:added': { - cell: Cell - edge: Edge - added: Point.PointLike[] - } - 'vertexs:removed': { - cell: Cell - edge: Edge - removed: Point.PointLike[] - } - 'labels:added': { - cell: Cell - edge: Edge - added: Edge.Label[] - } - 'labels:removed': { - cell: Cell - edge: Edge - removed: Edge.Label[] - } - - 'batch:start': { - name: Model.BatchName - data: KeyValue - cell: Cell - } - - 'batch:stop': { - name: Model.BatchName - data: KeyValue - cell: Cell - } - - changed: { - cell: Cell - options: MutateOptions - } - - added: { - cell: Cell - index: number - options: Cell.SetOptions - } - - removed: { - cell: Cell - index: number - options: Cell.RemoveOptions - } - } - - interface ChangeAnyKeyArgs { - key: T - current: Properties[T] - previous: Properties[T] - options: MutateOptions - cell: Cell - } - - export interface ChangeArgs { - cell: Cell - current?: T - previous?: T - options: MutateOptions - } - - interface NodeChangeArgs extends ChangeArgs { - node: Node - } - - interface EdgeChangeArgs extends ChangeArgs { - edge: Edge - } -} - -export namespace Cell { - export const toStringTag = `X6.${Cell.name}` - - export function isCell(instance: any): instance is Cell { - if (instance == null) { - return false - } - - if (instance instanceof Cell) { - return true - } - - const tag = instance[Symbol.toStringTag] - const cell = instance as Cell - - if ( - (tag == null || tag === toStringTag) && - typeof cell.isNode === 'function' && - typeof cell.isEdge === 'function' && - typeof cell.prop === 'function' && - typeof cell.attr === 'function' - ) { - return true - } - - return false - } -} - -export namespace Cell { - export function getCommonAncestor( - ...cells: (Cell | null | undefined)[] - ): Cell | null { - const ancestors = cells - .filter((cell) => cell != null) - .map((cell) => cell!.getAncestors()) - .sort((a, b) => { - return a.length - b.length - }) - - const first = ancestors.shift()! - return ( - first.find((cell) => ancestors.every((item) => item.includes(cell))) || - null - ) - } - - export interface GetCellsBBoxOptions { - deep?: boolean - } - - export function getCellsBBox( - cells: Cell[], - options: GetCellsBBoxOptions = {}, - ) { - let bbox: Rectangle | null = null - - for (let i = 0, ii = cells.length; i < ii; i += 1) { - const cell = cells[i] - let rect = cell.getBBox(options) - if (rect) { - if (cell.isNode()) { - const angle = cell.getAngle() - if (angle != null && angle !== 0) { - rect = rect.bbox(angle) - } - } - bbox = bbox == null ? rect : bbox.union(rect) - } - } - - return bbox - } - - export function deepClone(cell: Cell) { - const cells = [cell, ...cell.getDescendants({ deep: true })] - return Cell.cloneCells(cells) - } - - export function cloneCells(cells: Cell[]) { - const inputs = ArrayExt.uniq(cells) - const cloneMap = inputs.reduce>((map, cell) => { - map[cell.id] = cell.clone() - return map - }, {}) - - inputs.forEach((cell) => { - const clone = cloneMap[cell.id] - if (clone.isEdge()) { - const sourceId = clone.getSourceCellId() - const targetId = clone.getTargetCellId() - if (sourceId && cloneMap[sourceId]) { - // Source is a node and the node is among the clones. - // Then update the source of the cloned edge. - clone.setSource({ - ...clone.getSource(), - cell: cloneMap[sourceId].id, - }) - } - if (targetId && cloneMap[targetId]) { - // Target is a node and the node is among the clones. - // Then update the target of the cloned edge. - clone.setTarget({ - ...clone.getTarget(), - cell: cloneMap[targetId].id, - }) - } - } - - // Find the parent of the original cell - const parent = cell.getParent() - if (parent && cloneMap[parent.id]) { - clone.setParent(cloneMap[parent.id]) - } - - // Find the children of the original cell - const children = cell.getChildren() - if (children && children.length) { - const embeds = children.reduce((memo, child) => { - // Embedded cells that are not being cloned can not be carried - // over with other embedded cells. - if (cloneMap[child.id]) { - memo.push(cloneMap[child.id]) - } - return memo - }, []) - - if (embeds.length > 0) { - clone.setChildren(embeds) - } - } - }) - - return cloneMap - } -} - -export namespace Cell { - export type Definition = typeof Cell - - export type PropHook = ( - this: C, - metadata: M, - ) => M - - export type PropHooks = - | KeyValue> - | PropHook - | PropHook[] - - export interface Config - extends Defaults, - KeyValue { - constructorName?: string - overwrite?: boolean - propHooks?: PropHooks - attrHooks?: Attr.Definitions - } -} - -export namespace Cell { - Cell.config({ - propHooks({ tools, ...metadata }) { - if (tools) { - metadata.tools = normalizeTools(tools) - } - return metadata - }, - }) -} diff --git a/packages/x6-core/src/model/collection.ts b/packages/x6-core/src/model/collection.ts deleted file mode 100644 index 28b36776a9d..00000000000 --- a/packages/x6-core/src/model/collection.ts +++ /dev/null @@ -1,542 +0,0 @@ -import { ArrayExt } from '@antv/x6-common' -import { Basecoat } from '../common' -import { Cell } from './cell' -import { Node } from './node' -import { Edge } from './edge' - -export class Collection extends Basecoat { - public length = 0 - public comparator: Collection.Comparator | null - private cells: Cell[] - private map: { [id: string]: Cell } - - constructor(cells: Cell | Cell[], options: Collection.Options = {}) { - super() - this.comparator = options.comparator || 'zIndex' - this.clean() - if (cells) { - this.reset(cells, { silent: true }) - } - } - - toJSON() { - return this.cells.map((cell) => cell.toJSON()) - } - - add(cells: Cell | Cell[], options?: Collection.AddOptions): this - add( - cells: Cell | Cell[], - index: number, - options?: Collection.AddOptions, - ): this - add( - cells: Cell | Cell[], - index?: number | Collection.AddOptions, - options?: Collection.AddOptions, - ) { - let localIndex: number - let localOptions: Collection.AddOptions - - if (typeof index === 'number') { - localIndex = index - localOptions = { merge: false, ...options } - } else { - localIndex = this.length - localOptions = { merge: false, ...index } - } - - if (localIndex > this.length) { - localIndex = this.length - } - if (localIndex < 0) { - localIndex += this.length + 1 - } - - const entities = Array.isArray(cells) ? cells : [cells] - const sortable = - this.comparator && - typeof index !== 'number' && - localOptions.sort !== false - const sortAttr = this.comparator || null - - let sort = false - const added: Cell[] = [] - const merged: Cell[] = [] - - entities.forEach((cell) => { - const existing = this.get(cell) - if (existing) { - if (localOptions.merge && !cell.isSameStore(existing)) { - existing.setProp(cell.getProp(), options) // merge - merged.push(existing) - if (sortable && !sort) { - if (sortAttr == null || typeof sortAttr === 'function') { - sort = existing.hasChanged() - } else if (typeof sortAttr === 'string') { - sort = existing.hasChanged(sortAttr) - } else { - sort = sortAttr.some((key) => existing.hasChanged(key)) - } - } - } - } else { - added.push(cell) - this.reference(cell) - } - }) - - if (added.length) { - if (sortable) { - sort = true - } - this.cells.splice(localIndex, 0, ...added) - this.length = this.cells.length - } - - if (sort) { - this.sort({ silent: true }) - } - - if (!localOptions.silent) { - added.forEach((cell, i) => { - const args = { - cell, - index: localIndex + i, - options: localOptions, - } - this.trigger('added', args) - if (!localOptions.dryrun) { - cell.notify('added', { ...args }) - } - }) - - if (sort) { - this.trigger('sorted') - } - - if (added.length || merged.length) { - this.trigger('updated', { - added, - merged, - removed: [], - options: localOptions, - }) - } - } - - return this - } - - remove(cell: Cell, options?: Collection.RemoveOptions): Cell - remove(cells: Cell[], options?: Collection.RemoveOptions): Cell[] - remove(cells: Cell | Cell[], options: Collection.RemoveOptions = {}) { - const arr = Array.isArray(cells) ? cells : [cells] - const removed = this.removeCells(arr, options) - if (!options.silent && removed.length > 0) { - this.trigger('updated', { - options, - removed, - added: [], - merged: [], - }) - } - return Array.isArray(cells) ? removed : removed[0] - } - - protected removeCells(cells: Cell[], options: Collection.RemoveOptions) { - const removed = [] - - for (let i = 0; i < cells.length; i += 1) { - const cell = this.get(cells[i]) - if (cell == null) { - continue - } - - const index = this.cells.indexOf(cell) - this.cells.splice(index, 1) - this.length -= 1 - delete this.map[cell.id] - removed.push(cell) - this.unreference(cell) - - if (!options.dryrun) { - cell.remove() - } - - if (!options.silent) { - this.trigger('removed', { cell, index, options }) - - if (!options.dryrun) { - cell.notify('removed', { cell, index, options }) - } - } - } - - return removed - } - - reset(cells: Cell | Cell[], options: Collection.SetOptions = {}) { - const previous = this.cells.slice() - previous.forEach((cell) => this.unreference(cell)) - this.clean() - this.add(cells, { silent: true, ...options }) - if (!options.silent) { - const current = this.cells.slice() - this.trigger('reseted', { - options, - previous, - current, - }) - - const added: Cell[] = [] - const removed: Cell[] = [] - - current.forEach((a) => { - const exist = previous.some((b) => b.id === a.id) - if (!exist) { - added.push(a) - } - }) - - previous.forEach((a) => { - const exist = current.some((b) => b.id === a.id) - if (!exist) { - removed.push(a) - } - }) - - this.trigger('updated', { options, added, removed, merged: [] }) - } - - return this - } - - push(cell: Cell, options?: Collection.SetOptions) { - return this.add(cell, this.length, options) - } - - pop(options?: Collection.SetOptions) { - const cell = this.at(this.length - 1)! - return this.remove(cell, options) - } - - unshift(cell: Cell, options?: Collection.SetOptions) { - return this.add(cell, 0, options) - } - - shift(options?: Collection.SetOptions) { - const cell = this.at(0)! - return this.remove(cell, options) - } - - get(cell?: string | number | Cell | null): Cell | null { - if (cell == null) { - return null - } - - const id = - typeof cell === 'string' || typeof cell === 'number' ? cell : cell.id - return this.map[id] || null - } - - has(cell: string | Cell): boolean { - return this.get(cell as any) != null - } - - at(index: number): Cell | null { - if (index < 0) { - index += this.length // eslint-disable-line - } - return this.cells[index] || null - } - - first() { - return this.at(0) - } - - last() { - return this.at(-1) - } - - indexOf(cell: Cell) { - return this.cells.indexOf(cell) - } - - toArray() { - return this.cells.slice() - } - - sort(options: Collection.SetOptions = {}) { - if (this.comparator != null) { - this.cells = ArrayExt.sortBy(this.cells, this.comparator) - if (!options.silent) { - this.trigger('sorted') - } - } - - return this - } - - clone() { - const constructor = this.constructor as any - return new constructor(this.cells.slice(), { - comparator: this.comparator, - }) as Collection - } - - protected reference(cell: Cell) { - this.map[cell.id] = cell - cell.on('*', this.notifyCellEvent, this) - } - - protected unreference(cell: Cell) { - cell.off('*', this.notifyCellEvent, this) - delete this.map[cell.id] - } - - protected notifyCellEvent( - name: K, - args: Cell.EventArgs[K], - ) { - const cell = args.cell - this.trigger(`cell:${name}`, args) - if (cell) { - if (cell.isNode()) { - this.trigger(`node:${name}`, { ...args, node: cell }) - } else if (cell.isEdge()) { - this.trigger(`edge:${name}`, { ...args, edge: cell }) - } - } - } - - protected clean() { - this.length = 0 - this.cells = [] - this.map = {} - } -} - -export namespace Collection { - export type Comparator = string | string[] | ((cell: Cell) => number) - - export interface Options { - comparator?: Comparator - } - - export interface SetOptions extends Cell.SetOptions {} - - export interface RemoveOptions extends Cell.SetOptions { - /** - * The default is to remove all the associated links. - * Set `disconnectEdges` option to `true` to disconnect edges - * when a cell is removed. - */ - disconnectEdges?: boolean - - dryrun?: boolean - } - - export interface AddOptions extends SetOptions { - sort?: boolean - merge?: boolean - dryrun?: boolean - } -} - -export namespace Collection { - export interface EventArgs - extends CellEventArgs, - NodeEventArgs, - EdgeEventArgs { - sorted?: null - reseted: { - current: Cell[] - previous: Cell[] - options: SetOptions - } - updated: { - added: Cell[] - merged: Cell[] - removed: Cell[] - options: SetOptions - } - added: { - cell: Cell - index: number - options: AddOptions - } - removed: { - cell: Cell - index: number - options: RemoveOptions - } - } - - interface NodeEventCommonArgs { - node: Node - } - - interface EdgeEventCommonArgs { - edge: Edge - } - - export interface CellEventArgs { - 'cell:transition:start': Cell.EventArgs['transition:start'] - 'cell:transition:progress': Cell.EventArgs['transition:progress'] - 'cell:transition:complete': Cell.EventArgs['transition:complete'] - 'cell:transition:stop': Cell.EventArgs['transition:stop'] - 'cell:transition:finish': Cell.EventArgs['transition:finish'] - - 'cell:changed': Cell.EventArgs['changed'] - 'cell:added': Cell.EventArgs['added'] - 'cell:removed': Cell.EventArgs['removed'] - - 'cell:change:*': Cell.EventArgs['change:*'] - 'cell:change:attrs': Cell.EventArgs['change:attrs'] - 'cell:change:zIndex': Cell.EventArgs['change:zIndex'] - 'cell:change:markup': Cell.EventArgs['change:markup'] - 'cell:change:visible': Cell.EventArgs['change:visible'] - 'cell:change:parent': Cell.EventArgs['change:parent'] - 'cell:change:children': Cell.EventArgs['change:children'] - 'cell:change:tools': Cell.EventArgs['change:tools'] - 'cell:change:view': Cell.EventArgs['change:view'] - 'cell:change:data': Cell.EventArgs['change:data'] - - 'cell:change:size': Cell.EventArgs['change:size'] - 'cell:change:angle': Cell.EventArgs['change:angle'] - 'cell:change:position': Cell.EventArgs['change:position'] - 'cell:change:ports': Cell.EventArgs['change:ports'] - 'cell:change:portMarkup': Cell.EventArgs['change:portMarkup'] - 'cell:change:portLabelMarkup': Cell.EventArgs['change:portLabelMarkup'] - 'cell:change:portContainerMarkup': Cell.EventArgs['change:portContainerMarkup'] - 'cell:ports:added': Cell.EventArgs['ports:added'] - 'cell:ports:removed': Cell.EventArgs['ports:removed'] - - 'cell:change:source': Cell.EventArgs['change:source'] - 'cell:change:target': Cell.EventArgs['change:target'] - 'cell:change:router': Cell.EventArgs['change:router'] - 'cell:change:connector': Cell.EventArgs['change:connector'] - 'cell:change:vertices': Cell.EventArgs['change:vertices'] - 'cell:change:labels': Cell.EventArgs['change:labels'] - 'cell:change:defaultLabel': Cell.EventArgs['change:defaultLabel'] - 'cell:change:vertexMarkup': Cell.EventArgs['change:vertexMarkup'] - 'cell:change:arrowheadMarkup': Cell.EventArgs['change:arrowheadMarkup'] - 'cell:vertexs:added': Cell.EventArgs['vertexs:added'] - 'cell:vertexs:removed': Cell.EventArgs['vertexs:removed'] - 'cell:labels:added': Cell.EventArgs['labels:added'] - 'cell:labels:removed': Cell.EventArgs['labels:removed'] - - 'cell:batch:start': Cell.EventArgs['batch:start'] - 'cell:batch:stop': Cell.EventArgs['batch:stop'] - } - - export interface NodeEventArgs { - 'node:transition:start': NodeEventCommonArgs & - Cell.EventArgs['transition:start'] - 'node:transition:progress': NodeEventCommonArgs & - Cell.EventArgs['transition:progress'] - 'node:transition:complete': NodeEventCommonArgs & - Cell.EventArgs['transition:complete'] - 'node:transition:stop': NodeEventCommonArgs & - Cell.EventArgs['transition:stop'] - 'node:transition:finish': NodeEventCommonArgs & - Cell.EventArgs['transition:finish'] - - 'node:changed': NodeEventCommonArgs & CellEventArgs['cell:changed'] - 'node:added': NodeEventCommonArgs & CellEventArgs['cell:added'] - 'node:removed': NodeEventCommonArgs & CellEventArgs['cell:removed'] - - 'node:change:*': NodeEventCommonArgs & Cell.EventArgs['change:*'] - 'node:change:attrs': NodeEventCommonArgs & Cell.EventArgs['change:attrs'] - 'node:change:zIndex': NodeEventCommonArgs & Cell.EventArgs['change:zIndex'] - 'node:change:markup': NodeEventCommonArgs & Cell.EventArgs['change:markup'] - 'node:change:visible': NodeEventCommonArgs & - Cell.EventArgs['change:visible'] - 'node:change:parent': NodeEventCommonArgs & Cell.EventArgs['change:parent'] - 'node:change:children': NodeEventCommonArgs & - Cell.EventArgs['change:children'] - 'node:change:tools': NodeEventCommonArgs & Cell.EventArgs['change:tools'] - 'node:change:view': NodeEventCommonArgs & Cell.EventArgs['change:view'] - 'node:change:data': NodeEventCommonArgs & Cell.EventArgs['change:data'] - - 'node:change:size': NodeEventCommonArgs & Cell.EventArgs['change:size'] - 'node:change:position': NodeEventCommonArgs & - Cell.EventArgs['change:position'] - 'node:change:angle': NodeEventCommonArgs & Cell.EventArgs['change:angle'] - 'node:change:ports': NodeEventCommonArgs & Cell.EventArgs['change:ports'] - 'node:change:portMarkup': NodeEventCommonArgs & - Cell.EventArgs['change:portMarkup'] - 'node:change:portLabelMarkup': NodeEventCommonArgs & - Cell.EventArgs['change:portLabelMarkup'] - 'node:change:portContainerMarkup': NodeEventCommonArgs & - Cell.EventArgs['change:portContainerMarkup'] - 'node:ports:added': NodeEventCommonArgs & Cell.EventArgs['ports:added'] - 'node:ports:removed': NodeEventCommonArgs & Cell.EventArgs['ports:removed'] - - 'node:batch:start': NodeEventCommonArgs & Cell.EventArgs['batch:start'] - 'node:batch:stop': NodeEventCommonArgs & Cell.EventArgs['batch:stop'] - - // 'node:translate': NodeEventCommonArgs - // 'node:translating': NodeEventCommonArgs - // 'node:translated': NodeEventCommonArgs - // 'node:resize': NodeEventCommonArgs - // 'node:resizing': NodeEventCommonArgs - // 'node:resized': NodeEventCommonArgs - // 'node:rotate': NodeEventCommonArgs - // 'node:rotating': NodeEventCommonArgs - // 'node:rotated': NodeEventCommonArgs - } - - export interface EdgeEventArgs { - 'edge:transition:start': EdgeEventCommonArgs & - Cell.EventArgs['transition:start'] - 'edge:transition:progress': EdgeEventCommonArgs & - Cell.EventArgs['transition:progress'] - 'edge:transition:complete': EdgeEventCommonArgs & - Cell.EventArgs['transition:complete'] - 'edge:transition:stop': EdgeEventCommonArgs & - Cell.EventArgs['transition:stop'] - 'edge:transition:finish': EdgeEventCommonArgs & - Cell.EventArgs['transition:finish'] - - 'edge:changed': EdgeEventCommonArgs & CellEventArgs['cell:changed'] - 'edge:added': EdgeEventCommonArgs & CellEventArgs['cell:added'] - 'edge:removed': EdgeEventCommonArgs & CellEventArgs['cell:removed'] - - 'edge:change:*': EdgeEventCommonArgs & Cell.EventArgs['change:*'] - 'edge:change:attrs': EdgeEventCommonArgs & Cell.EventArgs['change:attrs'] - 'edge:change:zIndex': EdgeEventCommonArgs & Cell.EventArgs['change:zIndex'] - 'edge:change:markup': EdgeEventCommonArgs & Cell.EventArgs['change:markup'] - 'edge:change:visible': EdgeEventCommonArgs & - Cell.EventArgs['change:visible'] - 'edge:change:parent': EdgeEventCommonArgs & Cell.EventArgs['change:parent'] - 'edge:change:children': EdgeEventCommonArgs & - Cell.EventArgs['change:children'] - 'edge:change:tools': EdgeEventCommonArgs & Cell.EventArgs['change:tools'] - 'edge:change:data': EdgeEventCommonArgs & Cell.EventArgs['change:data'] - - 'edge:change:source': EdgeEventCommonArgs & Cell.EventArgs['change:source'] - 'edge:change:target': EdgeEventCommonArgs & Cell.EventArgs['change:target'] - 'edge:change:router': EdgeEventCommonArgs & Cell.EventArgs['change:router'] - 'edge:change:connector': EdgeEventCommonArgs & - Cell.EventArgs['change:connector'] - 'edge:change:vertices': EdgeEventCommonArgs & - Cell.EventArgs['change:vertices'] - 'edge:change:labels': EdgeEventCommonArgs & Cell.EventArgs['change:labels'] - 'edge:change:defaultLabel': EdgeEventCommonArgs & - Cell.EventArgs['change:defaultLabel'] - 'edge:change:vertexMarkup': EdgeEventCommonArgs & - Cell.EventArgs['change:vertexMarkup'] - 'edge:change:arrowheadMarkup': EdgeEventCommonArgs & - Cell.EventArgs['change:arrowheadMarkup'] - 'edge:vertexs:added': EdgeEventCommonArgs & Cell.EventArgs['vertexs:added'] - 'edge:vertexs:removed': EdgeEventCommonArgs & - Cell.EventArgs['vertexs:removed'] - 'edge:labels:added': EdgeEventCommonArgs & Cell.EventArgs['labels:added'] - 'edge:labels:removed': EdgeEventCommonArgs & - Cell.EventArgs['labels:removed'] - - 'edge:batch:start': EdgeEventCommonArgs & Cell.EventArgs['batch:start'] - 'edge:batch:stop': EdgeEventCommonArgs & Cell.EventArgs['batch:stop'] - } -} diff --git a/packages/x6-core/src/model/edge.ts b/packages/x6-core/src/model/edge.ts deleted file mode 100644 index 59275fd715e..00000000000 --- a/packages/x6-core/src/model/edge.ts +++ /dev/null @@ -1,1206 +0,0 @@ -import { ObjectExt, StringExt, Registry, Size, KeyValue } from '@antv/x6-common' -import { Point, Polyline } from '@antv/x6-geometry' -import { - Attr, - Router, - Connector, - EdgeAnchor, - NodeAnchor, - ConnectionPoint, -} from '../registry' -import { Markup } from '../view/markup' -import { ShareRegistry } from './registry' -import { Store } from './store' -import { Cell } from './cell' -import { Node } from './node' - -export class Edge< - Properties extends Edge.Properties = Edge.Properties, -> extends Cell { - protected static defaults: Edge.Defaults = {} - protected readonly store: Store - - protected get [Symbol.toStringTag]() { - return Edge.toStringTag - } - - constructor(metadata: Edge.Metadata = {}) { - super(metadata) - } - - protected preprocess(metadata: Edge.Metadata, ignoreIdCheck?: boolean) { - const { - source, - sourceCell, - sourcePort, - sourcePoint, - target, - targetCell, - targetPort, - targetPoint, - ...others - } = metadata - - const data = others as Edge.BaseOptions - const isValidId = (val: any): val is string => - typeof val === 'string' || typeof val === 'number' - - if (source != null) { - if (Cell.isCell(source)) { - data.source = { cell: source.id } - } else if (isValidId(source)) { - data.source = { cell: source } - } else if (Point.isPoint(source)) { - data.source = source.toJSON() - } else if (Array.isArray(source)) { - data.source = { x: source[0], y: source[1] } - } else { - const cell = (source as Edge.TerminalCellLooseData).cell - if (Cell.isCell(cell)) { - data.source = { - ...source, - cell: cell.id, - } - } else { - data.source = source as Edge.TerminalCellData - } - } - } - - if (sourceCell != null || sourcePort != null) { - let terminal = data.source as Edge.TerminalCellData - if (sourceCell != null) { - const id = isValidId(sourceCell) ? sourceCell : sourceCell.id - if (terminal) { - terminal.cell = id - } else { - terminal = data.source = { cell: id } - } - } - - if (sourcePort != null && terminal) { - terminal.port = sourcePort - } - } else if (sourcePoint != null) { - data.source = Point.create(sourcePoint).toJSON() - } - - if (target != null) { - if (Cell.isCell(target)) { - data.target = { cell: target.id } - } else if (isValidId(target)) { - data.target = { cell: target } - } else if (Point.isPoint(target)) { - data.target = target.toJSON() - } else if (Array.isArray(target)) { - data.target = { x: target[0], y: target[1] } - } else { - const cell = (target as Edge.TerminalCellLooseData).cell - if (Cell.isCell(cell)) { - data.target = { - ...target, - cell: cell.id, - } - } else { - data.target = target as Edge.TerminalCellData - } - } - } - - if (targetCell != null || targetPort != null) { - let terminal = data.target as Edge.TerminalCellData - - if (targetCell != null) { - const id = isValidId(targetCell) ? targetCell : targetCell.id - if (terminal) { - terminal.cell = id - } else { - terminal = data.target = { cell: id } - } - } - - if (targetPort != null && terminal) { - terminal.port = targetPort - } - } else if (targetPoint != null) { - data.target = Point.create(targetPoint).toJSON() - } - - return super.preprocess(data, ignoreIdCheck) - } - - protected setup() { - super.setup() - this.on('change:labels', (args) => this.onLabelsChanged(args)) - this.on('change:vertices', (args) => this.onVertexsChanged(args)) - } - - isEdge(): this is Edge { - return true - } - - // #region terminal - - disconnect(options: Edge.SetOptions = {}) { - this.store.set( - { - source: { x: 0, y: 0 }, - target: { x: 0, y: 0 }, - }, - options, - ) - return this - } - - get source() { - return this.getSource() - } - - set source(data: Edge.TerminalData) { - this.setSource(data) - } - - getSource() { - return this.getTerminal('source') - } - - getSourceCellId() { - return (this.source as Edge.TerminalCellData).cell - } - - getSourcePortId() { - return (this.source as Edge.TerminalCellData).port - } - - setSource( - node: Node, - args?: Edge.SetCellTerminalArgs, - options?: Edge.SetOptions, - ): this - setSource( - edge: Edge, - args?: Edge.SetEdgeTerminalArgs, - options?: Edge.SetOptions, - ): this - setSource( - point: Point | Point.PointLike, - args?: Edge.SetTerminalCommonArgs, - options?: Edge.SetOptions, - ): this - setSource(args: Edge.TerminalData, options?: Edge.SetOptions): this - setSource( - source: Node | Edge | Point | Point.PointLike | Edge.TerminalData, - args?: Edge.SetTerminalCommonArgs | Edge.SetOptions, - options: Edge.SetOptions = {}, - ) { - return this.setTerminal('source', source, args, options) - } - - get target() { - return this.getTarget() - } - - set target(data: Edge.TerminalData) { - this.setTarget(data) - } - - getTarget() { - return this.getTerminal('target') - } - - getTargetCellId() { - return (this.target as Edge.TerminalCellData).cell - } - - getTargetPortId() { - return (this.target as Edge.TerminalCellData).port - } - - setTarget( - edge: Node, - args?: Edge.SetCellTerminalArgs, - options?: Edge.SetOptions, - ): this - setTarget( - edge: Edge, - args?: Edge.SetEdgeTerminalArgs, - options?: Edge.SetOptions, - ): this - setTarget( - point: Point | Point.PointLike, - args?: Edge.SetTerminalCommonArgs, - options?: Edge.SetOptions, - ): this - setTarget(args: Edge.TerminalData, options?: Edge.SetOptions): this - setTarget( - target: Node | Edge | Point | Point.PointLike | Edge.TerminalData, - args?: Edge.SetTerminalCommonArgs | Edge.SetOptions, - options: Edge.SetOptions = {}, - ) { - return this.setTerminal('target', target, args, options) - } - - getTerminal(type: Edge.TerminalType) { - return { ...this.store.get(type) } as Edge.TerminalData - } - - setTerminal( - type: Edge.TerminalType, - terminal: Node | Edge | Point | Point.PointLike | Edge.TerminalData, - args?: Edge.SetTerminalCommonArgs | Edge.SetOptions, - options: Edge.SetOptions = {}, - ): this { - // `terminal` is a cell - if (Cell.isCell(terminal)) { - this.store.set( - type, - ObjectExt.merge({}, args, { cell: terminal.id }), - options, - ) - return this - } - - // `terminal` is a point-like object - const p = terminal as Point.PointLike - if (Point.isPoint(terminal) || (p.x != null && p.y != null)) { - this.store.set( - type, - ObjectExt.merge({}, args, { x: p.x, y: p.y }), - options, - ) - return this - } - - // `terminal` is an object - this.store.set( - type, - ObjectExt.cloneDeep(terminal as Edge.TerminalData), - options, - ) - - return this - } - - getSourcePoint() { - return this.getTerminalPoint('source') - } - - getTargetPoint() { - return this.getTerminalPoint('target') - } - - protected getTerminalPoint(type: Edge.TerminalType): Point { - const terminal = this[type] - if (Point.isPointLike(terminal)) { - return Point.create(terminal) - } - - const cell = this.getTerminalCell(type) - if (cell) { - return cell.getConnectionPoint(this, type) - } - - return new Point() - } - - getSourceCell() { - return this.getTerminalCell('source') - } - - getTargetCell() { - return this.getTerminalCell('target') - } - - protected getTerminalCell(type: Edge.TerminalType) { - if (this.model) { - const cellId = - type === 'source' ? this.getSourceCellId() : this.getTargetCellId() - if (cellId) { - return this.model.getCell(cellId) - } - } - - return null - } - - getSourceNode() { - return this.getTerminalNode('source') - } - - getTargetNode() { - return this.getTerminalNode('target') - } - - protected getTerminalNode(type: Edge.TerminalType): Node | null { - let cell: Cell | null = this // eslint-disable-line - const visited: { [id: string]: boolean } = {} - - while (cell && cell.isEdge()) { - if (visited[cell.id]) { - return null - } - visited[cell.id] = true - cell = cell.getTerminalCell(type) - } - - return cell && cell.isNode() ? cell : null - } - - // #endregion - - // #region router - - get router() { - return this.getRouter() - } - - set router(data: Edge.RouterData | undefined) { - if (data == null) { - this.removeRouter() - } else { - this.setRouter(data) - } - } - - getRouter() { - return this.store.get('router') - } - - setRouter(name: string, args?: KeyValue, options?: Edge.SetOptions): this - setRouter(router: Edge.RouterData, options?: Edge.SetOptions): this - setRouter( - name?: string | Edge.RouterData, - args?: KeyValue, - options?: Edge.SetOptions, - ) { - if (typeof name === 'object') { - this.store.set('router', name, args) - } else { - this.store.set('router', { name, args }, options) - } - return this - } - - removeRouter(options: Edge.SetOptions = {}) { - this.store.remove('router', options) - return this - } - - // #endregion - - // #region connector - - get connector() { - return this.getConnector() - } - - set connector(data: Edge.ConnectorData | undefined) { - if (data == null) { - this.removeConnector() - } else { - this.setConnector(data) - } - } - - getConnector() { - return this.store.get('connector') - } - - setConnector(name: string, args?: KeyValue, options?: Edge.SetOptions): this - setConnector(connector: Edge.ConnectorData, options?: Edge.SetOptions): this - setConnector( - name?: string | Edge.ConnectorData, - args?: KeyValue | Edge.SetOptions, - options?: Edge.SetOptions, - ) { - if (typeof name === 'object') { - this.store.set('connector', name, args) - } else { - this.store.set('connector', { name, args }, options) - } - return this - } - - removeConnector(options: Edge.SetOptions = {}) { - return this.store.remove('connector', options) - } - - // #endregion - - // #region labels - - getDefaultLabel(): Edge.Label { - const ctor = this.constructor as Edge.Definition - const defaults = this.store.get('defaultLabel') || ctor.defaultLabel || {} - return ObjectExt.cloneDeep(defaults) - } - - get labels() { - return this.getLabels() - } - - set labels(labels: Edge.Label[]) { - this.setLabels(labels) - } - - getLabels(): Edge.Label[] { - return [...this.store.get('labels', [])].map((item) => - this.parseLabel(item), - ) - } - - setLabels( - labels: Edge.Label | Edge.Label[] | string | string[], - options: Edge.SetOptions = {}, - ) { - this.store.set('labels', Array.isArray(labels) ? labels : [labels], options) - return this - } - - insertLabel( - label: Edge.Label | string, - index?: number, - options: Edge.SetOptions = {}, - ) { - const labels = this.getLabels() - const len = labels.length - let idx = index != null && Number.isFinite(index) ? index : len - if (idx < 0) { - idx = len + idx + 1 - } - - labels.splice(idx, 0, this.parseLabel(label)) - return this.setLabels(labels, options) - } - - appendLabel(label: Edge.Label | string, options: Edge.SetOptions = {}) { - return this.insertLabel(label, -1, options) - } - - getLabelAt(index: number) { - const labels = this.getLabels() - if (index != null && Number.isFinite(index)) { - return this.parseLabel(labels[index]) - } - return null - } - - setLabelAt( - index: number, - label: Edge.Label | string, - options: Edge.SetOptions = {}, - ) { - if (index != null && Number.isFinite(index)) { - const labels = this.getLabels() - labels[index] = this.parseLabel(label) - this.setLabels(labels, options) - } - return this - } - - removeLabelAt(index: number, options: Edge.SetOptions = {}) { - const labels = this.getLabels() - const idx = index != null && Number.isFinite(index) ? index : -1 - - const removed = labels.splice(idx, 1) - this.setLabels(labels, options) - return removed.length ? removed[0] : null - } - - protected parseLabel(label: string | Edge.Label) { - if (typeof label === 'string') { - const ctor = this.constructor as Edge.Definition - return ctor.parseStringLabel(label) - } - return label - } - - protected onLabelsChanged({ - previous, - current, - }: Cell.ChangeArgs) { - const added = - previous && current - ? current.filter((label1) => { - if ( - !previous.find( - (label2) => - label1 === label2 || ObjectExt.isEqual(label1, label2), - ) - ) { - return label1 - } - return null - }) - : current - ? [...current] - : [] - - const removed = - previous && current - ? previous.filter((label1) => { - if ( - !current.find( - (label2) => - label1 === label2 || ObjectExt.isEqual(label1, label2), - ) - ) { - return label1 - } - return null - }) - : previous - ? [...previous] - : [] - - if (added.length > 0) { - this.notify('labels:added', { added, cell: this, edge: this }) - } - - if (removed.length > 0) { - this.notify('labels:removed', { removed, cell: this, edge: this }) - } - } - - // #endregion - - // #region vertices - get vertices() { - return this.getVertices() - } - - set vertices(vertices: Point.PointLike | Point.PointLike[]) { - this.setVertices(vertices) - } - - getVertices() { - return [...this.store.get('vertices', [])] - } - - setVertices( - vertices: Point.PointLike | Point.PointLike[], - options: Edge.SetOptions = {}, - ) { - const points = Array.isArray(vertices) ? vertices : [vertices] - this.store.set( - 'vertices', - points.map((p) => Point.toJSON(p)), - options, - ) - return this - } - - insertVertex( - vertice: Point.PointLike, - index?: number, - options: Edge.SetOptions = {}, - ) { - const vertices = this.getVertices() - const len = vertices.length - let idx = index != null && Number.isFinite(index) ? index : len - if (idx < 0) { - idx = len + idx + 1 - } - - vertices.splice(idx, 0, Point.toJSON(vertice)) - return this.setVertices(vertices, options) - } - - appendVertex(vertex: Point.PointLike, options: Edge.SetOptions = {}) { - return this.insertVertex(vertex, -1, options) - } - - getVertexAt(index: number) { - if (index != null && Number.isFinite(index)) { - const vertices = this.getVertices() - return vertices[index] - } - return null - } - - setVertexAt( - index: number, - vertice: Point.PointLike, - options: Edge.SetOptions = {}, - ) { - if (index != null && Number.isFinite(index)) { - const vertices = this.getVertices() - vertices[index] = vertice - this.setVertices(vertices, options) - } - return this - } - - removeVertexAt(index: number, options: Edge.SetOptions = {}) { - const vertices = this.getVertices() - const idx = index != null && Number.isFinite(index) ? index : -1 - vertices.splice(idx, 1) - return this.setVertices(vertices, options) - } - - protected onVertexsChanged({ - previous, - current, - }: Cell.ChangeArgs) { - const added = - previous && current - ? current.filter((p1) => { - if (!previous.find((p2) => Point.equals(p1, p2))) { - return p1 - } - return null - }) - : current - ? [...current] - : [] - - const removed = - previous && current - ? previous.filter((p1) => { - if (!current.find((p2) => Point.equals(p1, p2))) { - return p1 - } - return null - }) - : previous - ? [...previous] - : [] - - if (added.length > 0) { - this.notify('vertexs:added', { added, cell: this, edge: this }) - } - - if (removed.length > 0) { - this.notify('vertexs:removed', { removed, cell: this, edge: this }) - } - } - - // #endregion - - // #region markup - - getDefaultMarkup() { - return this.store.get('defaultMarkup') || Markup.getEdgeMarkup() - } - - getMarkup() { - return super.getMarkup() || this.getDefaultMarkup() - } - - // #endregion - - // #region transform - - /** - * Translate the edge vertices (and source and target if they are points) - * by `tx` pixels in the x-axis and `ty` pixels in the y-axis. - */ - translate(tx: number, ty: number, options: Cell.TranslateOptions = {}) { - options.translateBy = options.translateBy || this.id - options.tx = tx - options.ty = ty - - return this.applyToPoints( - (p) => ({ - x: (p.x || 0) + tx, - y: (p.y || 0) + ty, - }), - options, - ) - } - - /** - * Scales the edge's points (vertices) relative to the given origin. - */ - scale( - sx: number, - sy: number, - origin?: Point | Point.PointLike, - options: Edge.SetOptions = {}, - ) { - return this.applyToPoints((p) => { - return Point.create(p).scale(sx, sy, origin).toJSON() - }, options) - } - - protected applyToPoints( - worker: (p: Point.PointLike) => Point.PointLike, - options: Edge.SetOptions = {}, - ) { - const attrs: { - source?: Edge.TerminalPointData - target?: Edge.TerminalPointData - vertices?: Point.PointLike[] - } = {} - - const source = this.getSource() - const target = this.getTarget() - if (Point.isPointLike(source)) { - attrs.source = worker(source) - } - - if (Point.isPointLike(target)) { - attrs.target = worker(target) - } - - const vertices = this.getVertices() - if (vertices.length > 0) { - attrs.vertices = vertices.map(worker) - } - - this.store.set(attrs, options) - return this - } - - // #endregion - - // #region common - - getBBox() { - return this.getPolyline().bbox() - } - - getConnectionPoint() { - return this.getPolyline().pointAt(0.5)! - } - - getPolyline() { - const points = [ - this.getSourcePoint(), - ...this.getVertices().map((vertice) => Point.create(vertice)), - this.getTargetPoint(), - ] - return new Polyline(points) - } - - updateParent(options?: Edge.SetOptions) { - let newParent: Cell | null = null - - const source = this.getSourceCell() - const target = this.getTargetCell() - const prevParent = this.getParent() - - if (source && target) { - if (source === target || source.isDescendantOf(target)) { - newParent = target - } else if (target.isDescendantOf(source)) { - newParent = source - } else { - newParent = Cell.getCommonAncestor(source, target) - } - } - - // Unembeds the edge if source and target has no common - // ancestor or common ancestor changed - if (prevParent && (!newParent || newParent.id !== prevParent.id)) { - prevParent.unembed(this, options) - } - - if (newParent) { - newParent.embed(this, options) - } - - return newParent - } - - hasLoop(options: { deep?: boolean } = {}) { - const source = this.getSource() as Edge.TerminalCellData - const target = this.getTarget() as Edge.TerminalCellData - const sourceId = source.cell - const targetId = target.cell - - if (!sourceId || !targetId) { - return false - } - - let loop = sourceId === targetId - - // Note that there in the deep mode a edge can have a loop, - // even if it connects only a parent and its embed. - // A loop "target equals source" is valid in both shallow and deep mode. - // eslint-disable-next-line - if (!loop && options.deep && this._model) { - const sourceCell = this.getSourceCell() - const targetCell = this.getTargetCell() - - if (sourceCell && targetCell) { - loop = - sourceCell.isAncestorOf(targetCell, options) || - targetCell.isAncestorOf(sourceCell, options) - } - } - - return loop - } - - getFragmentAncestor(): Cell | null { - const cells = [this, this.getSourceNode(), this.getTargetNode()].filter( - (item) => item != null, - ) - return this.getCommonAncestor(...cells) - } - - isFragmentDescendantOf(cell: Cell) { - const ancestor = this.getFragmentAncestor() - return ( - !!ancestor && (ancestor.id === cell.id || ancestor.isDescendantOf(cell)) - ) - } - - // #endregion -} - -export namespace Edge { - export type RouterData = Router.NativeItem | Router.ManaualItem - export type ConnectorData = Connector.NativeItem | Connector.ManaualItem -} - -export namespace Edge { - interface Common extends Cell.Common { - source?: TerminalData - target?: TerminalData - router?: RouterData - connector?: ConnectorData - labels?: Label[] | string[] - defaultLabel?: Label - vertices?: (Point.PointLike | Point.PointData)[] - defaultMarkup?: Markup - } - - interface TerminalOptions { - sourceCell?: Cell | string - sourcePort?: string - sourcePoint?: Point.PointLike | Point.PointData - targetCell?: Cell | string - targetPort?: string - targetPoint?: Point.PointLike | Point.PointData - source?: - | string - | Cell - | Point.PointLike - | Point.PointData - | TerminalPointData - | TerminalCellLooseData - target?: - | string - | Cell - | Point.PointLike - | Point.PointData - | TerminalPointData - | TerminalCellLooseData - } - - export interface BaseOptions extends Common, Cell.Metadata {} - - export interface Metadata - extends Omit, - TerminalOptions {} - - export interface Defaults extends Common, Cell.Defaults {} - - export interface Properties - extends Cell.Properties, - Omit {} - - export interface Config - extends Omit, - TerminalOptions, - Cell.Config {} -} - -export namespace Edge { - export interface SetOptions extends Cell.SetOptions {} - - export type TerminalType = 'source' | 'target' - - export interface SetTerminalCommonArgs { - selector?: string - magnet?: string - connectionPoint?: - | string - | ConnectionPoint.NativeItem - | ConnectionPoint.ManaualItem - } - - export interface SetCellTerminalArgs extends SetTerminalCommonArgs { - port?: string - anchor?: string | NodeAnchor.NativeItem | NodeAnchor.ManaualItem - } - - export interface SetEdgeTerminalArgs extends SetTerminalCommonArgs { - anchor?: string | EdgeAnchor.NativeItem | EdgeAnchor.ManaualItem - } - - export interface TerminalPointData - extends SetTerminalCommonArgs, - Point.PointLike {} - - export interface TerminalCellData extends SetCellTerminalArgs { - cell: string - port?: string - } - - export interface TerminalCellLooseData extends SetCellTerminalArgs { - cell: string | Cell - port?: string - } - - export type TerminalData = TerminalPointData | TerminalCellData - - export function equalTerminals(a: TerminalData, b: TerminalData) { - const a1 = a as TerminalCellData - const b1 = b as TerminalCellData - if (a1.cell === b1.cell) { - return a1.port === b1.port || (a1.port == null && b1.port == null) - } - return false - } -} - -export namespace Edge { - export interface Label extends KeyValue { - markup?: Markup - attrs?: Attr.CellAttrs - /** - * If the distance is in the `[0,1]` range (inclusive), then the position - * of the label is defined as a percentage of the total length of the edge - * (the normalized length). For example, passing the number `0.5` positions - * the label to the middle of the edge. - * - * If the distance is larger than `1` (exclusive), the label will be - * positioned distance pixels away from the beginning of the path along - * the edge. - * - * If the distance is a negative number, the label will be positioned - * distance pixels away from the end of the path along the edge. - */ - position?: LabelPosition - size?: Size - } - - export interface LabelPositionOptions { - /** - * Forces absolute coordinates for distance. - */ - absoluteDistance?: boolean - /** - * Forces reverse absolute coordinates (if absoluteDistance = true) - */ - reverseDistance?: boolean - /** - * Forces absolute coordinates for offset. - */ - absoluteOffset?: boolean - /** - * Auto-adjusts the angle of the label to match path gradient at position. - */ - keepGradient?: boolean - /** - * Whether rotates labels so they are never upside-down. - */ - ensureLegibility?: boolean - } - - export interface LabelPositionObject { - distance: number - offset?: - | number - | { - x?: number - y?: number - } - angle?: number - options?: LabelPositionOptions - } - - export type LabelPosition = number | LabelPositionObject - - export const defaultLabel: Label = { - markup: [ - { - tagName: 'rect', - selector: 'body', - }, - { - tagName: 'text', - selector: 'label', - }, - ], - attrs: { - text: { - fill: '#000', - fontSize: 14, - textAnchor: 'middle', - textVerticalAnchor: 'middle', - pointerEvents: 'none', - }, - rect: { - ref: 'label', - fill: '#fff', - rx: 3, - ry: 3, - refWidth: 1, - refHeight: 1, - refX: 0, - refY: 0, - }, - }, - position: { - distance: 0.5, - }, - } - - export function parseStringLabel(text: string): Label { - return { - attrs: { label: { text } }, - } - } -} - -export namespace Edge { - export const toStringTag = `X6.${Edge.name}` - - export function isEdge(instance: any): instance is Edge { - if (instance == null) { - return false - } - - if (instance instanceof Edge) { - return true - } - - const tag = instance[Symbol.toStringTag] - const edge = instance as Edge - - if ( - (tag == null || tag === toStringTag) && - typeof edge.isNode === 'function' && - typeof edge.isEdge === 'function' && - typeof edge.prop === 'function' && - typeof edge.attr === 'function' && - typeof edge.disconnect === 'function' && - typeof edge.getSource === 'function' && - typeof edge.getTarget === 'function' - ) { - return true - } - - return false - } -} - -export namespace Edge { - export const registry = Registry.create< - Definition, - never, - Config & { inherit?: string | Definition } - >({ - type: 'edge', - process(shape, options) { - if (ShareRegistry.exist(shape, false)) { - throw new Error( - `Edge with name '${shape}' was registered by anthor Node`, - ) - } - - if (typeof options === 'function') { - options.config({ shape }) - return options - } - - let parent = Edge - - // default inherit from 'dege' - const { inherit = 'edge', ...others } = options - if (typeof inherit === 'string') { - const base = this.get(inherit || 'edge') - if (base == null && inherit) { - this.onNotFound(inherit, 'inherited') - } else { - parent = base - } - } else { - parent = inherit - } - - if (others.constructorName == null) { - others.constructorName = shape - } - - const ctor: Definition = parent.define.call(parent, others) - ctor.config({ shape }) - return ctor as any - }, - }) - - ShareRegistry.setEdgeRegistry(registry) -} - -export namespace Edge { - type EdgeClass = typeof Edge - - export interface Definition extends EdgeClass { - new (metadata: T): Edge - } - - let counter = 0 - function getClassName(name?: string) { - if (name) { - return StringExt.pascalCase(name) - } - counter += 1 - return `CustomEdge${counter}` - } - - export function define(config: Config) { - const { constructorName, overwrite, ...others } = config - const ctor = ObjectExt.createClass( - getClassName(constructorName || others.shape), - this as Definition, - ) - - ctor.config(others) - - if (others.shape) { - registry.register(others.shape, ctor, overwrite) - } - - return ctor - } - - export function create(options: Metadata) { - const shape = options.shape || 'edge' - const Ctor = registry.get(shape) - if (Ctor) { - return new Ctor(options) - } - return registry.onNotFound(shape) - } -} - -export namespace Edge { - const shape = 'basic.edge' - Edge.config({ - shape, - propHooks(metadata: Properties) { - const { label, vertices, ...others } = metadata - if (label) { - if (others.labels == null) { - others.labels = [] - } - const formated = - typeof label === 'string' ? parseStringLabel(label) : label - others.labels.push(formated) - } - - if (vertices) { - if (Array.isArray(vertices)) { - others.vertices = vertices.map((item) => Point.create(item).toJSON()) - } - } - - return others - }, - }) - registry.register(shape, Edge) -} diff --git a/packages/x6-core/src/model/index.ts b/packages/x6-core/src/model/index.ts deleted file mode 100644 index 990424f14a5..00000000000 --- a/packages/x6-core/src/model/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './cell' -export * from './node' -export * from './edge' -export * from './model' -export * from './collection' diff --git a/packages/x6-core/src/model/model.ts b/packages/x6-core/src/model/model.ts deleted file mode 100644 index fc32c5981ba..00000000000 --- a/packages/x6-core/src/model/model.ts +++ /dev/null @@ -1,1483 +0,0 @@ -import { FunctionExt, Dijkstra, KeyValue } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { Basecoat } from '../common' -import { Cell } from './cell' -import { Edge } from './edge' -import { Node } from './node' -import { Collection } from './collection' -import { Renderer } from '../renderer' - -export class Model extends Basecoat { - public readonly collection: Collection - protected readonly batches: KeyValue = {} - protected readonly addings: WeakMap = new WeakMap() - public renderer: Renderer - protected nodes: KeyValue = {} - protected edges: KeyValue = {} - protected outgoings: KeyValue = {} - protected incomings: KeyValue = {} - - protected get [Symbol.toStringTag]() { - return Model.toStringTag - } - - constructor(cells: Cell[] = []) { - super() - this.collection = new Collection(cells) - this.setup() - } - - notify( - name: Key, - args: Model.EventArgs[Key], - ): this - notify(name: Exclude, args: any): this - notify( - name: Key, - args: Model.EventArgs[Key], - ) { - this.trigger(name, args) - const renderer = this.renderer - if (renderer) { - if (name === 'sorted' || name === 'reseted' || name === 'updated') { - renderer.trigger(`model:${name}`, args) - } else { - renderer.trigger(name, args) - } - } - return this - } - - protected setup() { - const collection = this.collection - - collection.on('sorted', () => this.notify('sorted', null)) - collection.on('updated', (args) => this.notify('updated', args)) - collection.on('cell:change:zIndex', () => this.sortOnChangeZ()) - - collection.on('added', ({ cell }) => { - this.onCellAdded(cell) - }) - - collection.on('removed', (args) => { - const cell = args.cell - this.onCellRemoved(cell, args.options) - - // Should trigger remove-event manually after cell was removed. - this.notify('cell:removed', args) - if (cell.isNode()) { - this.notify('node:removed', { ...args, node: cell }) - } else if (cell.isEdge()) { - this.notify('edge:removed', { ...args, edge: cell }) - } - }) - - collection.on('reseted', (args) => { - this.onReset(args.current) - this.notify('reseted', args) - }) - - collection.on('edge:change:source', ({ edge }) => - this.onEdgeTerminalChanged(edge, 'source'), - ) - - collection.on('edge:change:target', ({ edge }) => { - this.onEdgeTerminalChanged(edge, 'target') - }) - } - - protected sortOnChangeZ() { - this.collection.sort() - } - - protected onCellAdded(cell: Cell) { - const cellId = cell.id - if (cell.isEdge()) { - // Auto update edge's parent - cell.updateParent() - this.edges[cellId] = true - this.onEdgeTerminalChanged(cell, 'source') - this.onEdgeTerminalChanged(cell, 'target') - } else { - this.nodes[cellId] = true - } - } - - protected onCellRemoved(cell: Cell, options: Collection.RemoveOptions) { - const cellId = cell.id - if (cell.isEdge()) { - delete this.edges[cellId] - - const source = cell.getSource() as Edge.TerminalCellData - const target = cell.getTarget() as Edge.TerminalCellData - if (source && source.cell) { - const cache = this.outgoings[source.cell] - const index = cache ? cache.indexOf(cellId) : -1 - if (index >= 0) { - cache.splice(index, 1) - if (cache.length === 0) { - delete this.outgoings[source.cell] - } - } - } - - if (target && target.cell) { - const cache = this.incomings[target.cell] - const index = cache ? cache.indexOf(cellId) : -1 - if (index >= 0) { - cache.splice(index, 1) - if (cache.length === 0) { - delete this.incomings[target.cell] - } - } - } - } else { - delete this.nodes[cellId] - } - - if (!options.clear) { - if (options.disconnectEdges) { - this.disconnectConnectedEdges(cell, options) - } else { - this.removeConnectedEdges(cell, options) - } - } - - if (cell.model === this) { - cell.model = null - } - } - - protected onReset(cells: Cell[]) { - this.nodes = {} - this.edges = {} - this.outgoings = {} - this.incomings = {} - cells.forEach((cell) => this.onCellAdded(cell)) - } - - protected onEdgeTerminalChanged(edge: Edge, type: Edge.TerminalType) { - const ref = type === 'source' ? this.outgoings : this.incomings - const prev = edge.previous(type) - - if (prev && prev.cell) { - const cache = ref[prev.cell] - const index = cache ? cache.indexOf(edge.id) : -1 - if (index >= 0) { - cache.splice(index, 1) - if (cache.length === 0) { - delete ref[prev.cell] - } - } - } - - const terminal = edge.getTerminal(type) as Edge.TerminalCellData - if (terminal && terminal.cell) { - const cache = ref[terminal.cell] || [] - const index = cache.indexOf(edge.id) - if (index === -1) { - cache.push(edge.id) - } - ref[terminal.cell] = cache - } - } - - protected prepareCell(cell: Cell, options: Collection.AddOptions) { - if (!cell.model && (!options || !options.dryrun)) { - cell.model = this - } - - if (cell.zIndex == null) { - cell.setZIndex(this.getMaxZIndex() + 1, { silent: true }) - } - - return cell - } - - resetCells(cells: Cell[], options: Collection.SetOptions = {}) { - // Do not update model at this time. Because if we just update the graph - // with the same json-data, the edge will reference to the old nodes. - cells.map((cell) => this.prepareCell(cell, { ...options, dryrun: true })) - this.collection.reset(cells, options) - // Update model and trigger edge update it's references - cells.map((cell) => this.prepareCell(cell, { options })) - return this - } - - clear(options: Cell.SetOptions = {}) { - const raw = this.getCells() - if (raw.length === 0) { - return this - } - const localOptions = { ...options, clear: true } - this.batchUpdate( - 'clear', - () => { - // The nodes come after the edges. - const cells = raw.sort((a, b) => { - const v1 = a.isEdge() ? 1 : 2 - const v2 = b.isEdge() ? 1 : 2 - return v1 - v2 - }) - - while (cells.length > 0) { - // Note that all the edges are removed first, so it's safe to - // remove the nodes without removing the connected edges first. - const cell = cells.shift() - if (cell) { - cell.remove(localOptions) - } - } - }, - localOptions, - ) - - return this - } - - addNode(metadata: Node | Node.Metadata, options: Model.AddOptions = {}) { - const node = Node.isNode(metadata) ? metadata : this.createNode(metadata) - this.addCell(node, options) - return node - } - - createNode(metadata: Node.Metadata) { - return Node.create(metadata) - } - - addEdge(metadata: Edge.Metadata | Edge, options: Model.AddOptions = {}) { - const edge = Edge.isEdge(metadata) ? metadata : this.createEdge(metadata) - this.addCell(edge, options) - return edge - } - - createEdge(metadata: Edge.Metadata) { - return Edge.create(metadata) - } - - addCell(cell: Cell | Cell[], options: Model.AddOptions = {}) { - if (Array.isArray(cell)) { - return this.addCells(cell, options) - } - - if (!this.collection.has(cell) && !this.addings.has(cell)) { - this.addings.set(cell, true) - this.collection.add(this.prepareCell(cell, options), options) - cell.eachChild((child) => this.addCell(child, options)) - this.addings.delete(cell) - } - - return this - } - - addCells(cells: Cell[], options: Model.AddOptions = {}) { - const count = cells.length - if (count === 0) { - return this - } - - const localOptions = { - ...options, - position: count - 1, - maxPosition: count - 1, - } - - this.startBatch('add', { ...localOptions, cells }) - cells.forEach((cell) => { - this.addCell(cell, localOptions) - localOptions.position -= 1 - }) - this.stopBatch('add', { ...localOptions, cells }) - - return this - } - - removeCell(cellId: string, options?: Collection.RemoveOptions): Cell | null - removeCell(cell: Cell, options?: Collection.RemoveOptions): Cell | null - removeCell( - obj: Cell | string, - options: Collection.RemoveOptions = {}, - ): Cell | null { - const cell = typeof obj === 'string' ? this.getCell(obj) : obj - if (cell && this.has(cell)) { - return this.collection.remove(cell, options) - } - return null - } - - updateCellId(cell: Cell, newId: string) { - this.startBatch('update', { id: newId }) - cell.prop('id', newId) - const newCell = cell.clone({ keepId: true }) - this.addCell(newCell) - - // update connected edge terminal - const edges = this.getConnectedEdges(cell) - edges.forEach((edge) => { - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - if (sourceCell === cell) { - edge.setSource({ - ...edge.getSource(), - cell: newId, - }) - } - if (targetCell === cell) { - edge.setTarget({ - ...edge.getTarget(), - cell: newId, - }) - } - }) - - this.removeCell(cell) - this.stopBatch('update', { id: newId }) - return newCell - } - - removeCells(cells: (Cell | string)[], options: Cell.RemoveOptions = {}) { - if (cells.length) { - return this.batchUpdate('remove', () => { - return cells.map((cell) => this.removeCell(cell as Cell, options)) - }) - } - return [] - } - - removeConnectedEdges(cell: Cell | string, options: Cell.RemoveOptions = {}) { - const edges = this.getConnectedEdges(cell) - edges.forEach((edge) => { - edge.remove(options) - }) - return edges - } - - disconnectConnectedEdges(cell: Cell | string, options: Edge.SetOptions = {}) { - const cellId = typeof cell === 'string' ? cell : cell.id - this.getConnectedEdges(cell).forEach((edge) => { - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - - if (sourceCell && sourceCell.id === cellId) { - edge.setSource({ x: 0, y: 0 }, options) - } - - if (targetCell && targetCell.id === cellId) { - edge.setTarget({ x: 0, y: 0 }, options) - } - }) - } - - has(id: string): boolean - has(cell: Cell): boolean - has(obj: string | Cell): boolean { - return this.collection.has(obj) - } - - total() { - return this.collection.length - } - - indexOf(cell: Cell) { - return this.collection.indexOf(cell) - } - - /** - * Returns a cell from the graph by its id. - */ - getCell(id: string) { - return this.collection.get(id) as T - } - - /** - * Returns all the nodes and edges in the graph. - */ - getCells() { - return this.collection.toArray() - } - - /** - * Returns the first cell (node or edge) in the graph. The first cell is - * defined as the cell with the lowest `zIndex`. - */ - getFirstCell() { - return this.collection.first() - } - - /** - * Returns the last cell (node or edge) in the graph. The last cell is - * defined as the cell with the highest `zIndex`. - */ - getLastCell() { - return this.collection.last() - } - - /** - * Returns the lowest `zIndex` value in the graph. - */ - getMinZIndex() { - const first = this.collection.first() - return first ? first.getZIndex() || 0 : 0 - } - - /** - * Returns the highest `zIndex` value in the graph. - */ - getMaxZIndex() { - const last = this.collection.last() - return last ? last.getZIndex() || 0 : 0 - } - - protected getCellsFromCache(cache: { - [key: string]: boolean - }) { - return cache - ? Object.keys(cache) - .map((id) => this.getCell(id)) - .filter((cell) => cell != null) - : [] - } - - /** - * Returns all the nodes in the graph. - */ - getNodes() { - return this.getCellsFromCache(this.nodes) - } - - /** - * Returns all the edges in the graph. - */ - getEdges() { - return this.getCellsFromCache(this.edges) - } - - /** - * Returns all outgoing edges for the node. - */ - getOutgoingEdges(cell: Cell | string) { - const cellId = typeof cell === 'string' ? cell : cell.id - const cellIds = this.outgoings[cellId] - return cellIds - ? cellIds - .map((id) => this.getCell(id) as Edge) - .filter((cell) => cell && cell.isEdge()) - : null - } - - /** - * Returns all incoming edges for the node. - */ - getIncomingEdges(cell: Cell | string) { - const cellId = typeof cell === 'string' ? cell : cell.id - const cellIds = this.incomings[cellId] - return cellIds - ? cellIds - .map((id) => this.getCell(id) as Edge) - .filter((cell) => cell && cell.isEdge()) - : null - } - - /** - * Returns edges connected with cell. - */ - getConnectedEdges( - cell: Cell | string, - options: Model.GetConnectedEdgesOptions = {}, - ) { - const result: Edge[] = [] - const node = typeof cell === 'string' ? this.getCell(cell) : cell - if (node == null) { - return result - } - - const cache: { [id: string]: boolean } = {} - const indirect = options.indirect - let incoming = options.incoming - let outgoing = options.outgoing - if (incoming == null && outgoing == null) { - incoming = outgoing = true - } - - const collect = (cell: Cell, isOutgoing: boolean) => { - const edges = isOutgoing - ? this.getOutgoingEdges(cell) - : this.getIncomingEdges(cell) - - if (edges != null) { - edges.forEach((edge) => { - if (cache[edge.id]) { - return - } - - result.push(edge) - cache[edge.id] = true - - if (indirect) { - if (incoming) { - collect(edge, false) - } - - if (outgoing) { - collect(edge, true) - } - } - }) - } - - if (indirect && cell.isEdge()) { - const terminal = isOutgoing - ? cell.getTargetCell() - : cell.getSourceCell() - if (terminal && terminal.isEdge()) { - if (!cache[terminal.id]) { - result.push(terminal) - collect(terminal, isOutgoing) - } - } - } - } - - if (outgoing) { - collect(node, true) - } - - if (incoming) { - collect(node, false) - } - - if (options.deep) { - const descendants = node.getDescendants({ deep: true }) - const embedsCache: KeyValue = {} - descendants.forEach((cell) => { - if (cell.isNode()) { - embedsCache[cell.id] = true - } - }) - - const collectSub = (cell: Cell, isOutgoing: boolean) => { - const edges = isOutgoing - ? this.getOutgoingEdges(cell.id) - : this.getIncomingEdges(cell.id) - - if (edges != null) { - edges.forEach((edge) => { - if (!cache[edge.id]) { - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - - if ( - !options.enclosed && - sourceCell && - embedsCache[sourceCell.id] && - targetCell && - embedsCache[targetCell.id] - ) { - return - } - - result.push(edge) - cache[edge.id] = true - } - }) - } - } - - descendants.forEach((cell) => { - if (cell.isEdge()) { - return - } - - if (outgoing) { - collectSub(cell, true) - } - - if (incoming) { - collectSub(cell, false) - } - }) - } - - return result - } - - protected isBoundary(cell: Cell | string, isOrigin: boolean) { - const node = typeof cell === 'string' ? this.getCell(cell) : cell - const arr = isOrigin - ? this.getIncomingEdges(node) - : this.getOutgoingEdges(node) - return arr == null || arr.length === 0 - } - - protected getBoundaryNodes(isOrigin: boolean) { - const result: Node[] = [] - Object.keys(this.nodes).forEach((nodeId) => { - if (this.isBoundary(nodeId, isOrigin)) { - const node = this.getCell(nodeId) - if (node) { - result.push(node) - } - } - }) - return result - } - - /** - * Returns an array of all the roots of the graph. - */ - getRoots() { - return this.getBoundaryNodes(true) - } - - /** - * Returns an array of all the leafs of the graph. - */ - getLeafs() { - return this.getBoundaryNodes(false) - } - - /** - * Returns `true` if the node is a root node, i.e. there is no edges - * coming to the node. - */ - isRoot(cell: Cell | string) { - return this.isBoundary(cell, true) - } - - /** - * Returns `true` if the node is a leaf node, i.e. there is no edges - * going out from the node. - */ - isLeaf(cell: Cell | string) { - return this.isBoundary(cell, false) - } - - /** - * Returns all the neighbors of node in the graph. Neighbors are all - * the nodes connected to node via either incoming or outgoing edge. - */ - getNeighbors(cell: Cell, options: Model.GetNeighborsOptions = {}) { - let incoming = options.incoming - let outgoing = options.outgoing - if (incoming == null && outgoing == null) { - incoming = outgoing = true - } - - const edges = this.getConnectedEdges(cell, options) - const map = edges.reduce>((memo, edge) => { - const hasLoop = edge.hasLoop(options) - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - - if ( - incoming && - sourceCell && - sourceCell.isNode() && - !memo[sourceCell.id] - ) { - if ( - hasLoop || - (sourceCell !== cell && - (!options.deep || !sourceCell.isDescendantOf(cell))) - ) { - memo[sourceCell.id] = sourceCell - } - } - - if ( - outgoing && - targetCell && - targetCell.isNode() && - !memo[targetCell.id] - ) { - if ( - hasLoop || - (targetCell !== cell && - (!options.deep || !targetCell.isDescendantOf(cell))) - ) { - memo[targetCell.id] = targetCell - } - } - - return memo - }, {}) - - if (cell.isEdge()) { - if (incoming) { - const sourceCell = cell.getSourceCell() - if (sourceCell && sourceCell.isNode() && !map[sourceCell.id]) { - map[sourceCell.id] = sourceCell - } - } - if (outgoing) { - const targetCell = cell.getTargetCell() - if (targetCell && targetCell.isNode() && !map[targetCell.id]) { - map[targetCell.id] = targetCell - } - } - } - - return Object.keys(map).map((id) => map[id]) - } - - /** - * Returns `true` if `cell2` is a neighbor of `cell1`. - */ - isNeighbor( - cell1: Cell, - cell2: Cell, - options: Model.GetNeighborsOptions = {}, - ) { - let incoming = options.incoming - let outgoing = options.outgoing - if (incoming == null && outgoing == null) { - incoming = outgoing = true - } - - return this.getConnectedEdges(cell1, options).some((edge) => { - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - - if (incoming && sourceCell && sourceCell.id === cell2.id) { - return true - } - - if (outgoing && targetCell && targetCell.id === cell2.id) { - return true - } - - return false - }) - } - - getSuccessors(cell: Cell, options: Model.GetPredecessorsOptions = {}) { - const successors: Cell[] = [] - this.search( - cell, - (curr, distance) => { - if (curr !== cell && this.matchDistance(distance, options.distance)) { - successors.push(curr) - } - }, - { ...options, outgoing: true }, - ) - return successors - } - - /** - * Returns `true` if `cell2` is a successor of `cell1`. - */ - isSuccessor( - cell1: Cell, - cell2: Cell, - options: Model.GetPredecessorsOptions = {}, - ) { - let result = false - this.search( - cell1, - (curr, distance) => { - if ( - curr === cell2 && - curr !== cell1 && - this.matchDistance(distance, options.distance) - ) { - result = true - return false - } - }, - { ...options, outgoing: true }, - ) - return result - } - - getPredecessors(cell: Cell, options: Model.GetPredecessorsOptions = {}) { - const predecessors: Cell[] = [] - this.search( - cell, - (curr, distance) => { - if (curr !== cell && this.matchDistance(distance, options.distance)) { - predecessors.push(curr) - } - }, - { ...options, incoming: true }, - ) - return predecessors - } - - /** - * Returns `true` if `cell2` is a predecessor of `cell1`. - */ - isPredecessor( - cell1: Cell, - cell2: Cell, - options: Model.GetPredecessorsOptions = {}, - ) { - let result = false - this.search( - cell1, - (curr, distance) => { - if ( - curr === cell2 && - curr !== cell1 && - this.matchDistance(distance, options.distance) - ) { - result = true - return false - } - }, - { ...options, incoming: true }, - ) - return result - } - - protected matchDistance( - distance: number, - preset?: number | number[] | ((d: number) => boolean), - ) { - if (preset == null) { - return true - } - - if (typeof preset === 'function') { - return preset(distance) - } - - if (Array.isArray(preset) && preset.includes(distance)) { - return true - } - - return distance === preset - } - - /** - * Returns the common ancestor of the passed cells. - */ - getCommonAncestor(...cells: (Cell | Cell[] | null | undefined)[]) { - const arr: Cell[] = [] - cells.forEach((item) => { - if (item) { - if (Array.isArray(item)) { - arr.push(...item) - } else { - arr.push(item) - } - } - }) - return Cell.getCommonAncestor(...arr) - } - - /** - * Returns an array of cells that result from finding nodes/edges that - * are connected to any of the cells in the cells array. This function - * loops over cells and if the current cell is a edge, it collects its - * source/target nodes; if it is an node, it collects its incoming and - * outgoing edges if both the edge terminal (source/target) are in the - * cells array. - */ - getSubGraph(cells: Cell[], options: Model.GetSubgraphOptions = {}) { - const subgraph: Cell[] = [] - const cache: KeyValue = {} - const nodes: Node[] = [] - const edges: Edge[] = [] - const collect = (cell: Cell) => { - if (!cache[cell.id]) { - subgraph.push(cell) - cache[cell.id] = cell - if (cell.isEdge()) { - edges.push(cell) - } - - if (cell.isNode()) { - nodes.push(cell) - } - } - } - - cells.forEach((cell) => { - collect(cell) - if (options.deep) { - const descendants = cell.getDescendants({ deep: true }) - descendants.forEach((descendant) => collect(descendant)) - } - }) - - edges.forEach((edge) => { - // For edges, include their source & target - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - if (sourceCell && !cache[sourceCell.id]) { - subgraph.push(sourceCell) - cache[sourceCell.id] = sourceCell - if (sourceCell.isNode()) { - nodes.push(sourceCell) - } - } - if (targetCell && !cache[targetCell.id]) { - subgraph.push(targetCell) - cache[targetCell.id] = targetCell - if (targetCell.isNode()) { - nodes.push(targetCell) - } - } - }) - - nodes.forEach((node) => { - // For nodes, include their connected edges if their source/target - // is in the subgraph. - const edges = this.getConnectedEdges(node, options) - edges.forEach((edge) => { - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - if ( - !cache[edge.id] && - sourceCell && - cache[sourceCell.id] && - targetCell && - cache[targetCell.id] - ) { - subgraph.push(edge) - cache[edge.id] = edge - } - }) - }) - - return subgraph - } - - /** - * Clones the whole subgraph (including all the connected links whose - * source/target is in the subgraph). If `options.deep` is `true`, also - * take into account all the embedded cells of all the subgraph cells. - * - * Returns a map of the form: { [original cell ID]: [clone] }. - */ - cloneSubGraph(cells: Cell[], options: Model.GetSubgraphOptions = {}) { - const subgraph = this.getSubGraph(cells, options) - return this.cloneCells(subgraph) - } - - cloneCells(cells: Cell[]) { - return Cell.cloneCells(cells) - } - - /** - * Returns an array of nodes whose bounding box contains point. - * Note that there can be more then one node as nodes might overlap. - */ - getNodesFromPoint(x: number, y: number): Node[] - getNodesFromPoint(p: Point.PointLike): Node[] - getNodesFromPoint(x: number | Point.PointLike, y?: number) { - const p = typeof x === 'number' ? { x, y: y || 0 } : x - return this.getNodes().filter((node) => { - return node.getBBox().containsPoint(p) - }) - } - - /** - * Returns an array of nodes whose bounding box top/left coordinate - * falls into the rectangle. - */ - getNodesInArea( - x: number, - y: number, - w: number, - h: number, - options?: Model.GetCellsInAreaOptions, - ): Node[] - getNodesInArea( - rect: Rectangle.RectangleLike, - options?: Model.GetCellsInAreaOptions, - ): Node[] - getNodesInArea( - x: number | Rectangle.RectangleLike, - y?: number | Model.GetCellsInAreaOptions, - w?: number, - h?: number, - options?: Model.GetCellsInAreaOptions, - ): Node[] { - const rect = - typeof x === 'number' - ? new Rectangle(x, y as number, w as number, h as number) - : Rectangle.create(x) - const opts = - typeof x === 'number' ? options : (y as Model.GetCellsInAreaOptions) - const strict = opts && opts.strict - return this.getNodes().filter((node) => { - const bbox = node.getBBox() - return strict ? rect.containsRect(bbox) : rect.isIntersectWithRect(bbox) - }) - } - - /** - * Returns an array of edges whose bounding box top/left coordinate - * falls into the rectangle. - */ - getEdgesInArea( - x: number, - y: number, - w: number, - h: number, - options?: Model.GetCellsInAreaOptions, - ): Edge[] - getEdgesInArea( - rect: Rectangle.RectangleLike, - options?: Model.GetCellsInAreaOptions, - ): Edge[] - getEdgesInArea( - x: number | Rectangle.RectangleLike, - y?: number | Model.GetCellsInAreaOptions, - w?: number, - h?: number, - options?: Model.GetCellsInAreaOptions, - ): Edge[] { - const rect = - typeof x === 'number' - ? new Rectangle(x, y as number, w as number, h as number) - : Rectangle.create(x) - const opts = - typeof x === 'number' ? options : (y as Model.GetCellsInAreaOptions) - const strict = opts && opts.strict - return this.getEdges().filter((edge) => { - const bbox = edge.getBBox() - if (bbox.width === 0) { - bbox.inflate(1, 0) - } else if (bbox.height === 0) { - bbox.inflate(0, 1) - } - return strict ? rect.containsRect(bbox) : rect.isIntersectWithRect(bbox) - }) - } - - getNodesUnderNode( - node: Node, - options: { - by?: 'bbox' | Rectangle.KeyPoint - } = {}, - ) { - const bbox = node.getBBox() - const nodes = - options.by == null || options.by === 'bbox' - ? this.getNodesInArea(bbox) - : this.getNodesFromPoint(bbox[options.by]) - - return nodes.filter( - (curr) => node.id !== curr.id && !curr.isDescendantOf(node), - ) - } - - /** - * Returns the bounding box that surrounds all cells in the graph. - */ - getAllCellsBBox() { - return this.getCellsBBox(this.getCells()) - } - - /** - * Returns the bounding box that surrounds all the given cells. - */ - getCellsBBox(cells: Cell[], options: Cell.GetCellsBBoxOptions = {}) { - return Cell.getCellsBBox(cells, options) - } - - // #region search - - search( - cell: Cell, - iterator: Model.SearchIterator, - options: Model.SearchOptions = {}, - ) { - if (options.breadthFirst) { - this.breadthFirstSearch(cell, iterator, options) - } else { - this.depthFirstSearch(cell, iterator, options) - } - } - - breadthFirstSearch( - cell: Cell, - iterator: Model.SearchIterator, - options: Model.GetNeighborsOptions = {}, - ) { - const queue: Cell[] = [] - const visited: KeyValue = {} - const distance: KeyValue = {} - - queue.push(cell) - distance[cell.id] = 0 - - while (queue.length > 0) { - const next = queue.shift() - if (next == null || visited[next.id]) { - continue - } - visited[next.id] = true - if (FunctionExt.call(iterator, this, next, distance[next.id]) === false) { - continue - } - const neighbors = this.getNeighbors(next, options) - neighbors.forEach((neighbor) => { - distance[neighbor.id] = distance[next.id] + 1 - queue.push(neighbor) - }) - } - } - - depthFirstSearch( - cell: Cell, - iterator: Model.SearchIterator, - options: Model.GetNeighborsOptions = {}, - ) { - const queue: Cell[] = [] - const visited: KeyValue = {} - const distance: KeyValue = {} - - queue.push(cell) - distance[cell.id] = 0 - - while (queue.length > 0) { - const next = queue.pop() - if (next == null || visited[next.id]) { - continue - } - visited[next.id] = true - - if (FunctionExt.call(iterator, this, next, distance[next.id]) === false) { - continue - } - - const neighbors = this.getNeighbors(next, options) - const lastIndex = queue.length - neighbors.forEach((neighbor) => { - distance[neighbor.id] = distance[next.id] + 1 - queue.splice(lastIndex, 0, neighbor) - }) - } - } - - // #endregion - - // #region shortest path - - /** * - * Returns an array of IDs of nodes on the shortest - * path between source and target. - */ - getShortestPath( - source: Cell | string, - target: Cell | string, - options: Model.GetShortestPathOptions = {}, - ) { - const adjacencyList: Dijkstra.AdjacencyList = {} - this.getEdges().forEach((edge) => { - const sourceId = edge.getSourceCellId() - const targetId = edge.getTargetCellId() - if (sourceId && targetId) { - if (!adjacencyList[sourceId]) { - adjacencyList[sourceId] = [] - } - if (!adjacencyList[targetId]) { - adjacencyList[targetId] = [] - } - - adjacencyList[sourceId].push(targetId) - if (!options.directed) { - adjacencyList[targetId].push(sourceId) - } - } - }) - - const sourceId = typeof source === 'string' ? source : source.id - const previous = Dijkstra.run(adjacencyList, sourceId, options.weight) - - const path = [] - let targetId = typeof target === 'string' ? target : target.id - if (previous[targetId]) { - path.push(targetId) - } - - while ((targetId = previous[targetId])) { - path.unshift(targetId) - } - return path - } - - // #endregion - - // #region transform - - /** - * Translate all cells in the graph by `tx` and `ty` pixels. - */ - translate(tx: number, ty: number, options: Cell.TranslateOptions) { - this.getCells() - .filter((cell) => !cell.hasParent()) - .forEach((cell) => cell.translate(tx, ty, options)) - - return this - } - - resize(width: number, height: number, options: Cell.SetOptions) { - return this.resizeCells(width, height, this.getCells(), options) - } - - resizeCells( - width: number, - height: number, - cells: Cell[], - options: Cell.SetOptions = {}, - ) { - const bbox = this.getCellsBBox(cells) - if (bbox) { - const sx = Math.max(width / bbox.width, 0) - const sy = Math.max(height / bbox.height, 0) - const origin = bbox.getOrigin() - cells.forEach((cell) => cell.scale(sx, sy, origin, options)) - } - - return this - } - - // #endregion - - // #region serialize/deserialize - - toJSON(options: Model.ToJSONOptions = {}) { - return Model.toJSON(this.getCells(), options) - } - - parseJSON(data: Model.FromJSONData) { - return Model.fromJSON(data) - } - - fromJSON(data: Model.FromJSONData, options: Model.FromJSONOptions = {}) { - const cells = this.parseJSON(data) - this.resetCells(cells, options) - return this - } - - // #endregion - - // #region batch - - startBatch(name: Model.BatchName, data: KeyValue = {}) { - this.batches[name] = (this.batches[name] || 0) + 1 - this.notify('batch:start', { name, data }) - return this - } - - stopBatch(name: Model.BatchName, data: KeyValue = {}) { - this.batches[name] = (this.batches[name] || 0) - 1 - this.notify('batch:stop', { name, data }) - return this - } - - batchUpdate(name: Model.BatchName, execute: () => T, data: KeyValue = {}) { - this.startBatch(name, data) - const result = execute() - this.stopBatch(name, data) - return result - } - - hasActiveBatch( - name: Model.BatchName | Model.BatchName[] = Object.keys( - this.batches, - ) as Model.BatchName[], - ) { - const names = Array.isArray(name) ? name : [name] - return names.some((batch) => this.batches[batch] > 0) - } - - // #endregion -} - -export namespace Model { - export const toStringTag = `X6.${Model.name}` - - export function isModel(instance: any): instance is Model { - if (instance == null) { - return false - } - - if (instance instanceof Model) { - return true - } - - const tag = instance[Symbol.toStringTag] - const model = instance as Model - - if ( - (tag == null || tag === toStringTag) && - typeof model.addNode === 'function' && - typeof model.addEdge === 'function' && - model.collection != null - ) { - return true - } - - return false - } -} -export namespace Model { - export interface SetOptions extends Collection.SetOptions {} - export interface AddOptions extends Collection.AddOptions {} - export interface RemoveOptions extends Collection.RemoveOptions {} - export interface FromJSONOptions extends Collection.SetOptions {} - - export type FromJSONData = - | (Node.Metadata | Edge.Metadata)[] - | (Partial> & { - nodes?: Node.Metadata[] - edges?: Edge.Metadata[] - }) - export type ToJSONData = { - cells: Cell.Properties[] - } - - export interface GetCellsInAreaOptions { - strict?: boolean - } - - export interface SearchOptions extends GetNeighborsOptions { - breadthFirst?: boolean - } - - export type SearchIterator = ( - this: Model, - cell: Cell, - distance: number, - ) => any - - export interface GetNeighborsOptions { - deep?: boolean - incoming?: boolean - outgoing?: boolean - indirect?: boolean - } - - export interface GetConnectedEdgesOptions extends GetNeighborsOptions { - enclosed?: boolean - } - - export interface GetSubgraphOptions { - deep?: boolean - } - - export interface GetShortestPathOptions { - directed?: boolean - weight?: Dijkstra.Weight - } - - export interface GetPredecessorsOptions extends Cell.GetDescendantsOptions { - distance?: number | number[] | ((distance: number) => boolean) - } -} - -export namespace Model { - export interface EventArgs - extends Collection.CellEventArgs, - Collection.NodeEventArgs, - Collection.EdgeEventArgs { - 'batch:start': { - name: BatchName | string - data: KeyValue - } - 'batch:stop': { - name: BatchName | string - data: KeyValue - } - - sorted: null - reseted: { - current: Cell[] - previous: Cell[] - options: Collection.SetOptions - } - updated: { - added: Cell[] - merged: Cell[] - removed: Cell[] - options: Collection.SetOptions - } - } - - export type BatchName = - | 'update' - | 'add' - | 'remove' - | 'clear' - | 'to-back' - | 'to-front' - | 'scale' - | 'resize' - | 'rotate' - | 'translate' - | 'mouse' - | 'layout' - | 'add-edge' - | 'fit-embeds' - | 'dnd' - | 'halo' - | 'cut' - | 'paste' - | 'knob' - | 'add-vertex' - | 'move-anchor' - | 'move-vertex' - | 'move-segment' - | 'move-arrowhead' - | 'move-selection' -} - -export namespace Model { - export interface ToJSONOptions extends Cell.ToJSONOptions {} - - export function toJSON(cells: Cell[], options: ToJSONOptions = {}) { - return { - cells: cells.map((cell) => cell.toJSON(options)), - } - } - - export function fromJSON(data: FromJSONData) { - const cells: Cell.Metadata[] = [] - if (Array.isArray(data)) { - cells.push(...data) - } else { - if (data.cells) { - cells.push(...data.cells) - } - - if (data.nodes) { - data.nodes.forEach((node) => { - if (node.shape == null) { - node.shape = 'rect' - } - cells.push(node) - }) - } - - if (data.edges) { - data.edges.forEach((edge) => { - if (edge.shape == null) { - edge.shape = 'edge' - } - cells.push(edge) - }) - } - } - - return cells.map((cell) => { - const type = cell.shape - if (type) { - if (Node.registry.exist(type)) { - return Node.create(cell) - } - if (Edge.registry.exist(type)) { - return Edge.create(cell) - } - } - throw new Error( - 'The `shape` should be specified when creating a node/edge instance', - ) - }) - } -} diff --git a/packages/x6-core/src/model/node.ts b/packages/x6-core/src/model/node.ts deleted file mode 100644 index 9573478ff83..00000000000 --- a/packages/x6-core/src/model/node.ts +++ /dev/null @@ -1,1197 +0,0 @@ -import { Point, Rectangle, Angle } from '@antv/x6-geometry' -import { - StringExt, - ObjectExt, - NumberExt, - Registry, - Size, - KeyValue, -} from '@antv/x6-common' -import { DeepPartial, Omit } from 'utility-types' -import { Markup } from '../view/markup' -import { Cell } from './cell' -import { Edge } from './edge' -import { Store } from './store' -import { ShareRegistry } from './registry' -import { PortManager } from './port' -import { Animation } from './animation' -import { Interp } from '../animation' - -export class Node< - Properties extends Node.Properties = Node.Properties, -> extends Cell { - protected static defaults: Node.Defaults = { - angle: 0, - position: { x: 0, y: 0 }, - size: { width: 1, height: 1 }, - } - protected readonly store: Store - protected port: PortManager - - protected get [Symbol.toStringTag]() { - return Node.toStringTag - } - - constructor(metadata: Node.Metadata = {}) { - super(metadata) - this.initPorts() - } - - protected preprocess( - metadata: Node.Metadata, - ignoreIdCheck?: boolean, - ): Properties { - const { x, y, width, height, ...others } = metadata - - if (x != null || y != null) { - const position = others.position - others.position = { - ...position, - x: x != null ? x : position ? position.x : 0, - y: y != null ? y : position ? position.y : 0, - } - } - - if (width != null || height != null) { - const size = others.size - others.size = { - ...size, - width: width != null ? width : size ? size.width : 0, - height: height != null ? height : size ? size.height : 0, - } - } - - return super.preprocess(others, ignoreIdCheck) - } - - isNode(): this is Node { - return true - } - - // #region size - - size(): Size - size(size: Size, options?: Node.ResizeOptions): this - size(width: number, height: number, options?: Node.ResizeOptions): this - size( - width?: number | Size, - height?: number | Node.ResizeOptions, - options?: Node.ResizeOptions, - ) { - if (width === undefined) { - return this.getSize() - } - - if (typeof width === 'number') { - return this.setSize(width, height as number, options) - } - - return this.setSize(width, height as Node.ResizeOptions) - } - - getSize() { - const size = this.store.get('size') - return size ? { ...size } : { width: 1, height: 1 } - } - - setSize(size: Size, options?: Node.ResizeOptions): this - setSize(width: number, height: number, options?: Node.ResizeOptions): this - setSize( - width: number | Size, - height?: number | Node.ResizeOptions, - options?: Node.ResizeOptions, - ) { - if (typeof width === 'object') { - this.resize(width.width, width.height, height as Node.ResizeOptions) - } else { - this.resize(width, height as number, options) - } - - return this - } - - resize(width: number, height: number, options: Node.ResizeOptions = {}) { - this.startBatch('resize', options) - const direction = options.direction - - if (direction) { - const currentSize = this.getSize() - switch (direction) { - case 'left': - case 'right': - // Don't change height when resizing horizontally. - height = currentSize.height // eslint-disable-line - break - case 'top': - case 'bottom': - // Don't change width when resizing vertically. - width = currentSize.width // eslint-disable-line - break - default: - break - } - - const map: { [direction: string]: number } = { - right: 0, - 'top-right': 0, - top: 1, - 'top-left': 1, - left: 2, - 'bottom-left': 2, - bottom: 3, - 'bottom-right': 3, - } - - let quadrant = map[direction] - const angle = Angle.normalize(this.getAngle() || 0) - if (options.absolute) { - // We are taking the node's rotation into account - quadrant += Math.floor((angle + 45) / 90) - quadrant %= 4 - } - - // This is a rectangle in size of the un-rotated node. - const bbox = this.getBBox() - - // Pick the corner point on the node, which meant to stay on its - // place before and after the rotation. - let fixedPoint: Point - if (quadrant === 0) { - fixedPoint = bbox.getBottomLeft() - } else if (quadrant === 1) { - fixedPoint = bbox.getCorner() - } else if (quadrant === 2) { - fixedPoint = bbox.getTopRight() - } else { - fixedPoint = bbox.getOrigin() - } - - // Find an image of the previous indent point. This is the position, - // where is the point actually located on the screen. - const imageFixedPoint = fixedPoint - .clone() - .rotate(-angle, bbox.getCenter()) - - // Every point on the element rotates around a circle with the centre of - // rotation in the middle of the element while the whole element is being - // rotated. That means that the distance from a point in the corner of - // the element (supposed its always rect) to the center of the element - // doesn't change during the rotation and therefore it equals to a - // distance on un-rotated element. - // We can find the distance as DISTANCE = (ELEMENTWIDTH/2)^2 + (ELEMENTHEIGHT/2)^2)^0.5. - const radius = Math.sqrt(width * width + height * height) / 2 - - // Now we are looking for an angle between x-axis and the line starting - // at image of fixed point and ending at the center of the element. - // We call this angle `alpha`. - - // The image of a fixed point is located in n-th quadrant. For each - // quadrant passed going anti-clockwise we have to add 90 degrees. - // Note that the first quadrant has index 0. - // - // 3 | 2 - // --c-- Quadrant positions around the element's center `c` - // 0 | 1 - // - let alpha = (quadrant * Math.PI) / 2 - - // Add an angle between the beginning of the current quadrant (line - // parallel with x-axis or y-axis going through the center of the - // element) and line crossing the indent of the fixed point and the - // center of the element. This is the angle we need but on the - // un-rotated element. - alpha += Math.atan(quadrant % 2 === 0 ? height / width : width / height) - - // Lastly we have to deduct the original angle the element was rotated - // by and that's it. - alpha -= Angle.toRad(angle) - - // With this angle and distance we can easily calculate the centre of - // the un-rotated element. - // Note that fromPolar constructor accepts an angle in radians. - const center = Point.fromPolar(radius, alpha, imageFixedPoint) - - // The top left corner on the un-rotated element has to be half a width - // on the left and half a height to the top from the center. This will - // be the origin of rectangle we were looking for. - const origin = center.clone().translate(width / -2, height / -2) - - this.store.set('size', { width, height }, options) - this.setPosition(origin.x, origin.y, options) - } else { - this.store.set('size', { width, height }, options) - } - - this.stopBatch('resize', options) - - return this - } - - scale( - sx: number, - sy: number, - origin?: Point.PointLike | null, - options: Node.SetOptions = {}, - ) { - const scaledBBox = this.getBBox().scale( - sx, - sy, - origin == null ? undefined : origin, - ) - - this.startBatch('scale', options) - this.setPosition(scaledBBox.x, scaledBBox.y, options) - this.resize(scaledBBox.width, scaledBBox.height, options) - this.stopBatch('scale') - return this - } - - // #endregion - - // #region position - - position(x: number, y: number, options?: Node.SetPositionOptions): this - position(options?: Node.GetPositionOptions): Point.PointLike - position( - arg0?: number | Node.GetPositionOptions, - arg1?: number, - arg2?: Node.SetPositionOptions, - ) { - if (typeof arg0 === 'number') { - return this.setPosition(arg0, arg1 as number, arg2) - } - return this.getPosition(arg0) - } - - getPosition(options: Node.GetPositionOptions = {}): Point.PointLike { - if (options.relative) { - const parent = this.getParent() - if (parent != null && parent.isNode()) { - const currentPosition = this.getPosition() - const parentPosition = parent.getPosition() - - return { - x: currentPosition.x - parentPosition.x, - y: currentPosition.y - parentPosition.y, - } - } - } - - const pos = this.store.get('position') - return pos ? { ...pos } : { x: 0, y: 0 } - } - - setPosition( - p: Point | Point.PointLike, - options?: Node.SetPositionOptions, - ): this - setPosition(x: number, y: number, options?: Node.SetPositionOptions): this - setPosition( - arg0: number | Point | Point.PointLike, - arg1?: number | Node.SetPositionOptions, - arg2: Node.SetPositionOptions = {}, - ) { - let x: number - let y: number - let options: Node.SetPositionOptions - - if (typeof arg0 === 'object') { - x = arg0.x - y = arg0.y - options = (arg1 as Node.SetPositionOptions) || {} - } else { - x = arg0 - y = arg1 as number - options = arg2 || {} - } - - if (options.relative) { - const parent = this.getParent() as Node - if (parent != null && parent.isNode()) { - const parentPosition = parent.getPosition() - x += parentPosition.x - y += parentPosition.y - } - } - - if (options.deep) { - const currentPosition = this.getPosition() - this.translate(x - currentPosition.x, y - currentPosition.y, options) - } else { - this.store.set('position', { x, y }, options) - } - - return this - } - - translate(tx = 0, ty = 0, options: Node.TranslateOptions = {}) { - if (tx === 0 && ty === 0) { - return this - } - - // Pass the initiator of the translation. - options.translateBy = options.translateBy || this.id - - const position = this.getPosition() - - if (options.restrict != null && options.translateBy === this.id) { - // We are restricting the translation for the element itself only. We get - // the bounding box of the element including all its embeds. - // All embeds have to be translated the exact same way as the element. - const bbox = this.getBBox({ deep: true }) - const ra = options.restrict - // - - - - - - - - - - - - -> ra.x + ra.width - // - - - -> position.x | - // -> bbox.x - // ▓▓▓▓▓▓▓ | - // ░░░░░░░▓▓▓▓▓▓▓ - // ░░░░░░░░░ | - // ▓▓▓▓▓▓▓▓░░░░░░░ - // ▓▓▓▓▓▓▓▓ | - // <-dx-> | restricted area right border - // <-width-> | ░ translated element - // <- - bbox.width - -> ▓ embedded element - const dx = position.x - bbox.x - const dy = position.y - bbox.y - // Find the maximal/minimal coordinates that the element can be translated - // while complies the restrictions. - const x = Math.max( - ra.x + dx, - Math.min(ra.x + ra.width + dx - bbox.width, position.x + tx), - ) - const y = Math.max( - ra.y + dy, - Math.min(ra.y + ra.height + dy - bbox.height, position.y + ty), - ) - - // recalculate the translation taking the restrictions into account. - tx = x - position.x // eslint-disable-line - ty = y - position.y // eslint-disable-line - } - - const translatedPosition = { - x: position.x + tx, - y: position.y + ty, - } - - // To find out by how much an element was translated in event - // 'change:position' handlers. - options.tx = tx - options.ty = ty - - if (options.transition) { - if (typeof options.transition !== 'object') { - options.transition = {} - } - - this.transition('position', translatedPosition, { - ...options.transition, - interp: Interp.object, - }) - this.eachChild((child) => { - const excluded = options.exclude?.includes(child) - if (!excluded) { - child.translate(tx, ty, options) - } - }) - } else { - this.startBatch('translate', options) - this.store.set('position', translatedPosition, options) - this.eachChild((child) => { - const excluded = options.exclude?.includes(child) - if (!excluded) { - child.translate(tx, ty, options) - } - }) - this.stopBatch('translate', options) - } - - return this - } - - // #endregion - - // #region angle - - angle(): number - angle(val: number, options?: Node.RotateOptions): this - angle(val?: number, options?: Node.RotateOptions) { - if (val == null) { - return this.getAngle() - } - return this.rotate(val, options) - } - - getAngle() { - return this.store.get('angle', 0) - } - - rotate(angle: number, options: Node.RotateOptions = {}) { - const currentAngle = this.getAngle() - if (options.center) { - const size = this.getSize() - const position = this.getPosition() - const center = this.getBBox().getCenter() - center.rotate(currentAngle - angle, options.center) - const dx = center.x - size.width / 2 - position.x - const dy = center.y - size.height / 2 - position.y - this.startBatch('rotate', { angle, options }) - this.setPosition(position.x + dx, position.y + dy, options) - this.rotate(angle, { ...options, center: null }) - this.stopBatch('rotate') - } else { - this.store.set( - 'angle', - options.absolute ? angle : (currentAngle + angle) % 360, - options, - ) - } - - return this - } - - // #endregion - - // #region common - - getBBox(options: { deep?: boolean } = {}) { - if (options.deep) { - const cells = this.getDescendants({ deep: true, breadthFirst: true }) - cells.push(this) - return Cell.getCellsBBox(cells)! - } - - return Rectangle.fromPositionAndSize(this.getPosition(), this.getSize()) - } - - getConnectionPoint(edge: Edge, type: Edge.TerminalType) { - const bbox = this.getBBox() - const center = bbox.getCenter() - const terminal = edge.getTerminal(type) as Edge.TerminalCellData - if (terminal == null) { - return center - } - - const portId = terminal.port - if (!portId || !this.hasPort(portId)) { - return center - } - - const port = this.getPort(portId) - if (!port || !port.group) { - return center - } - - const layouts = this.getPortsPosition(port.group) - const position = layouts[portId].position - const portCenter = Point.create(position).translate(bbox.getOrigin()) - - const angle = this.getAngle() - if (angle) { - portCenter.rotate(-angle, center) - } - - return portCenter - } - - /** - * Sets cell's size and position based on the children bbox and given padding. - */ - fit(options: Node.FitEmbedsOptions = {}) { - const children = this.getChildren() || [] - const embeds = children.filter((cell) => cell.isNode()) as Node[] - if (embeds.length === 0) { - return this - } - - this.startBatch('fit-embeds', options) - - if (options.deep) { - embeds.forEach((cell) => cell.fit(options)) - } - - let { x, y, width, height } = Cell.getCellsBBox(embeds)! - const padding = NumberExt.normalizeSides(options.padding) - - x -= padding.left - y -= padding.top - width += padding.left + padding.right - height += padding.bottom + padding.top - - this.store.set( - { - position: { x, y }, - size: { width, height }, - }, - options, - ) - - this.stopBatch('fit-embeds') - - return this - } - - // #endregion - - // #region ports - - get portContainerMarkup() { - return this.getPortContainerMarkup() - } - - set portContainerMarkup(markup: Markup) { - this.setPortContainerMarkup(markup) - } - - getDefaultPortContainerMarkup() { - return ( - this.store.get('defaultPortContainerMarkup') || - Markup.getPortContainerMarkup() - ) - } - - getPortContainerMarkup() { - return ( - this.store.get('portContainerMarkup') || - this.getDefaultPortContainerMarkup() - ) - } - - setPortContainerMarkup(markup?: Markup, options: Node.SetOptions = {}) { - this.store.set('portContainerMarkup', Markup.clone(markup), options) - return this - } - - get portMarkup() { - return this.getPortMarkup() - } - - set portMarkup(markup: Markup) { - this.setPortMarkup(markup) - } - - getDefaultPortMarkup() { - return this.store.get('defaultPortMarkup') || Markup.getPortMarkup() - } - - getPortMarkup() { - return this.store.get('portMarkup') || this.getDefaultPortMarkup() - } - - setPortMarkup(markup?: Markup, options: Node.SetOptions = {}) { - this.store.set('portMarkup', Markup.clone(markup), options) - return this - } - - get portLabelMarkup() { - return this.getPortLabelMarkup() - } - - set portLabelMarkup(markup: Markup) { - this.setPortLabelMarkup(markup) - } - - getDefaultPortLabelMarkup() { - return ( - this.store.get('defaultPortLabelMarkup') || Markup.getPortLabelMarkup() - ) - } - - getPortLabelMarkup() { - return this.store.get('portLabelMarkup') || this.getDefaultPortLabelMarkup() - } - - setPortLabelMarkup(markup?: Markup, options: Node.SetOptions = {}) { - this.store.set('portLabelMarkup', Markup.clone(markup), options) - return this - } - - get ports() { - const res = this.store.get('ports', { items: [] }) - if (res.items == null) { - res.items = [] - } - return res - } - - getPorts() { - return ObjectExt.cloneDeep(this.ports.items) - } - - getPortsByGroup(groupName: string) { - return this.getPorts().filter((port) => port.group === groupName) - } - - getPort(portId: string) { - return ObjectExt.cloneDeep( - this.ports.items.find((port) => port.id && port.id === portId), - ) - } - - getPortAt(index: number) { - return this.ports.items[index] || null - } - - hasPorts() { - return this.ports.items.length > 0 - } - - hasPort(portId: string) { - return this.getPortIndex(portId) !== -1 - } - - getPortIndex(port: PortManager.PortMetadata | string) { - const portId = typeof port === 'string' ? port : port.id - return portId != null - ? this.ports.items.findIndex((item) => item.id === portId) - : -1 - } - - getPortsPosition(groupName: string) { - const size = this.getSize() - const layouts = this.port.getPortsLayoutByGroup( - groupName, - new Rectangle(0, 0, size.width, size.height), - ) - - return layouts.reduce< - KeyValue<{ - position: Point.PointLike - angle: number - }> - >((memo, item) => { - const layout = item.portLayout - memo[item.portId] = { - position: { ...layout.position }, - angle: layout.angle || 0, - } - return memo - }, {}) - } - - getPortProp(portId: string): PortManager.PortMetadata - getPortProp(portId: string, path: string | string[]): T - getPortProp(portId: string, path?: string | string[]) { - return this.getPropByPath(this.prefixPortPath(portId, path)) - } - - setPortProp( - portId: string, - path: string | string[], - value: any, - options?: Node.SetOptions, - ): this - setPortProp( - portId: string, - value: DeepPartial, - options?: Node.SetOptions, - ): this - setPortProp( - portId: string, - arg1: string | string[] | DeepPartial, - arg2: any | Node.SetOptions, - arg3?: Node.SetOptions, - ) { - if (typeof arg1 === 'string' || Array.isArray(arg1)) { - const path = this.prefixPortPath(portId, arg1) - const value = arg2 - return this.setPropByPath(path, value, arg3) - } - - const path = this.prefixPortPath(portId) - const value = arg1 as DeepPartial - return this.setPropByPath(path, value, arg2 as Node.SetOptions) - } - - removePortProp(portId: string, options?: Node.SetOptions): this - removePortProp( - portId: string, - path: string | string[], - options?: Node.SetOptions, - ): this - removePortProp( - portId: string, - path?: string | string[] | Node.SetOptions, - options?: Node.SetOptions, - ) { - if (typeof path === 'string' || Array.isArray(path)) { - return this.removePropByPath(this.prefixPortPath(portId, path), options) - } - return this.removePropByPath(this.prefixPortPath(portId), path) - } - - portProp(portId: string): PortManager.PortMetadata - portProp(portId: string, path: string | string[]): T - portProp( - portId: string, - path: string | string[], - value: any, - options?: Node.SetOptions, - ): this - portProp( - portId: string, - value: DeepPartial, - options?: Node.SetOptions, - ): this - portProp( - portId: string, - path?: string | string[] | DeepPartial, - value?: any | Node.SetOptions, - options?: Node.SetOptions, - ) { - if (path == null) { - return this.getPortProp(portId) - } - if (typeof path === 'string' || Array.isArray(path)) { - if (arguments.length === 2) { - return this.getPortProp(portId, path) - } - if (value == null) { - return this.removePortProp(portId, path, options) - } - return this.setPortProp( - portId, - path, - value as DeepPartial, - options, - ) - } - return this.setPortProp( - portId, - path as DeepPartial, - value as Node.SetOptions, - ) - } - - protected prefixPortPath(portId: string, path?: string | string[]) { - const index = this.getPortIndex(portId) - if (index === -1) { - throw new Error(`Unable to find port with id: "${portId}"`) - } - - if (path == null || path === '') { - return ['ports', 'items', `${index}`] - } - - if (Array.isArray(path)) { - return ['ports', 'items', `${index}`, ...path] - } - - return `ports/items/${index}/${path}` - } - - addPort(port: PortManager.PortMetadata, options?: Node.SetOptions) { - const ports = [...this.ports.items] - ports.push(port) - this.setPropByPath('ports/items', ports, options) - return this - } - - addPorts(ports: PortManager.PortMetadata[], options?: Node.SetOptions) { - this.setPropByPath('ports/items', [...this.ports.items, ...ports], options) - return this - } - - insertPort( - index: number, - port: PortManager.PortMetadata, - options?: Node.SetOptions, - ) { - const ports = [...this.ports.items] - ports.splice(index, 0, port) - this.setPropByPath('ports/items', ports, options) - return this - } - - removePort( - port: PortManager.PortMetadata | string, - options: Node.SetOptions = {}, - ) { - return this.removePortAt(this.getPortIndex(port), options) - } - - removePortAt(index: number, options: Node.SetOptions = {}) { - if (index >= 0) { - const ports = [...this.ports.items] - ports.splice(index, 1) - options.rewrite = true - this.setPropByPath('ports/items', ports, options) - } - return this - } - - removePorts(options?: Node.SetOptions): this - removePorts( - portsForRemoval: (PortManager.PortMetadata | string)[], - options?: Node.SetOptions, - ): this - removePorts( - portsForRemoval?: (PortManager.PortMetadata | string)[] | Node.SetOptions, - opt?: Node.SetOptions, - ) { - let options - - if (Array.isArray(portsForRemoval)) { - options = opt || {} - if (portsForRemoval.length) { - options.rewrite = true - const currentPorts = [...this.ports.items] - const remainingPorts = currentPorts.filter( - (cp) => - !portsForRemoval.some((p) => { - const id = typeof p === 'string' ? p : p.id - return cp.id === id - }), - ) - this.setPropByPath('ports/items', remainingPorts, options) - } - } else { - options = portsForRemoval || {} - options.rewrite = true - this.setPropByPath('ports/items', [], options) - } - - return this - } - - getParsedPorts() { - return this.port.getPorts() - } - - getParsedGroups() { - return this.port.groups - } - - getPortsLayoutByGroup(groupName: string | undefined, bbox: Rectangle) { - return this.port.getPortsLayoutByGroup(groupName, bbox) - } - - protected initPorts() { - this.updatePortData() - this.on('change:ports', () => { - this.processRemovedPort() - this.updatePortData() - }) - } - - protected processRemovedPort() { - const current = this.ports - const currentItemsMap: { [id: string]: boolean } = {} - - current.items.forEach((item) => { - if (item.id) { - currentItemsMap[item.id] = true - } - }) - - const removed: { [id: string]: boolean } = {} - const previous = this.store.getPrevious('ports') || { - items: [], - } - - previous.items.forEach((item) => { - if (item.id && !currentItemsMap[item.id]) { - removed[item.id] = true - } - }) - - const model = this.model - if (model && !ObjectExt.isEmpty(removed)) { - const incomings = model.getConnectedEdges(this, { incoming: true }) - incomings.forEach((edge) => { - const portId = edge.getTargetPortId() - if (portId && removed[portId]) { - edge.remove() - } - }) - const outgoings = model.getConnectedEdges(this, { outgoing: true }) - outgoings.forEach((edge) => { - const portId = edge.getSourcePortId() - if (portId && removed[portId]) { - edge.remove() - } - }) - } - } - - protected validatePorts() { - const ids: { [id: string]: boolean } = {} - const errors: string[] = [] - this.ports.items.forEach((p) => { - if (typeof p !== 'object') { - errors.push(`Invalid port ${p}.`) - } - - if (p.id == null) { - p.id = this.generatePortId() - } - - if (ids[p.id]) { - errors.push('Duplicitied port id.') - } - - ids[p.id] = true - }) - - return errors - } - - protected generatePortId() { - return StringExt.uuid() - } - - protected updatePortData() { - const err = this.validatePorts() - - if (err.length > 0) { - this.store.set( - 'ports', - this.store.getPrevious('ports'), - ) - throw new Error(err.join(' ')) - } - - const prev = this.port ? this.port.getPorts() : null - this.port = new PortManager(this.ports) - const curr = this.port.getPorts() - - const added = prev - ? curr.filter((item) => { - if (!prev.find((prevPort) => prevPort.id === item.id)) { - return item - } - return null - }) - : [...curr] - - const removed = prev - ? prev.filter((item) => { - if (!curr.find((curPort) => curPort.id === item.id)) { - return item - } - return null - }) - : [] - - if (added.length > 0) { - this.notify('ports:added', { added, cell: this, node: this }) - } - - if (removed.length > 0) { - this.notify('ports:removed', { removed, cell: this, node: this }) - } - } - - // #endregion -} - -export namespace Node { - interface Common extends Cell.Common { - size?: { width: number; height: number } - position?: { x: number; y: number } - angle?: number - ports?: Partial | PortManager.PortMetadata[] - portContainerMarkup?: Markup - portMarkup?: Markup - portLabelMarkup?: Markup - defaultPortMarkup?: Markup - defaultPortLabelMarkup?: Markup - defaultPortContainerMarkup?: Markup - } - - interface Boundary { - x?: number - y?: number - width?: number - height?: number - } - - export interface Defaults extends Common, Cell.Defaults {} - - export interface Metadata extends Common, Cell.Metadata, Boundary {} - - export interface Properties - extends Common, - Omit, - Cell.Properties {} - - export interface Config - extends Defaults, - Boundary, - Cell.Config {} -} - -export namespace Node { - export interface SetOptions extends Cell.SetOptions {} - - export interface GetPositionOptions { - relative?: boolean - } - - export interface SetPositionOptions extends SetOptions { - deep?: boolean - relative?: boolean - } - - export interface TranslateOptions extends Cell.TranslateOptions { - transition?: boolean | Animation.StartOptions - restrict?: Rectangle.RectangleLike | null - exclude?: Cell[] - } - - export interface RotateOptions extends SetOptions { - absolute?: boolean - center?: Point.PointLike | null - } - - export type ResizeDirection = - | 'left' - | 'top' - | 'right' - | 'bottom' - | 'top-left' - | 'top-right' - | 'bottom-left' - | 'bottom-right' - - export interface ResizeOptions extends SetOptions { - absolute?: boolean - direction?: ResizeDirection - } - - export interface FitEmbedsOptions extends SetOptions { - deep?: boolean - padding?: NumberExt.SideOptions - } -} - -export namespace Node { - export const toStringTag = `X6.${Node.name}` - - export function isNode(instance: any): instance is Node { - if (instance == null) { - return false - } - - if (instance instanceof Node) { - return true - } - - const tag = instance[Symbol.toStringTag] - const node = instance as Node - - if ( - (tag == null || tag === toStringTag) && - typeof node.isNode === 'function' && - typeof node.isEdge === 'function' && - typeof node.prop === 'function' && - typeof node.attr === 'function' && - typeof node.size === 'function' && - typeof node.position === 'function' - ) { - return true - } - - return false - } -} - -export namespace Node { - Node.config({ - propHooks({ ports, ...metadata }) { - if (ports) { - metadata.ports = Array.isArray(ports) ? { items: ports } : ports - } - return metadata - }, - }) -} - -export namespace Node { - export const registry = Registry.create< - Definition, - never, - Config & { inherit?: string | Definition } - >({ - type: 'node', - process(shape, options) { - if (ShareRegistry.exist(shape, true)) { - throw new Error( - `Node with name '${shape}' was registered by anthor Edge`, - ) - } - - if (typeof options === 'function') { - options.config({ shape }) - return options - } - - let parent = Node - const { inherit, ...config } = options - if (inherit) { - if (typeof inherit === 'string') { - const base = this.get(inherit) - if (base == null) { - this.onNotFound(inherit, 'inherited') - } else { - parent = base - } - } else { - parent = inherit - } - } - - if (config.constructorName == null) { - config.constructorName = shape - } - - const ctor: Definition = parent.define.call(parent, config) - ctor.config({ shape }) - return ctor as any - }, - }) - - ShareRegistry.setNodeRegistry(registry) -} - -export namespace Node { - type NodeClass = typeof Node - - export interface Definition extends NodeClass { - new (metadata: T): Node - } - - let counter = 0 - function getClassName(name?: string) { - if (name) { - return StringExt.pascalCase(name) - } - counter += 1 - return `CustomNode${counter}` - } - - export function define(config: Config) { - const { constructorName, overwrite, ...others } = config - const ctor = ObjectExt.createClass( - getClassName(constructorName || others.shape), - this as NodeClass, - ) - - ctor.config(others) - - if (others.shape) { - registry.register(others.shape, ctor, overwrite) - } - - return ctor - } - - export function create(options: Metadata) { - const shape = options.shape || 'rect' - const Ctor = registry.get(shape) - if (Ctor) { - return new Ctor(options) - } - return registry.onNotFound(shape) - } -} diff --git a/packages/x6-core/src/model/port.ts b/packages/x6-core/src/model/port.ts deleted file mode 100644 index 01d8b4954e8..00000000000 --- a/packages/x6-core/src/model/port.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { JSONObject, ObjectExt, Size, KeyValue } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { Markup } from '../view' -import { Attr, PortLayout, PortLabelLayout } from '../registry' - -export class PortManager { - ports: PortManager.Port[] - groups: { [name: string]: PortManager.Group } - - constructor(data: PortManager.Metadata) { - this.ports = [] - this.groups = {} - this.init(ObjectExt.cloneDeep(data)) - } - - getPorts() { - return this.ports - } - - getGroup(groupName?: string | null) { - return groupName != null ? this.groups[groupName] : null - } - - getPortsByGroup(groupName?: string): PortManager.Port[] { - return this.ports.filter( - (p) => p.group === groupName || (p.group == null && groupName == null), - ) - } - - getPortsLayoutByGroup(groupName: string | undefined, elemBBox: Rectangle) { - const ports = this.getPortsByGroup(groupName) - const group = groupName ? this.getGroup(groupName) : null - const groupPosition = group ? group.position : null - const groupPositionName = groupPosition ? groupPosition.name : null - - let layoutFn: PortLayout.Definition - - if (groupPositionName != null) { - const fn = PortLayout.registry.get(groupPositionName) - if (fn == null) { - return PortLayout.registry.onNotFound(groupPositionName) - } - layoutFn = fn - } else { - layoutFn = PortLayout.presets.left - } - - const portsArgs = ports.map( - (port) => (port && port.position && port.position.args) || {}, - ) - const groupArgs = (groupPosition && groupPosition.args) || {} - const layouts = layoutFn(portsArgs, elemBBox, groupArgs) - return layouts.map((portLayout, index) => { - const port = ports[index] - return { - portLayout, - portId: port.id!, - portSize: port.size, - portAttrs: port.attrs, - labelSize: port.label.size, - labelLayout: this.getPortLabelLayout( - port, - Point.create(portLayout.position), - elemBBox, - ), - } - }) - } - - protected init(data: PortManager.Metadata) { - const { groups, items } = data - - if (groups != null) { - Object.keys(groups).forEach((key) => { - this.groups[key] = this.parseGroup(groups[key]) - }) - } - - if (Array.isArray(items)) { - items.forEach((item) => { - this.ports.push(this.parsePort(item)) - }) - } - } - - protected parseGroup(group: PortManager.GroupMetadata) { - return { - ...group, - label: this.getLabel(group, true), - position: this.getPortPosition(group.position, true), - } as PortManager.Group - } - - protected parsePort(port: PortManager.PortMetadata) { - const result = { ...port } as PortManager.Port - const group = this.getGroup(port.group) || ({} as PortManager.Group) - - result.markup = result.markup || group.markup - result.attrs = ObjectExt.merge({}, group.attrs, result.attrs) - result.position = this.createPosition(group, result) - result.label = ObjectExt.merge({}, group.label, this.getLabel(result)) - result.zIndex = this.getZIndex(group, result) - result.size = { ...group.size, ...result.size } as Size - - return result - } - - protected getZIndex( - group: PortManager.Group, - port: PortManager.PortMetadata, - ) { - if (typeof port.zIndex === 'number') { - return port.zIndex - } - - if (typeof group.zIndex === 'number' || group.zIndex === 'auto') { - return group.zIndex - } - - return 'auto' - } - - protected createPosition( - group: PortManager.Group, - port: PortManager.PortMetadata, - ) { - return ObjectExt.merge( - { - name: 'left', - args: {}, - }, - group.position, - { args: port.args }, - ) as PortManager.PortPosition - } - - protected getPortPosition( - position?: PortManager.PortPositionMetadata, - setDefault = false, - ): PortManager.PortPosition { - if (position == null) { - if (setDefault) { - return { name: 'left', args: {} } - } - } else { - if (typeof position === 'string') { - return { - name: position, - args: {}, - } - } - - if (Array.isArray(position)) { - return { - name: 'absolute', - args: { x: position[0], y: position[1] }, - } - } - - if (typeof position === 'object') { - return position - } - } - - return { args: {} } - } - - protected getPortLabelPosition( - position?: PortManager.PortLabelPositionMetadata, - setDefault = false, - ): PortManager.PortLabelPosition { - if (position == null) { - if (setDefault) { - return { name: 'left', args: {} } - } - } else { - if (typeof position === 'string') { - return { - name: position, - args: {}, - } - } - - if (typeof position === 'object') { - return position - } - } - - return { args: {} } - } - - protected getLabel(item: PortManager.GroupMetadata, setDefaults = false) { - const label = item.label || {} - label.position = this.getPortLabelPosition(label.position, setDefaults) - return label as PortManager.Label - } - - protected getPortLabelLayout( - port: PortManager.Port, - portPosition: Point, - elemBBox: Rectangle, - ) { - const name = port.label.position.name || 'left' - const args = port.label.position.args || {} - const layoutFn = - PortLabelLayout.registry.get(name) || PortLabelLayout.presets.left - if (layoutFn) { - return layoutFn(portPosition, elemBBox, args) - } - - return null - } -} - -export namespace PortManager { - export interface Metadata { - groups?: { [name: string]: GroupMetadata } - items: PortMetadata[] - } - - export type PortPosition = - | Partial - | Partial - - export type PortPositionMetadata = - | PortLayout.NativeNames - | Exclude - | Point.PointData // absolute layout - | PortPosition - - export type PortLabelPosition = - | Partial - | Partial - - export type PortLabelPositionMetadata = - | PortLabelLayout.NativeNames - | Exclude - | PortLabelPosition - - export interface LabelMetadata { - markup?: Markup - size?: Size - position?: PortLabelPositionMetadata - } - - export interface Label { - markup: string - size?: Size - position: PortLabelPosition - } - - interface Common { - markup: Markup - attrs: Attr.CellAttrs - zIndex: number | 'auto' - size?: Size - } - - export interface GroupMetadata extends Partial, KeyValue { - label?: LabelMetadata - position?: PortPositionMetadata - } - - export interface Group extends Partial { - label: Label - position: PortPosition - } - - interface PortBase { - group?: string - /** - * Arguments for the port layout function. - */ - args?: JSONObject - } - - export interface PortMetadata extends Partial, PortBase, KeyValue { - id?: string - label?: LabelMetadata - } - - export interface Port extends Group, PortBase { - id: string - } - - export interface LayoutResult { - portId: string - portAttrs?: Attr.CellAttrs - portSize?: Size - portLayout: PortLayout.Result - labelSize?: Size - labelLayout: PortLabelLayout.Result | null - } -} diff --git a/packages/x6-core/src/model/registry.ts b/packages/x6-core/src/model/registry.ts deleted file mode 100644 index f9b19f7c4f0..00000000000 --- a/packages/x6-core/src/model/registry.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Registry } from '@antv/x6-common' - -export namespace ShareRegistry { - let edgeRegistry: Registry - let nodeRegistry: Registry - - export function exist(name: string, isNode: boolean) { - return isNode - ? edgeRegistry != null && edgeRegistry.exist(name) - : nodeRegistry != null && nodeRegistry.exist(name) - } - - export function setEdgeRegistry(registry: any) { - edgeRegistry = registry - } - - export function setNodeRegistry(registry: any) { - nodeRegistry = registry - } -} diff --git a/packages/x6-core/src/model/store.ts b/packages/x6-core/src/model/store.ts deleted file mode 100644 index 2f6ddb452f5..00000000000 --- a/packages/x6-core/src/model/store.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { ObjectExt, KeyValue } from '@antv/x6-common' -import { Assign, NonUndefined } from 'utility-types' -import { Basecoat } from '../common' - -export class Store extends Basecoat> { - protected data: D - protected previous: D - protected changed: Partial - protected pending = false - protected changing = false - protected pendingOptions: Store.MutateOptions | null - - constructor(data: Partial = {}) { - super() - this.data = {} as D - this.mutate(ObjectExt.cloneDeep(data)) - this.changed = {} - } - - protected mutate( - data: Partial, - options: Store.MutateOptions = {}, - ) { - const unset = options.unset === true - const silent = options.silent === true - const changes: K[] = [] - const changing = this.changing - - this.changing = true - - if (!changing) { - this.previous = ObjectExt.cloneDeep(this.data) - this.changed = {} - } - - const current = this.data - const previous = this.previous - const changed = this.changed - - Object.keys(data).forEach((k) => { - const key = k as K - const newValue = data[key] - if (!ObjectExt.isEqual(current[key], newValue)) { - changes.push(key) - } - - if (!ObjectExt.isEqual(previous[key], newValue)) { - changed[key] = newValue - } else { - delete changed[key] - } - - if (unset) { - delete current[key] - } else { - current[key] = newValue as any - } - }) - - if (!silent && changes.length > 0) { - this.pending = true - this.pendingOptions = options - changes.forEach((key) => { - this.emit('change:*', { - key, - options, - store: this, - current: current[key], - previous: previous[key], - }) - }) - } - - if (changing) { - return this - } - - if (!silent) { - // Changes can be recursively nested within `"change"` events. - while (this.pending) { - this.pending = false - this.emit('changed', { - current, - previous, - store: this, - options: this.pendingOptions!, - }) - } - } - - this.pending = false - this.changing = false - this.pendingOptions = null - - return this - } - - get(): D - get(key: K): D[K] - get(key: K, defaultValue: D[K]): NonUndefined - get(key: string): T - get(key: string, defaultValue: T): T - get(key?: K, defaultValue?: D[K]): D | D[K] | undefined { - if (key == null) { - return this.data - } - - const ret = this.data[key] - return ret == null ? defaultValue : ret - } - - getPrevious(key: keyof D) { - if (this.previous) { - const ret = this.previous[key] - return ret == null ? undefined : (ret as any as T) - } - - return undefined - } - - set( - key: K, - value: D[K] | null | undefined | void, - options?: Store.SetOptions, - ): this - set(key: string, value: any, options?: Store.SetOptions): this - set(data: D, options?: Store.SetOptions): this - set( - key: K | Partial, - value?: D[K] | null | undefined | void | Store.SetOptions, - options?: Store.SetOptions, - ): this { - if (key != null) { - if (typeof key === 'object') { - this.mutate(key, value as Store.SetOptions) - } else { - this.mutate({ [key]: value } as Partial, options) - } - } - - return this - } - - remove(key: K | K[], options?: Store.SetOptions): this - remove(options?: Store.SetOptions): this - remove( - key: K | K[] | Store.SetOptions, - options?: Store.SetOptions, - ) { - const empty = undefined - const subset: Partial = {} - let opts: Store.SetOptions | undefined - - if (typeof key === 'string') { - subset[key] = empty - opts = options - } else if (Array.isArray(key)) { - key.forEach((k) => (subset[k] = empty)) - opts = options - } else { - // eslint-disable-next-line - for (const key in this.data) { - subset[key] = empty - } - opts = key as Store.SetOptions - } - - this.mutate(subset, { ...opts, unset: true }) - return this - } - - getByPath(path: string | string[]) { - return ObjectExt.getByPath(this.data, path, '/') as T - } - - setByPath( - path: string | string[], - value: any, - options: Store.SetByPathOptions = {}, - ) { - const delim = '/' - const pathArray = Array.isArray(path) ? [...path] : path.split(delim) - const pathString = Array.isArray(path) ? path.join(delim) : path - - const property = pathArray[0] as K - const pathArrayLength = pathArray.length - - options.propertyPath = pathString - options.propertyValue = value - options.propertyPathArray = pathArray - - if (pathArrayLength === 1) { - this.set(property, value, options) - } else { - const update: KeyValue = {} - let diver = update - let nextKey = property as string - - // Initialize the nested object. Subobjects are either arrays or objects. - // An empty array is created if the sub-key is an integer. Otherwise, an - // empty object is created. - for (let i = 1; i < pathArrayLength; i += 1) { - const key = pathArray[i] - const isArrayIndex = Number.isFinite(Number(key)) - diver = diver[nextKey] = isArrayIndex ? [] : {} - nextKey = key - } - - // Fills update with the `value` on `path`. - ObjectExt.setByPath(update, pathArray, value, delim) - - const data = ObjectExt.cloneDeep(this.data) - - // If rewrite mode enabled, we replace value referenced by path with the - // new one (we don't merge). - if (options.rewrite) { - ObjectExt.unsetByPath(data, path, delim) - } - - const merged = ObjectExt.merge(data, update) - this.set(property, merged[property], options) - } - - return this - } - - removeByPath( - path: string | string[], - options?: Store.SetOptions, - ) { - const keys = Array.isArray(path) ? path : path.split('/') - const key = keys[0] as K - if (keys.length === 1) { - this.remove(key, options) - } else { - const paths = keys.slice(1) - const prop = ObjectExt.cloneDeep(this.get(key)) - if (prop) { - ObjectExt.unsetByPath(prop, paths) - } - - this.set(key, prop as D[K], options) - } - - return this - } - - hasChanged(): boolean - hasChanged(key: K | null): boolean - hasChanged(key: string | null): boolean - hasChanged(key?: K | null) { - if (key == null) { - return Object.keys(this.changed).length > 0 - } - - return key in this.changed - } - - /** - * Returns an object containing all the data that have changed, - * or `null` if there are no changes. Useful for determining what - * parts of a view need to be updated. - */ - getChanges(diff?: Partial) { - if (diff == null) { - return this.hasChanged() ? ObjectExt.cloneDeep(this.changed) : null - } - - const old = this.changing ? this.previous : this.data - const changed: Partial = {} - let hasChanged - // eslint-disable-next-line - for (const key in diff) { - const val = diff[key] - if (!ObjectExt.isEqual(old[key], val)) { - changed[key] = val - hasChanged = true - } - } - return hasChanged ? ObjectExt.cloneDeep(changed) : null - } - - /** - * Returns a copy of the store's `data` object. - */ - toJSON() { - return ObjectExt.cloneDeep(this.data) - } - - clone() { - const constructor = this.constructor as any - return new constructor(this.data) as T - } - - @Basecoat.dispose() - dispose() { - this.off() - this.data = {} as D - this.previous = {} as D - this.changed = {} - this.pending = false - this.changing = false - this.pendingOptions = null - this.trigger('disposed', { store: this }) - } -} - -export namespace Store { - export interface SetOptions extends KeyValue { - silent?: boolean - } - - export interface MutateOptions extends SetOptions { - unset?: boolean - } - - export interface SetByPathOptions extends SetOptions { - rewrite?: boolean - } - - type CommonArgs = { store: Store } - - export interface EventArgs { - 'change:*': Assign< - { - key: K - current: D[K] - previous: D[K] - options: MutateOptions - }, - CommonArgs - > - changed: Assign< - { - current: D - previous: D - options: MutateOptions - }, - CommonArgs - > - disposed: CommonArgs - } -} diff --git a/packages/x6-core/src/registry/attr/align.ts b/packages/x6-core/src/registry/attr/align.ts deleted file mode 100644 index db20fdb306a..00000000000 --- a/packages/x6-core/src/registry/attr/align.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { Point } from '@antv/x6-geometry' -import { Attr } from './index' - -// `x-align` when set to `middle` causes centering of the subelement around its new x coordinate. -// `x-align` when set to `right` uses the x coordinate as referenced to the right of the bbox. -export const xAlign: Attr.Definition = { - offset: offsetWrapper('x', 'width', 'right'), -} - -// `y-align` when set to `middle` causes centering of the subelement around its new y coordinate. -// `y-align` when set to `bottom` uses the y coordinate as referenced to the bottom of the bbox. -export const yAlign: Attr.Definition = { - offset: offsetWrapper('y', 'height', 'bottom'), -} - -export const resetOffset: Attr.Definition = { - offset(val, { refBBox }) { - return val ? { x: -refBBox.x, y: -refBBox.y } : { x: 0, y: 0 } - }, -} - -function offsetWrapper( - axis: 'x' | 'y', - dimension: 'width' | 'height', - corner: 'right' | 'bottom', -): Attr.OffsetFunction { - return (value, { refBBox }) => { - const point = new Point() - let delta - if (value === 'middle') { - delta = refBBox[dimension] / 2 - } else if (value === corner) { - delta = refBBox[dimension] - } else if (typeof value === 'number' && Number.isFinite(value)) { - delta = value > -1 && value < 1 ? -refBBox[dimension] * value : -value - } else if (NumberExt.isPercentage(value)) { - delta = (refBBox[dimension] * parseFloat(value)) / 100 - } else { - delta = 0 - } - point[axis] = -(refBBox[axis] + delta) - return point - } -} diff --git a/packages/x6-core/src/registry/attr/connection.ts b/packages/x6-core/src/registry/attr/connection.ts deleted file mode 100644 index 7318b608ca8..00000000000 --- a/packages/x6-core/src/registry/attr/connection.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { EdgeView } from '../../view' -import { Attr } from './index' - -const isEdgeView: Attr.QualifyFucntion = (val, { view }) => { - return view.cell.isEdge() -} - -export const connection: Attr.Definition = { - qualify: isEdgeView, - set(val, args) { - const view = args.view as EdgeView - const stubs = ((val as any).stubs || 0) as number - let d - if (Number.isFinite(stubs) && stubs !== 0) { - let offset - if (stubs < 0) { - const len = view.getConnectionLength() || 0 - offset = (len + stubs) / 2 - } else { - offset = stubs - } - - const path = view.getConnection() - if (path) { - const sourceParts = path.divideAtLength(offset) - const targetParts = path.divideAtLength(-offset) - if (sourceParts && targetParts) { - d = `${sourceParts[0].serialize()} ${targetParts[1].serialize()}` - } - } - } - - return { d: d || view.getConnectionPathData() } - }, -} - -export const atConnectionLengthKeepGradient: Attr.Definition = { - qualify: isEdgeView, - set: atConnectionWrapper('getTangentAtLength', { rotate: true }), -} - -export const atConnectionLengthIgnoreGradient: Attr.Definition = { - qualify: isEdgeView, - set: atConnectionWrapper('getTangentAtLength', { rotate: false }), -} - -export const atConnectionRatioKeepGradient: Attr.Definition = { - qualify: isEdgeView, - set: atConnectionWrapper('getTangentAtRatio', { rotate: true }), -} - -export const atConnectionRatioIgnoreGradient: Attr.Definition = { - qualify: isEdgeView, - set: atConnectionWrapper('getTangentAtRatio', { rotate: false }), -} - -// aliases -// ------- -export const atConnectionLength = atConnectionLengthKeepGradient -export const atConnectionRatio = atConnectionRatioKeepGradient - -// utils -// ----- - -function atConnectionWrapper( - method: 'getTangentAtLength' | 'getTangentAtRatio', - options: { rotate: boolean }, -): Attr.SetFunction { - const zeroVector = { x: 1, y: 0 } - - return (value, args) => { - let p - let angle - - const view = args.view as EdgeView - const tangent = view[method](Number(value)) - if (tangent) { - angle = options.rotate ? tangent.vector().vectorAngle(zeroVector) : 0 - p = tangent.start - } else { - p = (view as any).path.start - angle = 0 - } - - if (angle === 0) { - return { transform: `translate(${p.x},${p.y}')` } - } - - return { - transform: `translate(${p.x},${p.y}') rotate(${angle})`, - } - } -} diff --git a/packages/x6-core/src/registry/attr/fill.ts b/packages/x6-core/src/registry/attr/fill.ts deleted file mode 100644 index ebd7370808b..00000000000 --- a/packages/x6-core/src/registry/attr/fill.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ObjectExt } from '@antv/x6-common' -import { Attr } from './index' - -export const fill: Attr.Definition = { - qualify: ObjectExt.isPlainObject, - set(fill, { view }) { - return `url(#${view.renderer.defineGradient(fill as any)})` - }, -} diff --git a/packages/x6-core/src/registry/attr/filter.ts b/packages/x6-core/src/registry/attr/filter.ts deleted file mode 100644 index d96a855bcaf..00000000000 --- a/packages/x6-core/src/registry/attr/filter.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ObjectExt } from '@antv/x6-common' -import { Attr } from './index' - -export const filter: Attr.Definition = { - qualify: ObjectExt.isPlainObject, - set(filter, { view }) { - return `url(#${view.renderer.defineFilter(filter as any)})` - }, -} diff --git a/packages/x6-core/src/registry/attr/html.ts b/packages/x6-core/src/registry/attr/html.ts deleted file mode 100644 index bbf9e973a51..00000000000 --- a/packages/x6-core/src/registry/attr/html.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Attr } from './index' - -export const html: Attr.Definition = { - set(html, { elem }) { - elem.innerHTML = `${html}` - }, -} diff --git a/packages/x6-core/src/registry/attr/index.ts b/packages/x6-core/src/registry/attr/index.ts deleted file mode 100644 index 9f149966d17..00000000000 --- a/packages/x6-core/src/registry/attr/index.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Rectangle, Point } from '@antv/x6-geometry' -import { JSONObject, FunctionExt, Registry } from '@antv/x6-common' -import { Cell } from '../../model' -import { CellView } from '../../view' -import { raw } from './raw' -import * as attrs from './main' - -export namespace Attr { - export type SimpleAttrValue = null | undefined | string | number - - export type SimpleAttrs = { [name: string]: SimpleAttrValue } - - export type ComplexAttrValue = - | null - | undefined - | boolean - | string - | number - | JSONObject - - export type ComplexAttrs = { [name: string]: ComplexAttrValue } - - export type CellAttrs = { [selector: string]: ComplexAttrs } -} - -export namespace Attr { - export interface QualifyOptions { - elem: Element - attrs: ComplexAttrs - cell: Cell - view: CellView - } - - export type QualifyFucntion = ( - this: CellView, - val: ComplexAttrValue, - options: QualifyOptions, - ) => boolean - - export interface Options extends QualifyOptions { - refBBox: Rectangle - } - - export type SetFunction = ( - this: CellView, - val: ComplexAttrValue, - options: Options, - ) => SimpleAttrValue | SimpleAttrs | void - - export type OffsetFunction = ( - this: CellView, - val: ComplexAttrValue, - options: Options, - ) => Point.PointLike - - export type PositionFunction = ( - this: CellView, - val: ComplexAttrValue, - options: Options, - ) => Point.PointLike | undefined | null - - export interface Qualify { - qualify?: QualifyFucntion - } - - export interface SetDefinition extends Qualify { - set: SetFunction - } - - export interface OffsetDefinition extends Qualify { - offset: OffsetFunction - } - - export interface PositionDefinition extends Qualify { - /** - * Returns a point from the reference bounding box. - */ - position: PositionFunction - } - - export type Definition = - | string - | Qualify - | SetDefinition - | OffsetDefinition - | PositionDefinition - - export type Definitions = { [attrName: string]: Definition } - - export type GetDefinition = (name: string) => Definition | null | undefined -} - -export namespace Attr { - export function isValidDefinition( - this: CellView, - def: Definition | undefined | null, - val: ComplexAttrValue, - options: QualifyOptions, - ): def is Definition { - if (def != null) { - if (typeof def === 'string') { - return true - } - - if ( - typeof def.qualify !== 'function' || - FunctionExt.call(def.qualify, this, val, options) - ) { - return true - } - } - - return false - } -} - -export namespace Attr { - export type Presets = typeof Attr['presets'] - export type NativeNames = keyof Presets -} - -export namespace Attr { - export const presets: Definitions = { - ...raw, - ...attrs, - } - - export const registry = Registry.create({ - type: 'attribute definition', - }) - - registry.register(Attr.presets, true) -} diff --git a/packages/x6-core/src/registry/attr/main.ts b/packages/x6-core/src/registry/attr/main.ts deleted file mode 100644 index c0b99cf708f..00000000000 --- a/packages/x6-core/src/registry/attr/main.ts +++ /dev/null @@ -1,12 +0,0 @@ -export * from './ref' -export * from './fill' -export * from './stroke' -export * from './text' -export * from './title' -export * from './align' -export * from './style' -export * from './html' -export * from './filter' -export * from './port' -export * from './marker' -export * from './connection' diff --git a/packages/x6-core/src/registry/attr/marker.ts b/packages/x6-core/src/registry/attr/marker.ts deleted file mode 100644 index f3ac78b5458..00000000000 --- a/packages/x6-core/src/registry/attr/marker.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { ObjectExt, JSONObject, KeyValue } from '@antv/x6-common' -import { CellView } from '../../view' -import { Marker } from '../marker' -import { Attr } from './index' - -function qualify(value: any) { - return typeof value === 'string' || ObjectExt.isPlainObject(value) -} - -export const sourceMarker: Attr.Definition = { - qualify, - set(marker: string | JSONObject, { view, attrs }) { - return createMarker('marker-start', marker, view, attrs) - }, -} - -export const targetMarker: Attr.Definition = { - qualify, - set(marker: string | JSONObject, { view, attrs }) { - return createMarker('marker-end', marker, view, attrs, { - transform: 'rotate(180)', - }) - }, -} - -export const vertexMarker: Attr.Definition = { - qualify, - set(marker: string | JSONObject, { view, attrs }) { - return createMarker('marker-mid', marker, view, attrs) - }, -} - -function createMarker( - type: 'marker-start' | 'marker-end' | 'marker-mid', - marker: string | JSONObject, - view: CellView, - attrs: Attr.ComplexAttrs, - manual: Attr.SimpleAttrs = {}, -) { - const def = typeof marker === 'string' ? { name: marker } : marker - const { name, args, ...others } = def - let preset = others - - if (name && typeof name === 'string') { - const fn = Marker.registry.get(name) - if (fn) { - preset = fn({ ...others, ...(args as KeyValue) }) - } else { - return Marker.registry.onNotFound(name) - } - } - - const options: any = { - ...normalizeAttr(attrs, type), - ...manual, - ...preset, - } - - return { - [type]: `url(#${view.renderer.defineMarker(options)})`, - } -} - -function normalizeAttr( - attr: Attr.ComplexAttrs, - type: 'marker-start' | 'marker-end' | 'marker-mid', -) { - const result: Attr.SimpleAttrs = {} - - // The context 'fill' is disregared here. The usual case is to use the - // marker with a connection(for which 'fill' attribute is set to 'none'). - const stroke = attr.stroke - if (typeof stroke === 'string') { - result.stroke = stroke - result.fill = stroke - } - - // Again the context 'fill-opacity' is ignored. - let strokeOpacity = attr.strokeOpacity - if (strokeOpacity == null) { - strokeOpacity = attr['stroke-opacity'] - } - - if (strokeOpacity == null) { - strokeOpacity = attr.opacity - } - - if (strokeOpacity != null) { - result['stroke-opacity'] = strokeOpacity as number - result['fill-opacity'] = strokeOpacity as number - } - - if (type !== 'marker-mid') { - const strokeWidth = parseFloat( - (attr.strokeWidth || attr['stroke-width']) as string, - ) - if (Number.isFinite(strokeWidth) && strokeWidth > 1) { - const offset = Math.ceil(strokeWidth / 2) - result.refX = type === 'marker-start' ? offset : -offset - } - } - - return result -} diff --git a/packages/x6-core/src/registry/attr/port.ts b/packages/x6-core/src/registry/attr/port.ts deleted file mode 100644 index 41b47b1fb93..00000000000 --- a/packages/x6-core/src/registry/attr/port.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Attr } from './index' - -export const port: Attr.Definition = { - set(port) { - if (port != null && typeof port === 'object' && port.id) { - return port.id as string - } - return port as string - }, -} diff --git a/packages/x6-core/src/registry/attr/raw.ts b/packages/x6-core/src/registry/attr/raw.ts deleted file mode 100644 index dbb6e67773d..00000000000 --- a/packages/x6-core/src/registry/attr/raw.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Attr } from './index' - -export const raw: Attr.Definitions = { - xlinkHref: 'xlink:href', - xlinkShow: 'xlink:show', - xlinkRole: 'xlink:role', - xlinkType: 'xlink:type', - xlinkArcrole: 'xlink:arcrole', - xlinkTitle: 'xlink:title', - xlinkActuate: 'xlink:actuate', - xmlSpace: 'xml:space', - xmlBase: 'xml:base', - xmlLang: 'xml:lang', - preserveAspectRatio: 'preserveAspectRatio', - requiredExtension: 'requiredExtension', - requiredFeatures: 'requiredFeatures', - systemLanguage: 'systemLanguage', - externalResourcesRequired: 'externalResourceRequired', -} diff --git a/packages/x6-core/src/registry/attr/ref.ts b/packages/x6-core/src/registry/attr/ref.ts deleted file mode 100644 index ed38c0186e5..00000000000 --- a/packages/x6-core/src/registry/attr/ref.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { Point, Path, Polyline, Rectangle } from '@antv/x6-geometry' -import { NumberExt, FunctionExt, Dom } from '@antv/x6-common' -import { Attr } from './index' - -export const ref: Attr.Definition = { - // We do not set `ref` attribute directly on an element. - // The attribute itself does not qualify for relative positioning. -} - -// if `refX` is in [0, 1] then `refX` is a fraction of bounding box width -// if `refX` is < 0 then `refX`'s absolute values is the right coordinate of the bounding box -// otherwise, `refX` is the left coordinate of the bounding box - -export const refX: Attr.Definition = { - position: positionWrapper('x', 'width', 'origin'), -} - -export const refY: Attr.Definition = { - position: positionWrapper('y', 'height', 'origin'), -} - -// `ref-dx` and `ref-dy` define the offset of the subelement relative to the right and/or bottom -// coordinate of the reference element. - -export const refDx: Attr.Definition = { - position: positionWrapper('x', 'width', 'corner'), -} - -export const refDy: Attr.Definition = { - position: positionWrapper('y', 'height', 'corner'), -} - -// 'ref-width'/'ref-height' defines the width/height of the subelement relatively to -// the reference element size -// val in 0..1 ref-width = 0.75 sets the width to 75% of the ref. el. width -// val < 0 || val > 1 ref-height = -20 sets the height to the ref. el. height shorter by 20 -export const refWidth: Attr.Definition = { - set: setWrapper('width', 'width'), -} - -export const refHeight: Attr.Definition = { - set: setWrapper('height', 'height'), -} - -export const refRx: Attr.Definition = { - set: setWrapper('rx', 'width'), -} - -export const refRy: Attr.Definition = { - set: setWrapper('ry', 'height'), -} - -export const refRInscribed: Attr.Definition = { - set: ((attrName): Attr.SetFunction => { - const widthFn = setWrapper(attrName, 'width') - const heightFn = setWrapper(attrName, 'height') - return function (value, options) { - const refBBox = options.refBBox - const fn = refBBox.height > refBBox.width ? widthFn : heightFn - return FunctionExt.call(fn, this, value, options) - } - })('r'), -} - -export const refRCircumscribed: Attr.Definition = { - set(val, { refBBox }) { - let value = parseFloat(val as string) - const percentage = NumberExt.isPercentage(val) - if (percentage) { - value /= 100 - } - - const diagonalLength = Math.sqrt( - refBBox.height * refBBox.height + refBBox.width * refBBox.width, - ) - - let rValue - if (Number.isFinite(value)) { - if (percentage || (value >= 0 && value <= 1)) { - rValue = value * diagonalLength - } else { - rValue = Math.max(value + diagonalLength, 0) - } - } - - return { r: rValue } as Attr.SimpleAttrs - }, -} - -export const refCx: Attr.Definition = { - set: setWrapper('cx', 'width'), -} - -export const refCy: Attr.Definition = { - set: setWrapper('cy', 'height'), -} - -export const refDResetOffset: Attr.Definition = { - set: dWrapper({ resetOffset: true }), -} - -export const refDKeepOffset: Attr.Definition = { - set: dWrapper({ resetOffset: false }), -} - -export const refPointsResetOffset: Attr.Definition = { - set: pointsWrapper({ resetOffset: true }), -} - -export const refPointsKeepOffset: Attr.Definition = { - set: pointsWrapper({ resetOffset: false }), -} - -// aliases -// ------- -export const refR = refRInscribed -export const refD = refDResetOffset -export const refPoints = refPointsResetOffset -// Allows to combine both absolute and relative positioning -// refX: 50%, refX2: 20 -export const refX2 = refX -export const refY2 = refY -export const refWidth2 = refWidth -export const refHeight2 = refHeight - -// utils -// ----- - -function positionWrapper( - axis: 'x' | 'y', - dimension: 'width' | 'height', - origin: 'origin' | 'corner', -): Attr.PositionFunction { - return (val, { refBBox }) => { - if (val == null) { - return null - } - - let value = parseFloat(val as string) - const percentage = NumberExt.isPercentage(val) - if (percentage) { - value /= 100 - } - - let delta - if (Number.isFinite(value)) { - const refOrigin = refBBox[origin] - if (percentage || (value > 0 && value < 1)) { - delta = refOrigin[axis] + refBBox[dimension] * value - } else { - delta = refOrigin[axis] + value - } - } - - const point = new Point() - point[axis] = delta || 0 - return point - } -} - -function setWrapper( - attrName: string, - dimension: 'width' | 'height', -): Attr.SetFunction { - return function (val, { refBBox }) { - let value = parseFloat(val as string) - const percentage = NumberExt.isPercentage(val) - if (percentage) { - value /= 100 - } - - const attrs: Attr.SimpleAttrs = {} - - if (Number.isFinite(value)) { - const attrValue = - percentage || (value >= 0 && value <= 1) - ? value * refBBox[dimension] - : Math.max(value + refBBox[dimension], 0) - attrs[attrName] = attrValue - } - - return attrs - } -} - -function shapeWrapper( - shapeConstructor: (value: Attr.ComplexAttrValue) => any, - options: { resetOffset: boolean }, -): (value: Attr.ComplexAttrValue, options: Attr.Options) => T { - const cacheName = 'x6-shape' - const resetOffset = options && options.resetOffset - - return function (value, { elem, refBBox }) { - let cache = Dom.data(elem, cacheName) - if (!cache || cache.value !== value) { - // only recalculate if value has changed - const cachedShape = shapeConstructor(value) - cache = { - value, - shape: cachedShape, - shapeBBox: cachedShape.bbox(), - } - Dom.data(elem, cacheName, cache) - } - - const shape = cache.shape.clone() - const shapeBBox = cache.shapeBBox.clone() as Rectangle - const shapeOrigin = shapeBBox.getOrigin() - const refOrigin = refBBox.getOrigin() - - shapeBBox.x = refOrigin.x - shapeBBox.y = refOrigin.y - - const fitScale = refBBox.getMaxScaleToFit(shapeBBox, refOrigin) - // `maxRectScaleToFit` can give Infinity if width or height is 0 - const sx = shapeBBox.width === 0 || refBBox.width === 0 ? 1 : fitScale.sx - const sy = shapeBBox.height === 0 || refBBox.height === 0 ? 1 : fitScale.sy - - shape.scale(sx, sy, shapeOrigin) - if (resetOffset) { - shape.translate(-shapeOrigin.x, -shapeOrigin.y) - } - - return shape - } -} - -// `d` attribute for SVGPaths -function dWrapper(options: { resetOffset: boolean }): Attr.SetFunction { - function pathConstructor(value: string) { - return Path.parse(value) - } - - const shape = shapeWrapper(pathConstructor, options) - - return (value, args) => { - const path = shape(value, args) - return { - d: path.serialize(), - } - } -} - -// `points` attribute for SVGPolylines and SVGPolygons -function pointsWrapper(options: { resetOffset: boolean }): Attr.SetFunction { - const shape = shapeWrapper((points) => new Polyline(points as any), options) - return (value, args) => { - const polyline = shape(value, args) - return { - points: polyline.serialize(), - } - } -} diff --git a/packages/x6-core/src/registry/attr/stroke.ts b/packages/x6-core/src/registry/attr/stroke.ts deleted file mode 100644 index 239008dda5a..00000000000 --- a/packages/x6-core/src/registry/attr/stroke.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ObjectExt } from '@antv/x6-common' -import { EdgeView } from '../../view/edge' -import { Attr } from './index' - -export const stroke: Attr.Definition = { - qualify: ObjectExt.isPlainObject, - set(stroke: any, { view }) { - const cell = view.cell - const options = { ...stroke } - - if (cell.isEdge() && options.type === 'linearGradient') { - const edgeView = view as EdgeView - const source = edgeView.sourcePoint - const target = edgeView.targetPoint - - options.id = `gradient-${options.type}-${cell.id}` - options.attrs = { - ...options.attrs, - x1: source.x, - y1: source.y, - x2: target.x, - y2: target.y, - gradientUnits: 'userSpaceOnUse', - } - - view.renderer.defs.remove(options.id) - } - - return `url(#${view.renderer.defineGradient(options)})` - }, -} diff --git a/packages/x6-core/src/registry/attr/style.ts b/packages/x6-core/src/registry/attr/style.ts deleted file mode 100644 index c20e7ac440a..00000000000 --- a/packages/x6-core/src/registry/attr/style.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ObjectExt, Dom } from '@antv/x6-common' -import { Attr } from './index' - -export const style: Attr.Definition = { - qualify: ObjectExt.isPlainObject, - set(styles, { elem }) { - Dom.css(elem, styles as Record) - }, -} diff --git a/packages/x6-core/src/registry/attr/text.ts b/packages/x6-core/src/registry/attr/text.ts deleted file mode 100644 index db6b7752c0f..00000000000 --- a/packages/x6-core/src/registry/attr/text.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { - ObjectExt, - JSONObject, - NumberExt, - Dom, - FunctionExt, - Text, -} from '@antv/x6-common' -import { Attr } from './index' - -export const text: Attr.Definition = { - qualify(text, { attrs }) { - return attrs.textWrap == null || !ObjectExt.isPlainObject(attrs.textWrap) - }, - set(text, { view, elem, attrs }) { - const cacheName = 'x6-text' - const cache = Dom.data(elem, cacheName) - const json = (str: any) => { - try { - return JSON.parse(str) as T - } catch (error) { - return str - } - } - const options: Dom.TextOptions = { - x: attrs.x as string | number, - eol: attrs.eol as string, - annotations: json(attrs.annotations) as - | Text.Annotation - | Text.Annotation[], - textPath: json(attrs['text-path'] || attrs.textPath), - textVerticalAnchor: (attrs['text-vertical-anchor'] || - attrs.textVerticalAnchor) as 'middle' | 'bottom' | 'top' | number, - displayEmpty: (attrs['display-empty'] || attrs.displayEmpty) === 'true', - lineHeight: (attrs['line-height'] || attrs.lineHeight) as string, - } - - const fontSize = (attrs['font-size'] || attrs.fontSize) as string - const textHash = JSON.stringify([text, options]) - - if (fontSize) { - elem.setAttribute('font-size', fontSize) - } - - // Updates the text only if there was a change in the string - // or any of its attributes. - if (cache == null || cache !== textHash) { - // Text Along Path Selector - const textPath = options.textPath as any - if (textPath != null && typeof textPath === 'object') { - const selector = textPath.selector - if (typeof selector === 'string') { - const pathNode = view.find(selector)[0] - if (pathNode instanceof SVGPathElement) { - Dom.ensureId(pathNode) - options.textPath = { - 'xlink:href': `#${pathNode.id}`, - ...textPath, - } - } - } - } - - Dom.text(elem as SVGElement, `${text}`, options) - Dom.data(elem, cacheName, textHash) - } - }, -} - -export const textWrap: Attr.Definition = { - qualify: ObjectExt.isPlainObject, - set(val, { view, elem, attrs, refBBox }) { - const info = val as JSONObject - - // option `width` - const width = info.width || 0 - if (NumberExt.isPercentage(width)) { - refBBox.width *= parseFloat(width) / 100 - } else if (width <= 0) { - refBBox.width += width as number - } else { - refBBox.width = width as number - } - - // option `height` - const height = info.height || 0 - if (NumberExt.isPercentage(height)) { - refBBox.height *= parseFloat(height) / 100 - } else if (height <= 0) { - refBBox.height += height as number - } else { - refBBox.height = height as number - } - - // option `text` - let wrappedText - let txt = info.text - if (txt == null) { - txt = attrs.text - } - - if (txt != null) { - wrappedText = Dom.breakText( - `${txt}`, - refBBox, - { - 'font-weight': attrs['font-weight'] || attrs.fontWeight, - 'font-size': attrs['font-size'] || attrs.fontSize, - 'font-family': attrs['font-family'] || attrs.fontFamily, - lineHeight: attrs.lineHeight, - }, - { - // svgDocument: view.graph.view.svg, - ellipsis: info.ellipsis as string, - // hyphen: info.hyphen as string, - // breakWord: info.breakWord as boolean, - }, - ) - } else { - wrappedText = '' - } - - FunctionExt.call(text.set, this, wrappedText, { - view, - elem, - attrs, - refBBox, - cell: view.cell, - }) - }, -} - -const isTextInUse: Attr.QualifyFucntion = (val, { attrs }) => { - return attrs.text !== undefined -} - -export const lineHeight: Attr.Definition = { - qualify: isTextInUse, -} - -export const textVerticalAnchor: Attr.Definition = { - qualify: isTextInUse, -} - -export const textPath: Attr.Definition = { - qualify: isTextInUse, -} - -export const annotations: Attr.Definition = { - qualify: isTextInUse, -} - -export const eol: Attr.Definition = { - qualify: isTextInUse, -} - -export const displayEmpty: Attr.Definition = { - qualify: isTextInUse, -} diff --git a/packages/x6-core/src/registry/attr/title.ts b/packages/x6-core/src/registry/attr/title.ts deleted file mode 100644 index 94040573c9a..00000000000 --- a/packages/x6-core/src/registry/attr/title.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Attr } from './index' - -export const title: Attr.Definition = { - qualify(title, { elem }) { - // HTMLElement title is specified via an attribute (i.e. not an element) - return elem instanceof SVGElement - }, - set(val, { elem }) { - const cacheName = 'x6-title' - const title = `${val}` - const cache = Dom.data(elem, cacheName) - if (cache == null || cache !== title) { - Dom.data(elem, cacheName, title) - // Generally SVGTitleElement should be the first child - // element of its parent. - const firstChild = elem.firstChild as Element - if (firstChild && firstChild.tagName.toUpperCase() === 'TITLE') { - // Update an existing title - const titleElem = firstChild as SVGTitleElement - titleElem.textContent = title - } else { - // Create a new title - const titleNode = document.createElementNS( - elem.namespaceURI, - 'title', - ) as SVGTitleElement - titleNode.textContent = title - elem.insertBefore(titleNode, firstChild) - } - } - }, -} diff --git a/packages/x6-core/src/registry/connection-point/anchor.ts b/packages/x6-core/src/registry/connection-point/anchor.ts deleted file mode 100644 index a2c4243620a..00000000000 --- a/packages/x6-core/src/registry/connection-point/anchor.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Line } from '@antv/x6-geometry' -import { ConnectionPoint } from './index' -import { offset } from './util' - -type Align = 'top' | 'right' | 'bottom' | 'left' - -export interface AnchorOptions extends ConnectionPoint.BaseOptions { - align?: Align - alignOffset?: number -} - -function alignLine(line: Line, type: Align, offset = 0) { - const { start, end } = line - let a - let b - let direction - let coordinate: 'x' | 'y' - - switch (type) { - case 'left': - coordinate = 'x' - a = end - b = start - direction = -1 - break - case 'right': - coordinate = 'x' - a = start - b = end - direction = 1 - break - case 'top': - coordinate = 'y' - a = end - b = start - direction = -1 - break - case 'bottom': - coordinate = 'y' - a = start - b = end - direction = 1 - break - default: - return - } - - if (start[coordinate] < end[coordinate]) { - a[coordinate] = b[coordinate] - } else { - b[coordinate] = a[coordinate] - } - - if (Number.isFinite(offset)) { - a[coordinate] += direction * offset - b[coordinate] += direction * offset - } -} - -/** - * Places the connection point at the edge's endpoint. - */ -export const anchor: ConnectionPoint.Definition = function ( - line, - view, - magnet, - options, -) { - const { alignOffset, align } = options - if (align) { - alignLine(line, align, alignOffset) - } - return offset(line.end, line.start, options.offset) -} diff --git a/packages/x6-core/src/registry/connection-point/bbox.ts b/packages/x6-core/src/registry/connection-point/bbox.ts deleted file mode 100644 index bbadb33893f..00000000000 --- a/packages/x6-core/src/registry/connection-point/bbox.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { offset, getStrokeWidth } from './util' -import { ConnectionPoint } from './index' - -export interface BBoxOptions extends ConnectionPoint.StrokedOptions {} - -/** - * Places the connection point at the intersection between the edge - * path end segment and the target node bbox. - */ -export const bbox: ConnectionPoint.Definition = function ( - line, - view, - magnet, - options, -) { - const bbox = view.getBBoxOfElement(magnet) - if (options.stroked) { - bbox.inflate(getStrokeWidth(magnet) / 2) - } - const intersections = line.intersect(bbox) - const p = - intersections && intersections.length - ? line.start.closest(intersections)! - : line.end - return offset(p, line.start, options.offset) -} diff --git a/packages/x6-core/src/registry/connection-point/boundary.ts b/packages/x6-core/src/registry/connection-point/boundary.ts deleted file mode 100644 index d0ac477c7c6..00000000000 --- a/packages/x6-core/src/registry/connection-point/boundary.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { ObjectExt, Dom } from '@antv/x6-common' -import { Path, Rectangle, Ellipse, Segment } from '@antv/x6-geometry' -import { offset, getStrokeWidth, findShapeNode } from './util' -import { ConnectionPoint } from './index' -import { Util } from '../../util' - -export interface BoundaryOptions extends ConnectionPoint.StrokedOptions { - selector?: string | string[] - insideout?: boolean - precision?: number - extrapolate?: boolean - sticky?: boolean -} - -export interface BoundaryCache { - shapeBBox?: Rectangle | null - segmentSubdivisions?: Segment[][] -} - -/** - * Places the connection point at the intersection between the - * edge path end segment and the actual shape of the target magnet. - */ -export const boundary: ConnectionPoint.Definition = function ( - line, - view, - magnet, - options, -) { - let node - let intersection - const anchor = line.end - const selector = options.selector - - if (typeof selector === 'string') { - node = view.findOne(selector) - } else if (Array.isArray(selector)) { - node = ObjectExt.getByPath(magnet, selector) - } else { - node = findShapeNode(magnet) - } - - if (!Dom.isSVGGraphicsElement(node)) { - if (node === magnet || !Dom.isSVGGraphicsElement(magnet)) { - return anchor - } - node = magnet - } - - const localShape = view.getShapeOfElement(node) - const magnetMatrix = view.getMatrixOfElement(node) - const translateMatrix = view.getRootTranslatedMatrix() - const rotateMatrix = view.getRootRotatedMatrix() - const targetMatrix = translateMatrix - .multiply(rotateMatrix) - .multiply(magnetMatrix) - const localMatrix = targetMatrix.inverse() - const localLine = Util.transformLine(line, localMatrix) - const localRef = localLine.start.clone() - const data = view.getDataOfElement(node) as BoundaryCache - - if (options.insideout === false) { - if (data.shapeBBox == null) { - data.shapeBBox = localShape.bbox() - } - const localBBox = data.shapeBBox - if (localBBox != null && localBBox.containsPoint(localRef)) { - return anchor - } - } - - if (options.extrapolate === true) { - localLine.setLength(1e6) - } - - // Caching segment subdivisions for paths - let pathOptions - if (Path.isPath(localShape)) { - const precision = options.precision || 2 - if (data.segmentSubdivisions == null) { - data.segmentSubdivisions = localShape.getSegmentSubdivisions({ - precision, - }) - } - pathOptions = { - precision, - segmentSubdivisions: data.segmentSubdivisions, - } - - intersection = localLine.intersect(localShape, pathOptions) - } else { - intersection = localLine.intersect(localShape) - } - - if (intersection) { - if (Array.isArray(intersection)) { - intersection = localRef.closest(intersection) - } - } else if (options.sticky === true) { - // No intersection, find the closest point instead - if (Rectangle.isRectangle(localShape)) { - intersection = localShape.getNearestPointToPoint(localRef) - } else if (Ellipse.isEllipse(localShape)) { - intersection = localShape.intersectsWithLineFromCenterToPoint(localRef) - } else { - intersection = localShape.closestPoint(localRef, pathOptions) - } - } - - const cp = intersection - ? Util.transformPoint(intersection, targetMatrix) - : anchor - let cpOffset = options.offset || 0 - if (options.stroked !== false) { - if (typeof cpOffset === 'object') { - cpOffset = { ...cpOffset } - if (cpOffset.x == null) { - cpOffset.x = 0 - } - cpOffset.x += getStrokeWidth(node) / 2 - } else { - cpOffset += getStrokeWidth(node) / 2 - } - } - - return offset(cp, line.start, cpOffset) -} diff --git a/packages/x6-core/src/registry/connection-point/index.ts b/packages/x6-core/src/registry/connection-point/index.ts deleted file mode 100644 index cb20632e7df..00000000000 --- a/packages/x6-core/src/registry/connection-point/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Point, Line } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { Edge } from '../../model/edge' -import { CellView } from '../../view/cell' -import * as connectionPoints from './main' - -export namespace ConnectionPoint { - export type Definition = ( - line: Line, - view: CellView, - magnet: SVGElement, - options: T, - type: Edge.TerminalType, - ) => Point - - export type CommonDefinition = Definition - - export interface BaseOptions { - /** - * Offset the connection point from the anchor by the specified - * distance along the end edge path segment. - * - * Default is `0`. - */ - offset?: number | Point.PointLike - } - - export interface StrokedOptions extends BaseOptions { - /** - * If the stroke width should be included when calculating the - * connection point. - * - * Default is `false`. - */ - stroked?: boolean - } -} - -export namespace ConnectionPoint { - export type Presets = typeof ConnectionPoint['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[3] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace ConnectionPoint { - export const presets = connectionPoints - export const registry = Registry.create({ - type: 'connection point', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-core/src/registry/connection-point/main.ts b/packages/x6-core/src/registry/connection-point/main.ts deleted file mode 100644 index f0b301c448b..00000000000 --- a/packages/x6-core/src/registry/connection-point/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './bbox' -export * from './rect' -export * from './boundary' -export * from './anchor' diff --git a/packages/x6-core/src/registry/connection-point/rect.ts b/packages/x6-core/src/registry/connection-point/rect.ts deleted file mode 100644 index 8b93163e9e3..00000000000 --- a/packages/x6-core/src/registry/connection-point/rect.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { FunctionExt } from '@antv/x6-common' -import { bbox } from './bbox' -import { offset, getStrokeWidth } from './util' -import { ConnectionPoint } from './index' - -export interface RectangleOptions extends ConnectionPoint.StrokedOptions {} - -/** - * Places the connection point at the intersection between the - * link path end segment and the element's unrotated bbox. - */ -export const rect: ConnectionPoint.Definition = function ( - line, - view, - magnet, - options, - type, -) { - const cell = view.cell - const angle = cell.isNode() ? cell.getAngle() : 0 - if (angle === 0) { - return FunctionExt.call(bbox, this, line, view, magnet, options, type) - } - - const bboxRaw = view.getUnrotatedBBoxOfElement(magnet) - if (options.stroked) { - bboxRaw.inflate(getStrokeWidth(magnet) / 2) - } - const center = bboxRaw.getCenter() - const lineRaw = line.clone().rotate(angle, center) - const intersections = lineRaw.setLength(1e6).intersect(bboxRaw) - const p = - intersections && intersections.length - ? lineRaw.start.closest(intersections)!.rotate(-angle, center) - : line.end - return offset(p, line.start, options.offset) -} diff --git a/packages/x6-core/src/registry/connection-point/util.ts b/packages/x6-core/src/registry/connection-point/util.ts deleted file mode 100644 index 8962784d3df..00000000000 --- a/packages/x6-core/src/registry/connection-point/util.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Point, Line } from '@antv/x6-geometry' - -export function offset( - p1: Point, - p2: Point, - offset?: number | Point.PointLike, -) { - let tx: number | undefined - if (typeof offset === 'object') { - if (Number.isFinite(offset.y)) { - const line = new Line(p2, p1) - const { start, end } = line.parallel(offset.y) - p2 = start // eslint-disable-line - p1 = end // eslint-disable-line - } - tx = offset.x - } else { - tx = offset - } - - if (tx == null || !Number.isFinite(tx)) { - return p1 - } - - const length = p1.distance(p2) - if (tx === 0 && length > 0) { - return p1 - } - return p1.move(p2, -Math.min(tx, length - 1)) -} - -export function getStrokeWidth(magnet: SVGElement) { - const stroke = magnet.getAttribute('stroke-width') - if (stroke === null) { - return 0 - } - return parseFloat(stroke) || 0 -} - -export function findShapeNode(magnet: Element) { - if (magnet == null) { - return null - } - - let node = magnet - do { - let tagName = node.tagName - if (typeof tagName !== 'string') return null - tagName = tagName.toUpperCase() - if (tagName === 'G') { - node = node.firstElementChild as Element - } else if (tagName === 'TITLE') { - node = node.nextElementSibling as Element - } else break - } while (node) - - return node -} diff --git a/packages/x6-core/src/registry/connection-strategy/index.ts b/packages/x6-core/src/registry/connection-strategy/index.ts deleted file mode 100644 index e925967032b..00000000000 --- a/packages/x6-core/src/registry/connection-strategy/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { Edge } from '../../model' -import { CellView } from '../../view' -import * as strategies from './main' - -export namespace ConnectionStrategy { - export type Definition = ( - this: any, // todo Graph - terminal: Edge.TerminalCellData, - cellView: CellView, - magnet: Element, - coords: Point.PointLike, - edge: Edge, - type: Edge.TerminalType, - options: KeyValue, - ) => Edge.TerminalCellData -} - -export namespace ConnectionStrategy { - export type Presets = typeof ConnectionStrategy['presets'] - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: KeyValue - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace ConnectionStrategy { - export const presets = strategies - export const registry = Registry.create({ - type: 'connection strategy', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-core/src/registry/connection-strategy/main.ts b/packages/x6-core/src/registry/connection-strategy/main.ts deleted file mode 100644 index 718ea692bc5..00000000000 --- a/packages/x6-core/src/registry/connection-strategy/main.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './noop' -export * from './pin' diff --git a/packages/x6-core/src/registry/connection-strategy/noop.ts b/packages/x6-core/src/registry/connection-strategy/noop.ts deleted file mode 100644 index c618f812c8c..00000000000 --- a/packages/x6-core/src/registry/connection-strategy/noop.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ConnectionStrategy } from './index' - -export const noop: ConnectionStrategy.Definition = (terminal) => terminal diff --git a/packages/x6-core/src/registry/connection-strategy/pin.ts b/packages/x6-core/src/registry/connection-strategy/pin.ts deleted file mode 100644 index 42eea3cdb6f..00000000000 --- a/packages/x6-core/src/registry/connection-strategy/pin.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Node, Edge } from '../../model' -import { EdgeView, NodeView } from '../../view' -import { ConnectionStrategy } from './index' - -function toPercentage(value: number, max: number) { - if (max === 0) { - return '0%' - } - - return `${Math.round((value / max) * 100)}%` -} - -function pin(relative: boolean) { - const strategy: ConnectionStrategy.Definition = ( - terminal, - view, - magnet, - coords, - ) => { - return view.isEdgeElement(magnet) - ? pinEdgeTerminal(relative, terminal, view as EdgeView, magnet, coords) - : pinNodeTerminal(relative, terminal, view as NodeView, magnet, coords) - } - - return strategy -} - -function pinNodeTerminal( - relative: boolean, - data: Edge.TerminalCellData, - view: NodeView, - magnet: Element, - coords: Point.PointLike, -) { - const node = view.cell as Node - const angle = node.getAngle() - const bbox = view.getUnrotatedBBoxOfElement(magnet as SVGElement) - const center = node.getBBox().getCenter() - const pos = Point.create(coords).rotate(angle, center) - - let dx: number | string = pos.x - bbox.x - let dy: number | string = pos.y - bbox.y - - if (relative) { - dx = toPercentage(dx, bbox.width) - dy = toPercentage(dy, bbox.height) - } - - data.anchor = { - name: 'topLeft', - args: { - dx, - dy, - rotate: true, - }, - } - - return data -} - -function pinEdgeTerminal( - relative: boolean, - end: Edge.TerminalCellData, - view: EdgeView, - magnet: Element, - coords: Point.PointLike, -) { - const connection = view.getConnection() - if (!connection) { - return end - } - - const length = connection.closestPointLength(coords) - if (relative) { - const totalLength = connection.length() - end.anchor = { - name: 'ratio', - args: { - ratio: length / totalLength, - }, - } - } else { - end.anchor = { - name: 'length', - args: { - length, - }, - } - } - - return end -} - -export const pinRelative = pin(true) -export const pinAbsolute = pin(false) diff --git a/packages/x6-core/src/registry/connector/index.ts b/packages/x6-core/src/registry/connector/index.ts deleted file mode 100644 index c0010ffa07d..00000000000 --- a/packages/x6-core/src/registry/connector/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Point, Path } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { EdgeView } from '../../view' -import * as connectors from './main' - -export namespace Connector { - export interface BaseOptions { - raw?: boolean - } - - export type Definition = ( - this: EdgeView, - sourcePoint: Point.PointLike, - targetPoint: Point.PointLike, - routePoints: Point.PointLike[], - options: T, - edgeView: EdgeView, - ) => Path | string -} - -export namespace Connector { - export type Presets = typeof Connector['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[3] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace Connector { - export const presets = connectors - export const registry = Registry.create({ - type: 'connector', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-core/src/registry/connector/jumpover.ts b/packages/x6-core/src/registry/connector/jumpover.ts deleted file mode 100644 index 6053d4b353f..00000000000 --- a/packages/x6-core/src/registry/connector/jumpover.ts +++ /dev/null @@ -1,388 +0,0 @@ -/* eslint-disable no-underscore-dangle */ - -import { Point, Line, Path } from '@antv/x6-geometry' -import { Edge } from '../../model' -import { EdgeView } from '../../view' -import { Connector } from './index' - -// takes care of math. error for case when jump is too close to end of line -const CLOSE_PROXIMITY_PADDING = 1 -const F13 = 1 / 3 -const F23 = 2 / 3 - -function setupUpdating(view: EdgeView) { - let updateList = (view.renderer as any)._jumpOverUpdateList - - // first time setup for this paper - if (updateList == null) { - updateList = (view.renderer as any)._jumpOverUpdateList = [] - - /** - * Handler for a batch:stop event to force - * update of all registered links with jump over connector - */ - view.renderer.on('cell:mouseup', () => { - const list = (view.renderer as any)._jumpOverUpdateList - for (let i = 0; i < list.length; i += 1) { - list[i].update() - } - }) - - view.renderer.on('model:reseted', () => { - updateList = (view.renderer as any)._jumpOverUpdateList = [] - }) - } - - // add this link to a list so it can be updated when some other link is updated - if (updateList.indexOf(view) < 0) { - updateList.push(view) - - // watch for change of connector type or removal of link itself - // to remove the link from a list of jump over connectors - const clean = () => updateList.splice(updateList.indexOf(view), 1) - view.cell.once('change:connector', clean) - view.cell.once('removed', clean) - } -} - -function createLines( - sourcePoint: Point.PointLike, - targetPoint: Point.PointLike, - route: Point.PointLike[] = [], -) { - const points = [sourcePoint, ...route, targetPoint] - const lines: Line[] = [] - - points.forEach((point, idx) => { - const next = points[idx + 1] - if (next != null) { - lines.push(new Line(point, next)) - } - }) - - return lines -} - -function findLineIntersections(line: Line, crossCheckLines: Line[]) { - const intersections: Point[] = [] - crossCheckLines.forEach((crossCheckLine) => { - const intersection = line.intersectsWithLine(crossCheckLine) - if (intersection) { - intersections.push(intersection) - } - }) - return intersections -} - -function getDistence(p1: Point, p2: Point) { - return new Line(p1, p2).squaredLength() -} - -/** - * Split input line into multiple based on intersection points. - */ -function createJumps(line: Line, intersections: Point[], jumpSize: number) { - return intersections.reduce((memo, point, idx) => { - // skipping points that were merged with the previous line - // to make bigger arc over multiple lines that are close to each other - if (skippedPoints.includes(point)) { - return memo - } - - // always grab the last line from buffer and modify it - const lastLine = memo.pop() || line - - // calculate start and end of jump by moving by a given size of jump - const jumpStart = Point.create(point).move(lastLine.start, -jumpSize) - let jumpEnd = Point.create(point).move(lastLine.start, +jumpSize) - - // now try to look at the next intersection point - const nextPoint = intersections[idx + 1] - if (nextPoint != null) { - const distance = jumpEnd.distance(nextPoint) - if (distance <= jumpSize) { - // next point is close enough, move the jump end by this - // difference and mark the next point to be skipped - jumpEnd = nextPoint.move(lastLine.start, distance) - skippedPoints.push(nextPoint) - } - } else { - // this block is inside of `else` as an optimization so the distance is - // not calculated when we know there are no other intersection points - const endDistance = jumpStart.distance(lastLine.end) - // if the end is too close to possible jump, draw remaining line instead of a jump - if (endDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) { - memo.push(lastLine) - return memo - } - } - - const startDistance = jumpEnd.distance(lastLine.start) - if (startDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) { - // if the start of line is too close to jump, draw that line instead of a jump - memo.push(lastLine) - return memo - } - - // finally create a jump line - const jumpLine = new Line(jumpStart, jumpEnd) - // it's just simple line but with a `isJump` property - jumppedLines.push(jumpLine) - - memo.push( - new Line(lastLine.start, jumpStart), - jumpLine, - new Line(jumpEnd, lastLine.end), - ) - - return memo - }, []) -} - -function buildPath( - lines: Line[], - jumpSize: number, - jumpType: JumpType, - radius: number, -) { - const path = new Path() - let segment - - // first move to the start of a first line - segment = Path.createSegment('M', lines[0].start) - path.appendSegment(segment) - - lines.forEach((line, index) => { - if (jumppedLines.includes(line)) { - let angle - let diff - - let control1 - let control2 - - if (jumpType === 'arc') { - // approximates semicircle with 2 curves - angle = -90 - // determine rotation of arc based on difference between points - diff = line.start.diff(line.end) - // make sure the arc always points up (or right) - const xAxisRotate = diff.x < 0 || (diff.x === 0 && diff.y < 0) - if (xAxisRotate) { - angle += 180 - } - - const center = line.getCenter() - const centerLine = new Line(center, line.end).rotate(angle, center) - - let halfLine - - // first half - halfLine = new Line(line.start, center) - control1 = halfLine.pointAt(2 / 3).rotate(angle, line.start) - control2 = centerLine.pointAt(1 / 3).rotate(-angle, centerLine.end) - - segment = Path.createSegment('C', control1, control2, centerLine.end) - path.appendSegment(segment) - - // second half - halfLine = new Line(center, line.end) - - control1 = centerLine.pointAt(1 / 3).rotate(angle, centerLine.end) - control2 = halfLine.pointAt(1 / 3).rotate(-angle, line.end) - - segment = Path.createSegment('C', control1, control2, line.end) - path.appendSegment(segment) - } else if (jumpType === 'gap') { - segment = Path.createSegment('M', line.end) - path.appendSegment(segment) - } else if (jumpType === 'cubic') { - // approximates semicircle with 1 curve - angle = line.start.theta(line.end) - - const xOffset = jumpSize * 0.6 - let yOffset = jumpSize * 1.35 - - // determine rotation of arc based on difference between points - diff = line.start.diff(line.end) - // make sure the arc always points up (or right) - const xAxisRotate = diff.x < 0 || (diff.x === 0 && diff.y < 0) - if (xAxisRotate) { - yOffset *= -1 - } - - control1 = new Point( - line.start.x + xOffset, - line.start.y + yOffset, - ).rotate(angle, line.start) - control2 = new Point(line.end.x - xOffset, line.end.y + yOffset).rotate( - angle, - line.end, - ) - - segment = Path.createSegment('C', control1, control2, line.end) - path.appendSegment(segment) - } - } else { - const nextLine = lines[index + 1] - if (radius === 0 || !nextLine || jumppedLines.includes(nextLine)) { - segment = Path.createSegment('L', line.end) - path.appendSegment(segment) - } else { - buildRoundedSegment(radius, path, line.end, line.start, nextLine.end) - } - } - }) - - return path -} - -function buildRoundedSegment( - offset: number, - path: Path, - curr: Point, - prev: Point, - next: Point, -) { - const prevDistance = curr.distance(prev) / 2 - const nextDistance = curr.distance(next) / 2 - - const startMove = -Math.min(offset, prevDistance) - const endMove = -Math.min(offset, nextDistance) - - const roundedStart = curr.clone().move(prev, startMove).round() - const roundedEnd = curr.clone().move(next, endMove).round() - - const control1 = new Point( - F13 * roundedStart.x + F23 * curr.x, - F23 * curr.y + F13 * roundedStart.y, - ) - const control2 = new Point( - F13 * roundedEnd.x + F23 * curr.x, - F23 * curr.y + F13 * roundedEnd.y, - ) - - let segment - segment = Path.createSegment('L', roundedStart) - path.appendSegment(segment) - - segment = Path.createSegment('C', control1, control2, roundedEnd) - path.appendSegment(segment) -} - -export type JumpType = 'arc' | 'gap' | 'cubic' - -export interface JumpoverConnectorOptions extends Connector.BaseOptions { - size?: number - radius?: number - type?: JumpType - ignoreConnectors?: string[] -} - -let jumppedLines: Line[] -let skippedPoints: Point[] - -export const jumpover: Connector.Definition = - function (sourcePoint, targetPoint, routePoints, options = {}) { - jumppedLines = [] - skippedPoints = [] - - setupUpdating(this) - - const jumpSize = options.size || 5 - const jumpType = options.type || 'arc' - const radius = options.radius || 0 - // list of connector types not to jump over. - const ignoreConnectors = options.ignoreConnectors || ['smooth'] - - const renderer = this.renderer - const model = renderer.model - const allLinks = model.getEdges() as Edge[] - - // there is just one link, draw it directly - if (allLinks.length === 1) { - return buildPath( - createLines(sourcePoint, targetPoint, routePoints), - jumpSize, - jumpType, - radius, - ) - } - - const edge = this.cell - const thisIndex = allLinks.indexOf(edge) - const defaultConnector = renderer.options.connecting.connector || {} - - // not all links are meant to be jumped over. - const edges = allLinks.filter((link, idx) => { - const connector = link.getConnector() || (defaultConnector as any) - - // avoid jumping over links with connector type listed in `ignored connectors`. - if (ignoreConnectors.includes(connector.name)) { - return false - } - // filter out links that are above this one and have the same connector type - // otherwise there would double hoops for each intersection - if (idx > thisIndex) { - return connector.name !== 'jumpover' - } - return true - }) - - // find views for all links - const linkViews = edges.map((edge) => { - return renderer.findViewByCell(edge) as EdgeView - }) - - // create lines for this link - const thisLines = createLines(sourcePoint, targetPoint, routePoints) - - // create lines for all other links - const linkLines = linkViews.map((linkView) => { - if (linkView == null) { - return [] - } - if (linkView === this) { - return thisLines - } - return createLines( - linkView.sourcePoint, - linkView.targetPoint, - linkView.routePoints, - ) - }) - - // transform lines for this link by splitting with jump lines at - // points of intersection with other links - const jumpingLines: Line[] = [] - - thisLines.forEach((line) => { - // iterate all links and grab the intersections with this line - // these are then sorted by distance so the line can be split more easily - - const intersections = edges - .reduce((memo, link, i) => { - // don't intersection with itself - if (link !== edge) { - const lineIntersections = findLineIntersections(line, linkLines[i]) - memo.push(...lineIntersections) - } - return memo - }, []) - .sort((a, b) => getDistence(line.start, a) - getDistence(line.start, b)) - - if (intersections.length > 0) { - // split the line based on found intersection points - jumpingLines.push(...createJumps(line, intersections, jumpSize)) - } else { - // without any intersection the line goes uninterrupted - jumpingLines.push(line) - } - }) - - const path = buildPath(jumpingLines, jumpSize, jumpType, radius) - - jumppedLines = [] - skippedPoints = [] - - return options.raw ? path : path.serialize() - } diff --git a/packages/x6-core/src/registry/connector/loop.ts b/packages/x6-core/src/registry/connector/loop.ts deleted file mode 100644 index c01cf6e5c71..00000000000 --- a/packages/x6-core/src/registry/connector/loop.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Path, Point } from '@antv/x6-geometry' -import { Connector } from './index' - -export interface LoopConnectorOptions extends Connector.BaseOptions { - split?: boolean | number -} - -export const loop: Connector.Definition = function ( - sourcePoint, - targetPoint, - routePoints, - options = {}, -) { - const fix = routePoints.length === 3 ? 0 : 1 - const p1 = Point.create(routePoints[0 + fix]) - const p2 = Point.create(routePoints[2 + fix]) - const center = Point.create(routePoints[1 + fix]) - - if (!Point.equals(sourcePoint, targetPoint)) { - const middle = new Point( - (sourcePoint.x + targetPoint.x) / 2, - (sourcePoint.y + targetPoint.y) / 2, - ) - const angle = middle.angleBetween( - Point.create(sourcePoint).rotate(90, middle), - center, - ) - if (angle > 1) { - p1.rotate(180 - angle, middle) - p2.rotate(180 - angle, middle) - center.rotate(180 - angle, middle) - } - } - - const pathData = ` - M ${sourcePoint.x} ${sourcePoint.y} - Q ${p1.x} ${p1.y} ${center.x} ${center.y} - Q ${p2.x} ${p2.y} ${targetPoint.x} ${targetPoint.y} - ` - - return options.raw ? Path.parse(pathData) : pathData -} diff --git a/packages/x6-core/src/registry/connector/main.ts b/packages/x6-core/src/registry/connector/main.ts deleted file mode 100644 index e8fda3c5e3c..00000000000 --- a/packages/x6-core/src/registry/connector/main.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './normal' -export * from './loop' -export * from './rounded' -export * from './smooth' -export * from './jumpover' diff --git a/packages/x6-core/src/registry/connector/normal.ts b/packages/x6-core/src/registry/connector/normal.ts deleted file mode 100644 index b21ff7163be..00000000000 --- a/packages/x6-core/src/registry/connector/normal.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Polyline, Path } from '@antv/x6-geometry' -import { Connector } from './index' - -export const normal: Connector.Definition = function ( - sourcePoint, - targetPoint, - routePoints, - options = {}, -) { - const points = [sourcePoint, ...routePoints, targetPoint] - const polyline = new Polyline(points) - const path = new Path(polyline) - return options.raw ? path : path.serialize() -} diff --git a/packages/x6-core/src/registry/connector/rounded.ts b/packages/x6-core/src/registry/connector/rounded.ts deleted file mode 100644 index aa94518a58b..00000000000 --- a/packages/x6-core/src/registry/connector/rounded.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Point, Path } from '@antv/x6-geometry' -import { Connector } from './index' - -export interface RoundedConnectorOptions extends Connector.BaseOptions { - radius?: number -} - -export const rounded: Connector.Definition = function ( - sourcePoint, - targetPoint, - routePoints, - options = {}, -) { - const path = new Path() - - path.appendSegment(Path.createSegment('M', sourcePoint)) - - const f13 = 1 / 3 - const f23 = 2 / 3 - const radius = options.radius || 10 - - let prevDistance - let nextDistance - for (let i = 0, ii = routePoints.length; i < ii; i += 1) { - const curr = Point.create(routePoints[i]) - const prev = routePoints[i - 1] || sourcePoint - const next = routePoints[i + 1] || targetPoint - - prevDistance = nextDistance || curr.distance(prev) / 2 - nextDistance = curr.distance(next) / 2 - - const startMove = -Math.min(radius, prevDistance) - const endMove = -Math.min(radius, nextDistance) - - const roundedStart = curr.clone().move(prev, startMove).round() - const roundedEnd = curr.clone().move(next, endMove).round() - - const control1 = new Point( - f13 * roundedStart.x + f23 * curr.x, - f23 * curr.y + f13 * roundedStart.y, - ) - const control2 = new Point( - f13 * roundedEnd.x + f23 * curr.x, - f23 * curr.y + f13 * roundedEnd.y, - ) - - path.appendSegment(Path.createSegment('L', roundedStart)) - path.appendSegment(Path.createSegment('C', control1, control2, roundedEnd)) - } - - path.appendSegment(Path.createSegment('L', targetPoint)) - - return options.raw ? path : path.serialize() -} diff --git a/packages/x6-core/src/registry/connector/smooth.ts b/packages/x6-core/src/registry/connector/smooth.ts deleted file mode 100644 index 0aa67df31d2..00000000000 --- a/packages/x6-core/src/registry/connector/smooth.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Curve, Path } from '@antv/x6-geometry' -import { Connector } from './index' - -export interface SmoothConnectorOptions extends Connector.BaseOptions { - direction?: 'H' | 'V' -} - -export const smooth: Connector.Definition = function ( - sourcePoint, - targetPoint, - routePoints, - options = {}, -) { - let path - let direction = options.direction - - if (routePoints && routePoints.length !== 0) { - const points = [sourcePoint, ...routePoints, targetPoint] - const curves = Curve.throughPoints(points) - path = new Path(curves) - } else { - // If we have no route, use a default cubic bezier curve, cubic bezier - // requires two control points, the control points have `x` midway - // between source and target. This produces an S-like curve. - - path = new Path() - path.appendSegment(Path.createSegment('M', sourcePoint)) - - if (!direction) { - direction = - Math.abs(sourcePoint.x - targetPoint.x) >= - Math.abs(sourcePoint.y - targetPoint.y) - ? 'H' - : 'V' - } - - if (direction === 'H') { - const controlPointX = (sourcePoint.x + targetPoint.x) / 2 - path.appendSegment( - Path.createSegment( - 'C', - controlPointX, - sourcePoint.y, - controlPointX, - targetPoint.y, - targetPoint.x, - targetPoint.y, - ), - ) - } else { - const controlPointY = (sourcePoint.y + targetPoint.y) / 2 - path.appendSegment( - Path.createSegment( - 'C', - sourcePoint.x, - controlPointY, - targetPoint.x, - controlPointY, - targetPoint.x, - targetPoint.y, - ), - ) - } - } - - return options.raw ? path : path.serialize() -} diff --git a/packages/x6-core/src/registry/edge-anchor/closest.ts b/packages/x6-core/src/registry/edge-anchor/closest.ts deleted file mode 100644 index bf03fc0e53b..00000000000 --- a/packages/x6-core/src/registry/edge-anchor/closest.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { ResolveOptions, resolve } from '../node-anchor/util' -import { EdgeAnchor } from './index' - -export interface ClosestEndpointOptions extends ResolveOptions {} - -export const getClosestPoint: EdgeAnchor.ResolvedDefinition = - function ( - view, - magnet, - refPoint, - options, // eslint-disable-line @typescript-eslint/no-unused-vars - ) { - const closestPoint = view.getClosestPoint(refPoint) - return closestPoint != null ? closestPoint : new Point() - } - -export const closest = resolve< - EdgeAnchor.ResolvedDefinition, - EdgeAnchor.Definition ->(getClosestPoint) diff --git a/packages/x6-core/src/registry/edge-anchor/index.ts b/packages/x6-core/src/registry/edge-anchor/index.ts deleted file mode 100644 index 3a9a2c6489d..00000000000 --- a/packages/x6-core/src/registry/edge-anchor/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { Edge } from '../../model/edge' -import { EdgeView } from '../../view' -import * as anchors from './main' - -export namespace EdgeAnchor { - export type Definition = ( - this: EdgeView, - view: EdgeView, - magnet: SVGElement, - ref: Point | Point.PointLike | SVGElement, - options: T, - type: Edge.TerminalType, - ) => Point - - export type CommonDefinition = Definition - - export type ResolvedDefinition = ( - this: EdgeView, - view: EdgeView, - magnet: SVGElement, - refPoint: Point, - options: T, - ) => Point -} - -export namespace EdgeAnchor { - export type Presets = typeof EdgeAnchor['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[3] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace EdgeAnchor { - export const presets = anchors - export const registry = Registry.create({ - type: 'edge endpoint', - }) - registry.register(presets, true) -} diff --git a/packages/x6-core/src/registry/edge-anchor/length.ts b/packages/x6-core/src/registry/edge-anchor/length.ts deleted file mode 100644 index 2c8df553b71..00000000000 --- a/packages/x6-core/src/registry/edge-anchor/length.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { EdgeAnchor } from './index' - -export interface LengthEndpointOptions { - length?: number -} - -export const length: EdgeAnchor.Definition = function ( - view, - magnet, - ref, - options, -) { - const length = options.length != null ? options.length : 20 - return view.getPointAtLength(length)! -} diff --git a/packages/x6-core/src/registry/edge-anchor/main.ts b/packages/x6-core/src/registry/edge-anchor/main.ts deleted file mode 100644 index b0922e0301b..00000000000 --- a/packages/x6-core/src/registry/edge-anchor/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './ratio' -export * from './length' -export * from './orth' -export { - closest, - ClosestEndpointOptions as ClosestAnchorOptions, -} from './closest' diff --git a/packages/x6-core/src/registry/edge-anchor/orth.ts b/packages/x6-core/src/registry/edge-anchor/orth.ts deleted file mode 100644 index d37c2ac8534..00000000000 --- a/packages/x6-core/src/registry/edge-anchor/orth.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Line, Point } from '@antv/x6-geometry' -import { FunctionExt } from '@antv/x6-common' -import { ResolveOptions, resolve, getPointAtEdge } from '../node-anchor/util' -import { getClosestPoint } from './closest' -import { EdgeAnchor } from './index' - -export interface OrthEndpointOptions extends ResolveOptions { - fallbackAt?: number | string -} - -const orthogonal: EdgeAnchor.ResolvedDefinition = - function (view, magnet, refPoint, options): Point { - const OFFSET = 1e6 - const path = view.getConnection()! - const segmentSubdivisions = view.getConnectionSubdivisions() - const vLine = new Line( - refPoint.clone().translate(0, OFFSET), - refPoint.clone().translate(0, -OFFSET), - ) - const hLine = new Line( - refPoint.clone().translate(OFFSET, 0), - refPoint.clone().translate(-OFFSET, 0), - ) - - const vIntersections = vLine.intersect(path, { - segmentSubdivisions, - }) - - const hIntersections = hLine.intersect(path, { - segmentSubdivisions, - }) - - const intersections = [] - if (vIntersections) { - intersections.push(...vIntersections) - } - if (hIntersections) { - intersections.push(...hIntersections) - } - - if (intersections.length > 0) { - return refPoint.closest(intersections)! - } - - if (options.fallbackAt != null) { - return getPointAtEdge(view, options.fallbackAt)! - } - - return FunctionExt.call( - getClosestPoint, - this, - view, - magnet, - refPoint, - options, - ) - } - -export const orth = resolve< - EdgeAnchor.ResolvedDefinition, - EdgeAnchor.Definition ->(orthogonal) diff --git a/packages/x6-core/src/registry/edge-anchor/ratio.ts b/packages/x6-core/src/registry/edge-anchor/ratio.ts deleted file mode 100644 index 04f4c40d0f8..00000000000 --- a/packages/x6-core/src/registry/edge-anchor/ratio.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { EdgeAnchor } from './index' - -export interface RatioEndpointOptions { - ratio?: number -} - -export const ratio: EdgeAnchor.Definition = function ( - view, - magnet, - ref, - options, -) { - let ratio = options.ratio != null ? options.ratio : 0.5 - if (ratio > 1) { - ratio /= 100 - } - return view.getPointAtRatio(ratio)! -} diff --git a/packages/x6-core/src/registry/filter/blur.ts b/packages/x6-core/src/registry/filter/blur.ts deleted file mode 100644 index 5662cec6d24..00000000000 --- a/packages/x6-core/src/registry/filter/blur.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getNumber } from './util' - -export interface BlurArgs { - /** - * Horizontal blur. Default `2` - */ - x?: number - /** - * Vertical blur. - */ - y?: number -} - -export function blur(args: BlurArgs = {}) { - const x = getNumber(args.x, 2) - const stdDeviation = - args.y != null && Number.isFinite(args.y) ? [x, args.y] : x - - return ` - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/brightness.ts b/packages/x6-core/src/registry/filter/brightness.ts deleted file mode 100644 index a1f283e0710..00000000000 --- a/packages/x6-core/src/registry/filter/brightness.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { getNumber } from './util' - -export interface BrightnessArgs { - /** - * The proportion of the conversion. - * A value of `1` leaves the input unchanged. - * A value of `0` will create an image that is completely black. - * - * Default `1`. - */ - amount?: number -} - -export function brightness(args: BrightnessArgs = {}) { - const amount = getNumber(args.amount, 1) - return ` - - - - - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/contrast.ts b/packages/x6-core/src/registry/filter/contrast.ts deleted file mode 100644 index e9a307638cd..00000000000 --- a/packages/x6-core/src/registry/filter/contrast.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { getNumber } from './util' - -export interface ContrastArgs { - /** - * The proportion of the conversion. - * A value of `1` leaves the input unchanged. - * A value of `0` will create an image that is completely black. - * - * Default `1`. - */ - amount?: number -} - -export function contrast(args: ContrastArgs = {}) { - const amount = getNumber(args.amount, 1) - const amount2 = 0.5 - amount / 2 - - return ` - - - - - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/drop-shadow.ts b/packages/x6-core/src/registry/filter/drop-shadow.ts deleted file mode 100644 index de7398297bb..00000000000 --- a/packages/x6-core/src/registry/filter/drop-shadow.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { getString, getNumber } from './util' - -export interface DropShadowArgs { - dx?: number - dy?: number - color?: string - blur?: number - opacity?: number -} - -export function dropShadow(args: DropShadowArgs = {}) { - const dx = getNumber(args.dx, 0) - const dy = getNumber(args.dy, 0) - const color = getString(args.color, 'black') - const blur = getNumber(args.blur, 4) - const opacity = getNumber(args.opacity, 1) - - return 'SVGFEDropShadowElement' in window - ? ` - - `.trim() - : ` - - - - - - - - - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/gray-scale.ts b/packages/x6-core/src/registry/filter/gray-scale.ts deleted file mode 100644 index 16c90cb00d3..00000000000 --- a/packages/x6-core/src/registry/filter/gray-scale.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getNumber } from './util' - -export interface GrayScaleArgs { - /** - * The proportion of the conversion. - * A value of `1` is completely grayscale. - * A value of `0` leaves the input unchanged. - * - * Default `1`. - */ - amount?: number -} - -export function grayScale(args: GrayScaleArgs = {}) { - const amount = getNumber(args.amount, 1) - const a = 0.2126 + 0.7874 * (1 - amount) - const b = 0.7152 - 0.7152 * (1 - amount) - const c = 0.0722 - 0.0722 * (1 - amount) - const d = 0.2126 - 0.2126 * (1 - amount) - const e = 0.7152 + 0.2848 * (1 - amount) - const f = 0.0722 - 0.0722 * (1 - amount) - const g = 0.2126 - 0.2126 * (1 - amount) - const h = 0.0722 + 0.9278 * (1 - amount) - - return ` - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/highlight.ts b/packages/x6-core/src/registry/filter/highlight.ts deleted file mode 100644 index 631566c9bdc..00000000000 --- a/packages/x6-core/src/registry/filter/highlight.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { getString, getNumber } from './util' - -export interface HighlightArgs { - /** - * Highlight color. Default `'red'`. - */ - color?: string - /** - * Highlight blur. Default `0`. - */ - blur?: number - /** - * Highlight width. Default `1`. - */ - width?: number - /** - * Highlight opacity. Default `1`. - */ - opacity?: number -} - -export function highlight(args: HighlightArgs = {}) { - const color = getString(args.color, 'red') - const blur = getNumber(args.blur, 0) - const width = getNumber(args.width, 1) - const opacity = getNumber(args.opacity, 1) - - return ` - - - - - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/hue-rotate.ts b/packages/x6-core/src/registry/filter/hue-rotate.ts deleted file mode 100644 index c7b2392d02e..00000000000 --- a/packages/x6-core/src/registry/filter/hue-rotate.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { getNumber } from './util' - -export interface HueRotateArgs { - /** - * The number of degrees around the color. - * - * Default `0`. - */ - angle?: number -} - -export function hueRotate(args: HueRotateArgs = {}) { - const angle = getNumber(args.angle, 0) - return ` - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/index.ts b/packages/x6-core/src/registry/filter/index.ts deleted file mode 100644 index 6fbad5a147c..00000000000 --- a/packages/x6-core/src/registry/filter/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NonUndefined } from 'utility-types' -import { KeyValue, Registry } from '@antv/x6-common' -import * as filters from './main' - -export namespace Filter { - export type Definition = (args: T) => string - export type CommonDefinition = Definition -} - -export namespace Filter { - export type Presets = typeof Filter['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: NonUndefined[0]> - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace Filter { - export const presets = filters - export const registry = Registry.create({ - type: 'filter', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-core/src/registry/filter/invert.ts b/packages/x6-core/src/registry/filter/invert.ts deleted file mode 100644 index c72d0e4771e..00000000000 --- a/packages/x6-core/src/registry/filter/invert.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { getNumber } from './util' - -export interface InvertArgs { - /** - * The proportion of the conversion. - * A value of `1` is completely inverted. - * A value of `0` leaves the input unchanged. - * - * Default `1`. - */ - amount?: number -} - -export function invert(args: InvertArgs = {}) { - const amount = getNumber(args.amount, 1) - const amount2 = 1 - amount - return ` - - - - - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/main.ts b/packages/x6-core/src/registry/filter/main.ts deleted file mode 100644 index 432bd634d07..00000000000 --- a/packages/x6-core/src/registry/filter/main.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './outline' -export * from './highlight' -export * from './blur' -export * from './drop-shadow' -export * from './gray-scale' -export * from './sepia' -export * from './saturate' -export * from './hue-rotate' -export * from './invert' -export * from './brightness' -export * from './contrast' diff --git a/packages/x6-core/src/registry/filter/outline.ts b/packages/x6-core/src/registry/filter/outline.ts deleted file mode 100644 index e59cd17b323..00000000000 --- a/packages/x6-core/src/registry/filter/outline.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { getString, getNumber } from './util' - -export interface OutlineArgs { - /** - * Outline color. Default `'blue'`. - */ - color?: string - /** - * Outline width. Default `1` - */ - width?: number - /** - * Gap between outline and the element. Default `2` - */ - margin?: number - /** - * Outline opacity. Default `1` - */ - opacity?: number -} - -export function outline(args: OutlineArgs = {}) { - const color = getString(args.color, 'blue') - const width = getNumber(args.width, 1) - const margin = getNumber(args.margin, 2) - const opacity = getNumber(args.opacity, 1) - - const innerRadius = margin - const outerRadius = margin + width - - return ` - - - - - - - - - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/saturate.ts b/packages/x6-core/src/registry/filter/saturate.ts deleted file mode 100644 index 60ce4397aff..00000000000 --- a/packages/x6-core/src/registry/filter/saturate.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { getNumber } from './util' - -export interface SaturateArgs { - /** - * The proportion of the conversion. - * A value of `1` is completely un-saturated. - * A value of `0` leaves the input unchanged. - * - * Default `1`. - */ - amount?: number -} - -export function saturate(args: SaturateArgs = {}) { - const amount = getNumber(args.amount, 1) - return ` - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/sepia.ts b/packages/x6-core/src/registry/filter/sepia.ts deleted file mode 100644 index ef2f9554395..00000000000 --- a/packages/x6-core/src/registry/filter/sepia.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getNumber } from './util' - -export interface SepiaArgs { - /** - * The proportion of the conversion. - * A value of `1` is completely sepia. - * A value of `0` leaves the input unchanged. - * - * Default `1`. - */ - amount?: number -} -export function sepia(args: SepiaArgs = {}) { - const amount = getNumber(args.amount, 1) - const a = 0.393 + 0.607 * (1 - amount) - const b = 0.769 - 0.769 * (1 - amount) - const c = 0.189 - 0.189 * (1 - amount) - const d = 0.349 - 0.349 * (1 - amount) - const e = 0.686 + 0.314 * (1 - amount) - const f = 0.168 - 0.168 * (1 - amount) - const g = 0.272 - 0.272 * (1 - amount) - const h = 0.534 - 0.534 * (1 - amount) - const i = 0.131 + 0.869 * (1 - amount) - - return ` - - - - `.trim() -} diff --git a/packages/x6-core/src/registry/filter/util.ts b/packages/x6-core/src/registry/filter/util.ts deleted file mode 100644 index f55e32e76c9..00000000000 --- a/packages/x6-core/src/registry/filter/util.ts +++ /dev/null @@ -1,13 +0,0 @@ -export function getString( - value: string | null | undefined, - defaultValue: string, -) { - return value != null ? value : defaultValue -} - -export function getNumber( - num: number | null | undefined, - defaultValue: number, -) { - return num != null && Number.isFinite(num) ? num : defaultValue -} diff --git a/packages/x6-core/src/registry/highlighter/class.ts b/packages/x6-core/src/registry/highlighter/class.ts deleted file mode 100644 index 24b295e7afc..00000000000 --- a/packages/x6-core/src/registry/highlighter/class.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Config } from '../../common' -import { Highlighter } from './index' - -export interface ClassHighlighterOptions { - className?: string -} - -const defaultClassName = Config.prefix('highlighted') - -export const className: Highlighter.Definition = { - highlight(cellView, magnet, options) { - const cls = (options && options.className) || defaultClassName - Dom.addClass(magnet, cls) - }, - unhighlight(cellView, magnet, options) { - const cls = (options && options.className) || defaultClassName - Dom.removeClass(magnet, cls) - }, -} diff --git a/packages/x6-core/src/registry/highlighter/index.ts b/packages/x6-core/src/registry/highlighter/index.ts deleted file mode 100644 index 48a250fd5e0..00000000000 --- a/packages/x6-core/src/registry/highlighter/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Registry, KeyValue } from '@antv/x6-common' -import { CellView } from '../../view' -import * as highlighters from './main' - -export namespace Highlighter { - export interface Definition { - highlight: (cellView: CellView, magnet: Element, options: T) => void - unhighlight: (cellView: CellView, magnet: Element, options: T) => void - } - - export type CommonDefinition = Highlighter.Definition -} - -export namespace Highlighter { - export function check( - name: string, - highlighter: Highlighter.CommonDefinition, - ) { - if (typeof highlighter.highlight !== 'function') { - throw new Error( - `Highlighter '${name}' is missing required \`highlight()\` method`, - ) - } - - if (typeof highlighter.unhighlight !== 'function') { - throw new Error( - `Highlighter '${name}' is missing required \`unhighlight()\` method`, - ) - } - } -} - -export namespace Highlighter { - export type Presets = typeof Highlighter['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[2] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace Highlighter { - export const presets = highlighters - export const registry = Registry.create({ - type: 'highlighter', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-core/src/registry/highlighter/main.ts b/packages/x6-core/src/registry/highlighter/main.ts deleted file mode 100644 index eec2d1029e8..00000000000 --- a/packages/x6-core/src/registry/highlighter/main.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './class' -export * from './opacity' -export * from './stroke' diff --git a/packages/x6-core/src/registry/highlighter/opacity.ts b/packages/x6-core/src/registry/highlighter/opacity.ts deleted file mode 100644 index fdcb9629cc7..00000000000 --- a/packages/x6-core/src/registry/highlighter/opacity.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Config } from '../../common' -import { Highlighter } from './index' - -export interface OpacityHighlighterOptions {} - -const className = Config.prefix('highlight-opacity') - -export const opacity: Highlighter.Definition = { - highlight(cellView, magnet) { - Dom.addClass(magnet, className) - }, - - unhighlight(cellView, magnetEl) { - Dom.removeClass(magnetEl, className) - }, -} diff --git a/packages/x6-core/src/registry/highlighter/stroke.ts b/packages/x6-core/src/registry/highlighter/stroke.ts deleted file mode 100644 index 0753b3589eb..00000000000 --- a/packages/x6-core/src/registry/highlighter/stroke.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { ObjectExt, Dom, Vector } from '@antv/x6-common' -import { Attr } from '../attr' -import { Config } from '../../common' -import { EdgeView } from '../../view' -import { Highlighter } from './index' -import { Util } from '../../util' - -export interface StrokeHighlighterOptions { - padding?: number - rx?: number - ry?: number - attrs?: Attr.SimpleAttrs -} - -const defaultOptions: StrokeHighlighterOptions = { - padding: 3, - rx: 0, - ry: 0, - attrs: { - 'stroke-width': 3, - stroke: '#FEB663', - }, -} - -export const stroke: Highlighter.Definition = { - highlight(cellView, magnet, options) { - const id = Private.getHighlighterId(magnet, options) - if (Private.hasCache(id)) { - return - } - - // eslint-disable-next-line - options = ObjectExt.defaultsDeep({}, options, defaultOptions) - - const magnetVel = Vector.create(magnet as SVGElement) - let pathData - let magnetBBox - - try { - pathData = magnetVel.toPathData() - } catch (error) { - // Failed to get path data from magnet element. - // Draw a rectangle around the entire cell view instead. - magnetBBox = Util.bbox(magnetVel.node, true) - pathData = Dom.rectToPathData({ ...options, ...magnetBBox }) - } - - const path = Dom.createSvgElement('path') - Dom.attr(path, { - d: pathData, - 'pointer-events': 'none', - 'vector-effect': 'non-scaling-stroke', - fill: 'none', - ...(options.attrs ? Dom.kebablizeAttrs(options.attrs) : null), - }) - - // const highlightVel = v.create('path').attr() - - if (cellView.isEdgeElement(magnet)) { - Dom.attr(path, 'd', (cellView as EdgeView).getConnectionPathData()) - } else { - let highlightMatrix = magnetVel.getTransformToElement( - cellView.container as SVGElement, - ) - - // Add padding to the highlight element. - const padding = options.padding - if (padding) { - if (magnetBBox == null) { - magnetBBox = Util.bbox(magnetVel.node, true) - } - - const cx = magnetBBox.x + magnetBBox.width / 2 - const cy = magnetBBox.y + magnetBBox.height / 2 - - magnetBBox = Util.transformRectangle(magnetBBox, highlightMatrix) - - const width = Math.max(magnetBBox.width, 1) - const height = Math.max(magnetBBox.height, 1) - const sx = (width + padding) / width - const sy = (height + padding) / height - - const paddingMatrix = Dom.createSVGMatrix({ - a: sx, - b: 0, - c: 0, - d: sy, - e: cx - sx * cx, - f: cy - sy * cy, - }) - - highlightMatrix = highlightMatrix.multiply(paddingMatrix) - } - - Dom.transform(path, highlightMatrix) - } - - Dom.addClass(path, Config.prefix('highlight-stroke')) - - const cell = cellView.cell - const removeHandler = () => Private.removeHighlighter(id) - - cell.on('removed', removeHandler) - if (cell.model) { - cell.model.on('reseted', removeHandler) - } - - cellView.container.appendChild(path) - Private.setCache(id, path) - }, - - unhighlight(cellView, magnet, opt) { - Private.removeHighlighter(Private.getHighlighterId(magnet, opt)) - }, -} - -namespace Private { - export function getHighlighterId( - magnet: Element, - options: StrokeHighlighterOptions, - ) { - Dom.ensureId(magnet) - return magnet.id + JSON.stringify(options) - } - - const cache: { [id: string]: Element } = {} - - export function setCache(id: string, elem: Element) { - cache[id] = elem - } - - export function hasCache(id: string) { - return cache[id] != null - } - - export function removeHighlighter(id: string) { - const elem = cache[id] - if (elem) { - Dom.remove(elem) - delete cache[id] - } - } -} diff --git a/packages/x6-core/src/registry/index.ts b/packages/x6-core/src/registry/index.ts deleted file mode 100644 index fe705544559..00000000000 --- a/packages/x6-core/src/registry/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export * from './attr' -export * from './highlighter' -export * from './port-layout' -export * from './port-label-layout' -export * from './tool' -export * from './filter' - -// connection -export * from './marker' -export * from './node-anchor' -export * from './edge-anchor' -export * from './connection-point' -export * from './router' -export * from './connector' diff --git a/packages/x6-core/src/registry/marker/async.ts b/packages/x6-core/src/registry/marker/async.ts deleted file mode 100644 index 3f38cb77616..00000000000 --- a/packages/x6-core/src/registry/marker/async.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Path } from '@antv/x6-geometry' -import { KeyValue } from '@antv/x6-common' -import { normalize } from './util' -import { Marker } from './index' - -export interface AsyncMarkerOptions extends KeyValue { - width?: number - height?: number - offset?: number - open?: boolean - flip?: boolean -} - -export const async: Marker.Factory = ({ - width, - height, - offset, - open, - flip, - ...attrs -}) => { - let h = height || 6 - const w = width || 10 - const opened = open === true - const fliped = flip === true - const result: Marker.Result = { ...attrs, tagName: 'path' } - - if (fliped) { - h = -h - } - - const path = new Path() - - path.moveTo(0, h).lineTo(w, 0) - - if (!opened) { - path.lineTo(w, h) - path.close() - } else { - result.fill = 'none' - } - - result.d = normalize(path.serialize(), { - x: offset || -w / 2, - y: h / 2, - }) - - return result -} diff --git a/packages/x6-core/src/registry/marker/circle.ts b/packages/x6-core/src/registry/marker/circle.ts deleted file mode 100644 index 5ab7fa22ce1..00000000000 --- a/packages/x6-core/src/registry/marker/circle.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Path } from '@antv/x6-geometry' -import { Attr } from '../attr' -import { normalize } from './util' -import { Marker } from './index' - -export interface CircleMarkerOptions extends Attr.SimpleAttrs { - r?: number -} - -export interface CirclePlusMarkerOptions extends CircleMarkerOptions {} - -export const circle: Marker.Factory = ({ - r, - ...attrs -}) => { - const radius = r || 5 - return { - cx: radius, - ...attrs, - tagName: 'circle', - r: radius, - } -} - -export const circlePlus: Marker.Factory = ({ - r, - ...attrs -}) => { - const radius = r || 5 - const path = new Path() - - path.moveTo(radius, 0).lineTo(radius, radius * 2) - path.moveTo(0, radius).lineTo(radius * 2, radius) - - return { - children: [ - { - ...circle({ r: radius }), - fill: 'none', - }, - { - ...attrs, - tagName: 'path', - d: normalize(path.serialize(), -radius), - }, - ] as any, - } -} diff --git a/packages/x6-core/src/registry/marker/classic.ts b/packages/x6-core/src/registry/marker/classic.ts deleted file mode 100644 index 045ca2c6e3f..00000000000 --- a/packages/x6-core/src/registry/marker/classic.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Path } from '@antv/x6-geometry' -import { NumberExt, KeyValue } from '@antv/x6-common' -import { normalize } from './util' -import { Marker } from './index' - -interface Common { - size?: number - width?: number - height?: number - offset?: number -} - -export interface BlockMarkerOptions extends Common, KeyValue { - open?: boolean -} - -export interface ClassicMarkerOptions extends Common, KeyValue { - factor?: number -} - -export const block: Marker.Factory = ({ - size, - width, - height, - offset, - open, - ...attrs -}) => { - return createClassicMarker( - { size, width, height, offset }, - open === true, - true, - undefined, - attrs, - ) -} - -export const classic: Marker.Factory = ({ - size, - width, - height, - offset, - factor, - ...attrs -}) => { - return createClassicMarker( - { size, width, height, offset }, - false, - false, - factor, - attrs, - ) -} - -function createClassicMarker( - options: Common, - open: boolean, - full: boolean, - factor: number = 3 / 4, - attrs: KeyValue = {}, -) { - const size = options.size || 10 - const width = options.width || size - const height = options.height || size - const path = new Path() - const localAttrs: { fill?: string } = {} - - if (open) { - path - .moveTo(width, 0) - .lineTo(0, height / 2) - .lineTo(width, height) - localAttrs.fill = 'none' - } else { - path.moveTo(0, height / 2) - path.lineTo(width, 0) - - if (!full) { - const f = NumberExt.clamp(factor, 0, 1) - path.lineTo(width * f, height / 2) - } - - path.lineTo(width, height) - path.close() - } - - return { - ...localAttrs, - ...attrs, - tagName: 'path', - d: normalize(path.serialize(), { - x: options.offset != null ? options.offset : -width / 2, - }), - } -} diff --git a/packages/x6-core/src/registry/marker/cross.ts b/packages/x6-core/src/registry/marker/cross.ts deleted file mode 100644 index 4da6dc8a95a..00000000000 --- a/packages/x6-core/src/registry/marker/cross.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Path } from '@antv/x6-geometry' -import { Attr } from '../attr' -import { normalize } from './util' -import { Marker } from './index' - -export interface CrossMarkerOptions extends Attr.SimpleAttrs { - size?: number - width?: number - height?: number - offset?: number -} - -export const cross: Marker.Factory = ({ - size, - width, - height, - offset, - ...attrs -}) => { - const s = size || 10 - const w = width || s - const h = height || s - - const path = new Path() - path.moveTo(0, 0).lineTo(w, h).moveTo(0, h).lineTo(w, 0) - - return { - ...attrs, - tagName: 'path', - fill: 'none', - d: normalize(path.serialize(), offset || -w / 2), - } -} diff --git a/packages/x6-core/src/registry/marker/diamond.ts b/packages/x6-core/src/registry/marker/diamond.ts deleted file mode 100644 index 0077e8c5a5b..00000000000 --- a/packages/x6-core/src/registry/marker/diamond.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Path } from '@antv/x6-geometry' -import { Attr } from '../attr' -import { normalize } from './util' -import { Marker } from './index' - -export interface DiamondMarkerOptions extends Attr.SimpleAttrs { - size?: number - width?: number - height?: number - offset?: number -} - -export const diamond: Marker.Factory = ({ - size, - width, - height, - offset, - ...attrs -}) => { - const s = size || 10 - const w = width || s - const h = height || s - - const path = new Path() - path - .moveTo(0, h / 2) - .lineTo(w / 2, 0) - .lineTo(w, h / 2) - .lineTo(w / 2, h) - .close() - - return { - ...attrs, - tagName: 'path', - d: normalize(path.serialize(), offset == null ? -w / 2 : offset), - } -} diff --git a/packages/x6-core/src/registry/marker/ellipse.ts b/packages/x6-core/src/registry/marker/ellipse.ts deleted file mode 100644 index d367d536198..00000000000 --- a/packages/x6-core/src/registry/marker/ellipse.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Attr } from '../attr' -import { Marker } from './index' - -export interface EllipseMarkerOptions extends Attr.SimpleAttrs { - rx?: number - ry?: number -} - -export const ellipse: Marker.Factory = ({ - rx, - ry, - ...attrs -}) => { - const radiusX = rx || 5 - const radiusy = ry || 5 - return { - cx: radiusX, - ...attrs, - tagName: 'ellipse', - rx: radiusX, - ry: radiusy, - } -} diff --git a/packages/x6-core/src/registry/marker/index.ts b/packages/x6-core/src/registry/marker/index.ts deleted file mode 100644 index c3735becad4..00000000000 --- a/packages/x6-core/src/registry/marker/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Registry, KeyValue } from '@antv/x6-common' -import { Attr } from '../attr' -import * as markers from './main' -import { normalize as normalizeMarker } from './util' - -export namespace Marker { - export type Factory = (options: T) => Result - - export interface BaseResult extends Attr.SimpleAttrs { - tagName?: string - } - - export type Result = BaseResult & { - id?: string - refX?: number - refY?: number - // @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker - markerUnits?: string - markerOrient?: 'auto' | 'auto-start-reverse' | number - children?: BaseResult[] - } -} - -export namespace Marker { - export type Presets = typeof Marker['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[0] - } - - export type NativeNames = keyof OptionsMap - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace Marker { - export const presets = markers - export const registry = Registry.create({ - type: 'marker', - }) - registry.register(presets, true) -} - -export namespace Marker { - export const normalize = normalizeMarker -} diff --git a/packages/x6-core/src/registry/marker/main.ts b/packages/x6-core/src/registry/marker/main.ts deleted file mode 100644 index 3c91d12a0b4..00000000000 --- a/packages/x6-core/src/registry/marker/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './classic' -export * from './diamond' -export * from './path' -export * from './cross' -export * from './async' -export * from './circle' -export * from './ellipse' diff --git a/packages/x6-core/src/registry/marker/path.ts b/packages/x6-core/src/registry/marker/path.ts deleted file mode 100644 index 626b203d833..00000000000 --- a/packages/x6-core/src/registry/marker/path.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Attr } from '../attr' -import { normalize } from './util' -import { Marker } from './index' - -export interface PathMarkerOptions extends Attr.SimpleAttrs { - d: string - offsetX?: number - offsetY?: number -} - -export const path: Marker.Factory = ({ - d, - offsetX, - offsetY, - ...attrs -}) => { - return { - ...attrs, - tagName: 'path', - d: normalize(d, offsetX, offsetY), - } -} diff --git a/packages/x6-core/src/registry/marker/util.ts b/packages/x6-core/src/registry/marker/util.ts deleted file mode 100644 index 5146dabd7ae..00000000000 --- a/packages/x6-core/src/registry/marker/util.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Path } from '@antv/x6-geometry' - -/** - * Normalizes marker's path data by translate the center - * of an arbitrary path at <0 + offset,0>. - */ -export function normalize(d: string, offset: { x?: number; y?: number }): string -export function normalize(d: string, offsetX?: number, offsetY?: number): string -export function normalize( - d: string, - offset1?: number | { x?: number; y?: number }, - offset2?: number, -) { - let offsetX: number | undefined - let offsetY: number | undefined - if (typeof offset1 === 'object') { - offsetX = offset1.x - offsetY = offset1.y - } else { - offsetX = offset1 - offsetY = offset2 - } - - const path = Path.parse(d) - const bbox = path.bbox() - if (bbox) { - let ty = -bbox.height / 2 - bbox.y - let tx = -bbox.width / 2 - bbox.x - if (typeof offsetX === 'number') { - tx -= offsetX - } - if (typeof offsetY === 'number') { - ty -= offsetY - } - - path.translate(tx, ty) - } - - return path.serialize() -} diff --git a/packages/x6-core/src/registry/node-anchor/bbox.ts b/packages/x6-core/src/registry/node-anchor/bbox.ts deleted file mode 100644 index b344a7c3a09..00000000000 --- a/packages/x6-core/src/registry/node-anchor/bbox.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { NodeAnchor } from './index' - -export interface BBoxEndpointOptions { - dx?: number | string - dy?: number | string - /** - * Should the anchor bbox rotate with the terminal view. - * - * Default is `false`, meaning that the unrotated bbox is used. - */ - rotate?: boolean -} - -export const center = createBBoxAnchor('center') -export const top = createBBoxAnchor('topCenter') -export const bottom = createBBoxAnchor('bottomCenter') -export const left = createBBoxAnchor('leftMiddle') -export const right = createBBoxAnchor('rightMiddle') -export const topLeft = createBBoxAnchor('topLeft') -export const topRight = createBBoxAnchor('topRight') -export const bottomLeft = createBBoxAnchor('bottomLeft') -export const bottomRight = createBBoxAnchor('bottomRight') - -function createBBoxAnchor( - method: - | 'center' - | 'topCenter' - | 'bottomCenter' - | 'leftMiddle' - | 'rightMiddle' - | 'topLeft' - | 'topRight' - | 'bottomLeft' - | 'bottomRight', -): NodeAnchor.Definition { - return function (view, magnet, ref, options: BBoxEndpointOptions = {}) { - const bbox = options.rotate - ? view.getUnrotatedBBoxOfElement(magnet) - : view.getBBoxOfElement(magnet) - const result = bbox[method] - - result.x += NumberExt.normalizePercentage(options.dx, bbox.width) - result.y += NumberExt.normalizePercentage(options.dy, bbox.height) - - const cell = view.cell - return options.rotate - ? result.rotate(-cell.getAngle(), cell.getBBox().getCenter()) - : result - } -} diff --git a/packages/x6-core/src/registry/node-anchor/index.ts b/packages/x6-core/src/registry/node-anchor/index.ts deleted file mode 100644 index 6a60f69eb42..00000000000 --- a/packages/x6-core/src/registry/node-anchor/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { Edge } from '../../model' -import { EdgeView, NodeView } from '../../view' -import * as anchors from './main' - -export namespace NodeAnchor { - export type Definition = ( - this: EdgeView, - /** - * The NodeView to which we are connecting. - */ - nodeView: NodeView, - /** - * The SVGElement in our graph that contains the magnet - * (element/subelement/port) to which we are connecting. - */ - magnet: SVGElement, - /** - * A reference to another component of the edge path that may be - * necessary to find this anchor point. If we are calling this method - * for a source anchor, it is the first vertex, or if there are no - * vertices the target anchor. If we are calling this method for a target - * anchor, it is the last vertex, or if there are no vertices the source - * anchor... - */ - ref: Point | Point.PointLike | SVGElement, - args: T, - type: Edge.TerminalType, - ) => Point - - export type CommonDefinition = Definition - - export type ResolvedDefinition = ( - this: EdgeView, - view: NodeView, - magnet: SVGElement, - refPoint: Point, - args: T, - ) => Point -} - -export namespace NodeAnchor { - export type Presets = typeof NodeAnchor['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[3] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace NodeAnchor { - export const presets = anchors - export const registry = Registry.create({ - type: 'node endpoint', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-core/src/registry/node-anchor/main.ts b/packages/x6-core/src/registry/node-anchor/main.ts deleted file mode 100644 index 2ecd6814879..00000000000 --- a/packages/x6-core/src/registry/node-anchor/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './bbox' -export * from './orth' -export * from './node-center' -export * from './middle-side' diff --git a/packages/x6-core/src/registry/node-anchor/middle-side.ts b/packages/x6-core/src/registry/node-anchor/middle-side.ts deleted file mode 100644 index 02e4bf74f78..00000000000 --- a/packages/x6-core/src/registry/node-anchor/middle-side.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { ResolveOptions, resolve } from './util' -import { NodeAnchor } from './index' - -export interface MiddleSideEndpointOptions extends ResolveOptions { - rotate?: boolean - padding?: number - direction?: 'H' | 'V' -} - -const middleSide: NodeAnchor.ResolvedDefinition = - function (view, magnet, refPoint, options) { - let bbox - let angle = 0 - let center - - const node = view.cell - if (options.rotate) { - bbox = view.getUnrotatedBBoxOfElement(magnet) - center = node.getBBox().getCenter() - angle = node.getAngle() - } else { - bbox = view.getBBoxOfElement(magnet) - } - - const padding = options.padding - if (padding != null && Number.isFinite(padding)) { - bbox.inflate(padding) - } - - if (options.rotate) { - refPoint.rotate(angle, center) - } - - const side = bbox.getNearestSideToPoint(refPoint) - let result: Point - switch (side) { - case 'left': - result = bbox.getLeftMiddle() - break - case 'right': - result = bbox.getRightMiddle() - break - case 'top': - result = bbox.getTopCenter() - break - case 'bottom': - result = bbox.getBottomCenter() - break - default: - break - } - - const direction = options.direction - if (direction === 'H') { - if (side === 'top' || side === 'bottom') { - if (refPoint.x <= bbox.x + bbox.width) { - result = bbox.getLeftMiddle() - } else { - result = bbox.getRightMiddle() - } - } - } else if (direction === 'V') { - if (refPoint.y <= bbox.y + bbox.height) { - result = bbox.getTopCenter() - } else { - result = bbox.getBottomCenter() - } - } - - return options.rotate ? result!.rotate(-angle, center) : result! - } - -/** - * Places the anchor of the edge in the middle of the side of view bbox - * closest to the other endpoint. - */ -export const midSide = resolve< - NodeAnchor.ResolvedDefinition, - NodeAnchor.Definition ->(middleSide) diff --git a/packages/x6-core/src/registry/node-anchor/node-center.ts b/packages/x6-core/src/registry/node-anchor/node-center.ts deleted file mode 100644 index 18adde73515..00000000000 --- a/packages/x6-core/src/registry/node-anchor/node-center.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NodeAnchor } from './index' - -export interface NodeCenterEndpointOptions { - dx?: number - dy?: number -} - -/** - * Places the anchor of the edge at center of the node bbox. - */ -export const nodeCenter: NodeAnchor.Definition = - function (view, magnet, ref, options, endType) { - const result = view.cell.getConnectionPoint(this.cell, endType) - if (options.dx || options.dy) { - result.translate(options.dx || 0, options.dy || 0) - } - return result - } diff --git a/packages/x6-core/src/registry/node-anchor/orth.ts b/packages/x6-core/src/registry/node-anchor/orth.ts deleted file mode 100644 index 37bd9cecd81..00000000000 --- a/packages/x6-core/src/registry/node-anchor/orth.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Angle } from '@antv/x6-geometry' -import { ResolveOptions, resolve } from './util' -import { NodeAnchor } from './index' - -export interface OrthEndpointOptions extends ResolveOptions { - padding: number -} - -const orthogonal: NodeAnchor.ResolvedDefinition = - function (view, magnet, refPoint, options) { - const angle = view.cell.getAngle() - const bbox = view.getBBoxOfElement(magnet) - const result = bbox.getCenter() - const topLeft = bbox.getTopLeft() - const bottomRight = bbox.getBottomRight() - - let padding = options.padding - if (!Number.isFinite(padding)) { - padding = 0 - } - - if ( - topLeft.y + padding <= refPoint.y && - refPoint.y <= bottomRight.y - padding - ) { - const dy = refPoint.y - result.y - result.x += - angle === 0 || angle === 180 - ? 0 - : (dy * 1) / Math.tan(Angle.toRad(angle)) - result.y += dy - } else if ( - topLeft.x + padding <= refPoint.x && - refPoint.x <= bottomRight.x - padding - ) { - const dx = refPoint.x - result.x - result.y += - angle === 90 || angle === 270 ? 0 : dx * Math.tan(Angle.toRad(angle)) - result.x += dx - } - - return result - } - -/** - * Tries to place the anchor of the edge inside the view bbox so that the - * edge is made orthogonal. The anchor is placed along two line segments - * inside the view bbox (between the centers of the top and bottom side and - * between the centers of the left and right sides). If it is not possible - * to place the anchor so that the edge would be orthogonal, the anchor is - * placed at the center of the view bbox instead. - */ -export const orth = resolve< - NodeAnchor.ResolvedDefinition, - NodeAnchor.Definition ->(orthogonal) diff --git a/packages/x6-core/src/registry/node-anchor/util.ts b/packages/x6-core/src/registry/node-anchor/util.ts deleted file mode 100644 index 02b85ebb147..00000000000 --- a/packages/x6-core/src/registry/node-anchor/util.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { Point } from '@antv/x6-geometry' -import { EdgeView } from '../../view' - -export interface ResolveOptions { - fixedAt?: number | string -} - -// eslint-disable-next-line -export function resolve(fn: S): T { - return function ( - this: EdgeView, - view: EdgeView, - magnet: SVGElement, - ref: any, - options: ResolveOptions, - ) { - if (ref instanceof Element) { - const refView = this.renderer.findViewByElem(ref) - let refPoint - if (refView) { - if (refView.isEdgeElement(ref)) { - const distance = options.fixedAt != null ? options.fixedAt : '50%' - refPoint = getPointAtEdge(refView as EdgeView, distance) - } else { - refPoint = refView.getBBoxOfElement(ref).getCenter() - } - } else { - refPoint = new Point() - } - return fn.call(this, view, magnet, refPoint, options) - } - return fn.apply(this, arguments) // eslint-disable-line - } as any as T -} - -export function getPointAtEdge(edgeView: EdgeView, value: string | number) { - const isPercentage = NumberExt.isPercentage(value) - const num = typeof value === 'string' ? parseFloat(value) : value - if (isPercentage) { - return edgeView.getPointAtRatio(num / 100) - } - return edgeView.getPointAtLength(num) -} diff --git a/packages/x6-core/src/registry/port-label-layout/index.ts b/packages/x6-core/src/registry/port-label-layout/index.ts deleted file mode 100644 index c43230e5e9e..00000000000 --- a/packages/x6-core/src/registry/port-label-layout/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Point, Rectangle } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { Attr } from '../attr' -import * as layouts from './main' - -export namespace PortLabelLayout { - export interface Result { - position: Point.PointLike - angle: number - attrs: Attr.CellAttrs - } - - export type Definition = ( - portPosition: Point, - elemBBox: Rectangle, - args: T, - ) => Result - - export type CommonDefinition = Definition - - export interface CommonOptions { - x?: number - y?: number - angle?: number - attrs?: Attr.CellAttrs - } -} - -export namespace PortLabelLayout { - export type Presets = typeof PortLabelLayout['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[2] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace PortLabelLayout { - export const presets = layouts - export const registry = Registry.create({ - type: 'port label layout', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-core/src/registry/port-label-layout/inout.ts b/packages/x6-core/src/registry/port-label-layout/inout.ts deleted file mode 100644 index 866f8757e53..00000000000 --- a/packages/x6-core/src/registry/port-label-layout/inout.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { Point, Rectangle } from '@antv/x6-geometry' -import { PortLabelLayout } from './index' -import { toResult } from './util' - -export interface InOutArgs extends PortLabelLayout.CommonOptions { - offset?: number -} - -export const outside: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => outsideLayout(portPosition, elemBBox, false, args) - -export const outsideOriented: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => outsideLayout(portPosition, elemBBox, true, args) - -export const inside: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => insideLayout(portPosition, elemBBox, false, args) - -export const insideOriented: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => insideLayout(portPosition, elemBBox, true, args) - -function outsideLayout( - portPosition: Point, - elemBBox: Rectangle, - autoOrient: boolean, - args: InOutArgs, -) { - const offset = args.offset != null ? args.offset : 15 - const angle = elemBBox.getCenter().theta(portPosition) - const bboxAngles = getBBoxAngles(elemBBox) - - let y - let tx - let ty - let textAnchor - let orientAngle = 0 - - if (angle < bboxAngles[1] || angle > bboxAngles[2]) { - y = '.3em' - tx = offset - ty = 0 - textAnchor = 'start' - } else if (angle < bboxAngles[0]) { - y = '0' - tx = 0 - ty = -offset - if (autoOrient) { - orientAngle = -90 - textAnchor = 'start' - } else { - textAnchor = 'middle' - } - } else if (angle < bboxAngles[3]) { - y = '.3em' - tx = -offset - ty = 0 - textAnchor = 'end' - } else { - y = '.6em' - tx = 0 - ty = offset - if (autoOrient) { - orientAngle = 90 - textAnchor = 'start' - } else { - textAnchor = 'middle' - } - } - - return toResult( - { - position: { - x: Math.round(tx), - y: Math.round(ty), - }, - angle: orientAngle, - attrs: { - '.': { - y, - 'text-anchor': textAnchor, - }, - }, - }, - args, - ) -} - -function insideLayout( - portPosition: Point, - elemBBox: Rectangle, - autoOrient: boolean, - args: InOutArgs, -) { - const offset = args.offset != null ? args.offset : 15 - const angle = elemBBox.getCenter().theta(portPosition) - const bboxAngles = getBBoxAngles(elemBBox) - - let y - let tx - let ty - let textAnchor - let orientAngle = 0 - - if (angle < bboxAngles[1] || angle > bboxAngles[2]) { - y = '.3em' - tx = -offset - ty = 0 - textAnchor = 'end' - } else if (angle < bboxAngles[0]) { - y = '.6em' - tx = 0 - ty = offset - if (autoOrient) { - orientAngle = 90 - textAnchor = 'start' - } else { - textAnchor = 'middle' - } - } else if (angle < bboxAngles[3]) { - y = '.3em' - tx = offset - ty = 0 - textAnchor = 'start' - } else { - y = '0em' - tx = 0 - ty = -offset - if (autoOrient) { - orientAngle = -90 - textAnchor = 'start' - } else { - textAnchor = 'middle' - } - } - - return toResult( - { - position: { - x: Math.round(tx), - y: Math.round(ty), - }, - angle: orientAngle, - attrs: { - '.': { - y, - 'text-anchor': textAnchor, - }, - }, - }, - args, - ) -} - -function getBBoxAngles(elemBBox: Rectangle) { - const center = elemBBox.getCenter() - - const tl = center.theta(elemBBox.getTopLeft()) - const bl = center.theta(elemBBox.getBottomLeft()) - const br = center.theta(elemBBox.getBottomRight()) - const tr = center.theta(elemBBox.getTopRight()) - - return [tl, tr, br, bl] -} diff --git a/packages/x6-core/src/registry/port-label-layout/main.ts b/packages/x6-core/src/registry/port-label-layout/main.ts deleted file mode 100644 index dc587ffe371..00000000000 --- a/packages/x6-core/src/registry/port-label-layout/main.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './side' -export * from './inout' -export * from './radial' diff --git a/packages/x6-core/src/registry/port-label-layout/radial.ts b/packages/x6-core/src/registry/port-label-layout/radial.ts deleted file mode 100644 index 9d1557763dd..00000000000 --- a/packages/x6-core/src/registry/port-label-layout/radial.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { PortLabelLayout } from './index' -import { toResult } from './util' - -export interface RadialArgs extends PortLabelLayout.CommonOptions { - offset?: number -} - -export const radial: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => radialLayout(portPosition.diff(elemBBox.getCenter()), false, args) - -export const radialOriented: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => radialLayout(portPosition.diff(elemBBox.getCenter()), true, args) - -function radialLayout( - portCenterOffset: Point, - autoOrient: boolean, - args: RadialArgs, -) { - const offset = args.offset != null ? args.offset : 20 - const origin = new Point(0, 0) - const angle = -portCenterOffset.theta(origin) - const pos = portCenterOffset - .clone() - .move(origin, offset) - .diff(portCenterOffset) - .round() - - let y = '.3em' - let textAnchor - let orientAngle = angle - - if ((angle + 90) % 180 === 0) { - textAnchor = autoOrient ? 'end' : 'middle' - if (!autoOrient && angle === -270) { - y = '0em' - } - } else if (angle > -270 && angle < -90) { - textAnchor = 'start' - orientAngle = angle - 180 - } else { - textAnchor = 'end' - } - - return toResult( - { - position: pos.round().toJSON(), - angle: autoOrient ? orientAngle : 0, - attrs: { - '.': { - y, - 'text-anchor': textAnchor, - }, - }, - }, - args, - ) -} diff --git a/packages/x6-core/src/registry/port-label-layout/side.ts b/packages/x6-core/src/registry/port-label-layout/side.ts deleted file mode 100644 index 8d5a583b581..00000000000 --- a/packages/x6-core/src/registry/port-label-layout/side.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { PortLabelLayout } from './index' -import { toResult } from './util' - -export interface SideArgs extends PortLabelLayout.CommonOptions {} - -export const manual: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => toResult({ position: elemBBox.getTopLeft() }, args) - -export const left: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => - toResult( - { - position: { x: -15, y: 0 }, - attrs: { '.': { y: '.3em', 'text-anchor': 'end' } }, - }, - args, - ) - -export const right: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => - toResult( - { - position: { x: 15, y: 0 }, - attrs: { '.': { y: '.3em', 'text-anchor': 'start' } }, - }, - args, - ) - -export const top: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => - toResult( - { - position: { x: 0, y: -15 }, - attrs: { '.': { 'text-anchor': 'middle' } }, - }, - args, - ) - -export const bottom: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => - toResult( - { - position: { x: 0, y: 15 }, - attrs: { '.': { y: '.6em', 'text-anchor': 'middle' } }, - }, - args, - ) diff --git a/packages/x6-core/src/registry/port-label-layout/util.ts b/packages/x6-core/src/registry/port-label-layout/util.ts deleted file mode 100644 index d5b5a0666d7..00000000000 --- a/packages/x6-core/src/registry/port-label-layout/util.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ObjectExt } from '@antv/x6-common' -import { PortLabelLayout } from './index' - -const defaults: PortLabelLayout.Result = { - position: { x: 0, y: 0 }, - angle: 0, - attrs: { - '.': { - y: '0', - 'text-anchor': 'start', - }, - }, -} - -export function toResult( - preset: Partial, - args?: Partial, -): PortLabelLayout.Result { - const { x, y, angle, attrs } = args || {} - return ObjectExt.defaultsDeep( - {}, - { angle, attrs, position: { x, y } }, - preset, - defaults, - ) -} diff --git a/packages/x6-core/src/registry/port-layout/absolute.ts b/packages/x6-core/src/registry/port-layout/absolute.ts deleted file mode 100644 index 00decc7f385..00000000000 --- a/packages/x6-core/src/registry/port-layout/absolute.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PortLayout } from './index' -import { normalizePoint, toResult } from './util' - -export interface AbsoluteArgs { - x?: string | number - y?: string | number - angle?: number -} - -export const absolute: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, -) => { - return portsPositionArgs.map(({ x, y, angle }) => - toResult(normalizePoint(elemBBox, { x, y }), angle || 0), - ) -} diff --git a/packages/x6-core/src/registry/port-layout/ellipse.ts b/packages/x6-core/src/registry/port-layout/ellipse.ts deleted file mode 100644 index 7a66b6203ae..00000000000 --- a/packages/x6-core/src/registry/port-layout/ellipse.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Rectangle, Ellipse } from '@antv/x6-geometry' -import { PortLayout } from './index' -import { toResult } from './util' - -export interface EllipseArgs extends PortLayout.CommonArgs { - start?: number - step?: number - compensateRotate?: boolean - /** - * delta radius - */ - dr?: number -} - -export const ellipse: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - const startAngle = groupPositionArgs.start || 0 - const stepAngle = groupPositionArgs.step || 20 - - return ellipseLayout( - portsPositionArgs, - elemBBox, - startAngle, - (index, count) => (index + 0.5 - count / 2) * stepAngle, - ) -} - -export const ellipseSpread: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - const startAngle = groupPositionArgs.start || 0 - const stepAngle = groupPositionArgs.step || 360 / portsPositionArgs.length - - return ellipseLayout(portsPositionArgs, elemBBox, startAngle, (index) => { - return index * stepAngle - }) -} - -function ellipseLayout( - portsPositionArgs: EllipseArgs[], - elemBBox: Rectangle, - startAngle: number, - stepFn: (index: number, count: number) => number, -) { - const center = elemBBox.getCenter() - const start = elemBBox.getTopCenter() - const ratio = elemBBox.width / elemBBox.height - const ellipse = Ellipse.fromRect(elemBBox) - const count = portsPositionArgs.length - - return portsPositionArgs.map((item, index) => { - const angle = startAngle + stepFn(index, count) - const p = start.clone().rotate(-angle, center).scale(ratio, 1, center) - - const theta = item.compensateRotate ? -ellipse.tangentTheta(p) : 0 - - if (item.dx || item.dy) { - p.translate(item.dx || 0, item.dy || 0) - } - - if (item.dr) { - p.move(center, item.dr) - } - - return toResult(p.round(), theta, item) - }) -} diff --git a/packages/x6-core/src/registry/port-layout/index.ts b/packages/x6-core/src/registry/port-layout/index.ts deleted file mode 100644 index 80a70b5ec09..00000000000 --- a/packages/x6-core/src/registry/port-layout/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Rectangle, Point } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import * as layouts from './main' - -export namespace PortLayout { - export const presets = layouts - export const registry = Registry.create({ - type: 'port layout', - }) - - registry.register(presets, true) -} - -export namespace PortLayout { - export interface Result { - position: Point.PointLike - angle?: number - } - - export interface CommonArgs { - x?: number - y?: number - dx?: number - dy?: number - } - - export type Definition = ( - portsPositionArgs: T[], - elemBBox: Rectangle, - groupPositionArgs: T, - ) => Result[] - - export type CommonDefinition = Definition -} - -export namespace PortLayout { - export type Presets = typeof PortLayout['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[2] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: CommonArgs - } -} diff --git a/packages/x6-core/src/registry/port-layout/line.ts b/packages/x6-core/src/registry/port-layout/line.ts deleted file mode 100644 index aaaedd05e8b..00000000000 --- a/packages/x6-core/src/registry/port-layout/line.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Point, Line } from '@antv/x6-geometry' -import { normalizePoint, toResult } from './util' -import { PortLayout } from './index' - -export interface SideArgs extends PortLayout.CommonArgs { - strict?: boolean -} - -export interface LineArgs extends SideArgs { - start?: Point.PointLike - end?: Point.PointLike -} - -export const line: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - const start = normalizePoint( - elemBBox, - groupPositionArgs.start || elemBBox.getOrigin(), - ) - const end = normalizePoint( - elemBBox, - groupPositionArgs.end || elemBBox.getCorner(), - ) - - return lineLayout(portsPositionArgs, start, end, groupPositionArgs) -} - -export const left: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - return lineLayout( - portsPositionArgs, - elemBBox.getTopLeft(), - elemBBox.getBottomLeft(), - groupPositionArgs, - ) -} - -export const right: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - return lineLayout( - portsPositionArgs, - elemBBox.getTopRight(), - elemBBox.getBottomRight(), - groupPositionArgs, - ) -} - -export const top: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - return lineLayout( - portsPositionArgs, - elemBBox.getTopLeft(), - elemBBox.getTopRight(), - groupPositionArgs, - ) -} - -export const bottom: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - return lineLayout( - portsPositionArgs, - elemBBox.getBottomLeft(), - elemBBox.getBottomRight(), - groupPositionArgs, - ) -} - -function lineLayout( - portsPositionArgs: SideArgs[], - p1: Point, - p2: Point, - groupPositionArgs: SideArgs, -) { - const line = new Line(p1, p2) - const length = portsPositionArgs.length - return portsPositionArgs.map(({ strict, ...offset }, index) => { - const ratio = - strict || groupPositionArgs.strict - ? (index + 1) / (length + 1) - : (index + 0.5) / length - - const p = line.pointAt(ratio) - if (offset.dx || offset.dy) { - p.translate(offset.dx || 0, offset.dy || 0) - } - - return toResult(p.round(), 0, offset) - }) -} diff --git a/packages/x6-core/src/registry/port-layout/main.ts b/packages/x6-core/src/registry/port-layout/main.ts deleted file mode 100644 index 82e79b305b7..00000000000 --- a/packages/x6-core/src/registry/port-layout/main.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './absolute' -export * from './ellipse' -export * from './line' diff --git a/packages/x6-core/src/registry/port-layout/util.ts b/packages/x6-core/src/registry/port-layout/util.ts deleted file mode 100644 index a51d50f3f1f..00000000000 --- a/packages/x6-core/src/registry/port-layout/util.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { PortLayout } from './index' - -export function normalizePoint( - bbox: Rectangle, - args: { - x?: string | number - y?: string | number - } = {}, -) { - return new Point( - NumberExt.normalizePercentage(args.x, bbox.width), - NumberExt.normalizePercentage(args.y, bbox.height), - ) -} - -export function toResult( - point: Point, - angle?: number, - rawArgs?: T, -): PortLayout.Result { - return { - angle, - position: point.toJSON(), - ...rawArgs, - } -} diff --git a/packages/x6-core/src/registry/router/er.ts b/packages/x6-core/src/registry/router/er.ts deleted file mode 100644 index 24e402313df..00000000000 --- a/packages/x6-core/src/registry/router/er.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { Router } from './index' - -export interface ErRouterOptions { - min?: number - offset?: number | 'center' - direction?: 'T' | 'B' | 'L' | 'R' | 'H' | 'V' -} - -export const er: Router.Definition = function ( - vertices, - options, - edgeView, -) { - const offsetRaw = options.offset || 32 - const min = options.min == null ? 16 : options.min - - let offset = 0 - let direction = options.direction - - const sourceBBox = edgeView.sourceBBox - const targetBBox = edgeView.targetBBox - const sourcePoint = sourceBBox.getCenter() - const targetPoint = targetBBox.getCenter() - - if (typeof offsetRaw === 'number') { - offset = offsetRaw - } - - if (direction == null) { - let dx = targetBBox.left - sourceBBox.right - let dy = targetBBox.top - sourceBBox.bottom - - if (dx >= 0 && dy >= 0) { - direction = dx >= dy ? 'L' : 'T' - } else if (dx <= 0 && dy >= 0) { - dx = sourceBBox.left - targetBBox.right - if (dx >= 0) { - direction = dx >= dy ? 'R' : 'T' - } else { - direction = 'T' - } - } else if (dx >= 0 && dy <= 0) { - dy = sourceBBox.top - targetBBox.bottom - if (dy >= 0) { - direction = dx >= dy ? 'L' : 'B' - } else { - direction = 'L' - } - } else { - dx = sourceBBox.left - targetBBox.right - dy = sourceBBox.top - targetBBox.bottom - if (dx >= 0 && dy >= 0) { - direction = dx >= dy ? 'R' : 'B' - } else if (dx <= 0 && dy >= 0) { - direction = 'B' - } else if (dx >= 0 && dy <= 0) { - direction = 'R' - } else { - direction = Math.abs(dx) > Math.abs(dy) ? 'R' : 'B' - } - } - } - - if (direction === 'H') { - direction = targetPoint.x - sourcePoint.x >= 0 ? 'L' : 'R' - } else if (direction === 'V') { - direction = targetPoint.y - sourcePoint.y >= 0 ? 'T' : 'B' - } - - if (offsetRaw === 'center') { - if (direction === 'L') { - offset = (targetBBox.left - sourceBBox.right) / 2 - } else if (direction === 'R') { - offset = (sourceBBox.left - targetBBox.right) / 2 - } else if (direction === 'T') { - offset = (targetBBox.top - sourceBBox.bottom) / 2 - } else if (direction === 'B') { - offset = (sourceBBox.top - targetBBox.bottom) / 2 - } - } - - let coord: 'x' | 'y' - let dim: 'width' | 'height' - let factor - const horizontal = direction === 'L' || direction === 'R' - - if (horizontal) { - if (targetPoint.y === sourcePoint.y) { - return [...vertices] - } - - factor = direction === 'L' ? 1 : -1 - coord = 'x' - dim = 'width' - } else { - if (targetPoint.x === sourcePoint.x) { - return [...vertices] - } - - factor = direction === 'T' ? 1 : -1 - coord = 'y' - dim = 'height' - } - - const source = sourcePoint.clone() - const target = targetPoint.clone() - - source[coord] += factor * (sourceBBox[dim] / 2 + offset) - target[coord] -= factor * (targetBBox[dim] / 2 + offset) - - if (horizontal) { - const sourceX = source.x - const targetX = target.x - const sourceDelta = sourceBBox.width / 2 + min - const targetDelta = targetBBox.width / 2 + min - if (targetPoint.x > sourcePoint.x) { - if (targetX <= sourceX) { - source.x = Math.max(targetX, sourcePoint.x + sourceDelta) - target.x = Math.min(sourceX, targetPoint.x - targetDelta) - } - } else if (targetX >= sourceX) { - source.x = Math.min(targetX, sourcePoint.x - sourceDelta) - target.x = Math.max(sourceX, targetPoint.x + targetDelta) - } - } else { - const sourceY = source.y - const targetY = target.y - const sourceDelta = sourceBBox.height / 2 + min - const targetDelta = targetBBox.height / 2 + min - if (targetPoint.y > sourcePoint.y) { - if (targetY <= sourceY) { - source.y = Math.max(targetY, sourcePoint.y + sourceDelta) - target.y = Math.min(sourceY, targetPoint.y - targetDelta) - } - } else if (targetY >= sourceY) { - source.y = Math.min(targetY, sourcePoint.y - sourceDelta) - target.y = Math.max(sourceY, targetPoint.y + targetDelta) - } - } - - return [source.toJSON(), ...vertices, target.toJSON()] -} diff --git a/packages/x6-core/src/registry/router/index.ts b/packages/x6-core/src/registry/router/index.ts deleted file mode 100644 index 4b5956092f1..00000000000 --- a/packages/x6-core/src/registry/router/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { EdgeView } from '../../view' -import * as routers from './main' - -export namespace Router { - export type Definition = ( - this: EdgeView, - vertices: Point.PointLike[], - options: T, - edgeView: EdgeView, - ) => Point.PointLike[] - export type CommonDefinition = Definition -} - -export namespace Router { - export type Presets = typeof Router['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[1] - } - - export type NativeNames = keyof OptionsMap - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace Router { - export const presets = routers - export const registry = Registry.create({ - type: 'router', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-core/src/registry/router/loop.ts b/packages/x6-core/src/registry/router/loop.ts deleted file mode 100644 index 0c4dc41ca6a..00000000000 --- a/packages/x6-core/src/registry/router/loop.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { Angle, Point, Line } from '@antv/x6-geometry' -import { Router } from './index' - -export interface LoopRouterOptions { - width?: number - height?: number - angle?: 'auto' | number - merge?: boolean | number -} - -function rollup(points: Point.PointLike[], merge?: boolean | number) { - if (merge != null && merge !== false) { - const amount = typeof merge === 'boolean' ? 0 : merge - if (amount > 0) { - const center1 = Point.create(points[1]).move(points[2], amount) - const center2 = Point.create(points[1]).move(points[0], amount) - return [center1.toJSON(), ...points, center2.toJSON()] - } - { - const center = points[1] - return [{ ...center }, ...points, { ...center }] - } - } - return points -} - -export const loop: Router.Definition = function ( - vertices, - options, - edgeView, -) { - const width = options.width || 50 - const height = options.height || 80 - const halfHeight = height / 2 - const angle = options.angle || 'auto' - - const sourceAnchor = edgeView.sourceAnchor - const targetAnchor = edgeView.targetAnchor - const sourceBBox = edgeView.sourceBBox - const targetBBox = edgeView.targetBBox - - if (sourceAnchor.equals(targetAnchor)) { - const getVertices = (angle: number) => { - const rad = Angle.toRad(angle) - const sin = Math.sin(rad) - const cos = Math.cos(rad) - - const center = new Point( - sourceAnchor.x + cos * width, - sourceAnchor.y + sin * width, - ) - const ref = new Point( - center.x - cos * halfHeight, - center.y - sin * halfHeight, - ) - const p1 = ref.clone().rotate(-90, center) - const p2 = ref.clone().rotate(90, center) - - return [p1.toJSON(), center.toJSON(), p2.toJSON()] - } - - const validate = (end: Point.PointLike) => { - const start = sourceAnchor.clone().move(end, -1) - const line = new Line(start, end) - return ( - !sourceBBox.containsPoint(end) && !sourceBBox.intersectsWithLine(line) - ) - } - - const angles = [0, 90, 180, 270, 45, 135, 225, 315] - - if (typeof angle === 'number') { - return rollup(getVertices(angle), options.merge) - } - - const center = sourceBBox.getCenter() - if (center.equals(sourceAnchor)) { - return rollup(getVertices(0), options.merge) - } - - const deg = center.angleBetween( - sourceAnchor, - center.clone().translate(1, 0), - ) - let ret = getVertices(deg) - if (validate(ret[1])) { - return rollup(ret, options.merge) - } - - // return the best vertices - for (let i = 1, l = angles.length; i < l; i += 1) { - ret = getVertices(deg + angles[i]) - if (validate(ret[1])) { - return rollup(ret, options.merge) - } - } - return rollup(ret, options.merge) - } - { - const line = new Line(sourceAnchor, targetAnchor) - let parallel = line.parallel(-width) - let center = parallel.getCenter() - let p1 = parallel.start.clone().move(parallel.end, halfHeight) - let p2 = parallel.end.clone().move(parallel.start, halfHeight) - - const ref = line.parallel(-1) - const line1 = new Line(ref.start, center) - const line2 = new Line(ref.end, center) - - if ( - sourceBBox.containsPoint(center) || - targetBBox.containsPoint(center) || - sourceBBox.intersectsWithLine(line1) || - sourceBBox.intersectsWithLine(line2) || - targetBBox.intersectsWithLine(line1) || - targetBBox.intersectsWithLine(line2) - ) { - parallel = line.parallel(width) - center = parallel.getCenter() - p1 = parallel.start.clone().move(parallel.end, halfHeight) - p2 = parallel.end.clone().move(parallel.start, halfHeight) - } - - if (options.merge) { - const line = new Line(sourceAnchor, targetAnchor) - const normal = new Line(center, line.center).setLength( - Number.MAX_SAFE_INTEGER, - ) - const intersects1 = sourceBBox.intersectsWithLine(normal) - const intersects2 = targetBBox.intersectsWithLine(normal) - const intersects = intersects1 - ? Array.isArray(intersects1) - ? intersects1 - : [intersects1] - : [] - if (intersects2) { - if (Array.isArray(intersects2)) { - intersects.push(...intersects2) - } else { - intersects.push(intersects2) - } - } - const anchor = line.center.closest(intersects) - if (anchor) { - edgeView.sourceAnchor = anchor.clone() - edgeView.targetAnchor = anchor.clone() - } else { - edgeView.sourceAnchor = line.center.clone() - edgeView.targetAnchor = line.center.clone() - } - } - - return rollup([p1.toJSON(), center.toJSON(), p2.toJSON()], options.merge) - } -} diff --git a/packages/x6-core/src/registry/router/main.ts b/packages/x6-core/src/registry/router/main.ts deleted file mode 100644 index 23f81869bb3..00000000000 --- a/packages/x6-core/src/registry/router/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './normal' -export * from './oneside' -export * from './orth' -export * from './metro' -export * from './manhattan/index' -export * from './er' -export * from './loop' diff --git a/packages/x6-core/src/registry/router/manhattan/index.ts b/packages/x6-core/src/registry/router/manhattan/index.ts deleted file mode 100644 index 6641e7b47a4..00000000000 --- a/packages/x6-core/src/registry/router/manhattan/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { FunctionExt } from '@antv/x6-common' -import { Router } from '../index' -import { router } from './router' -import { defaults, ManhattanRouterOptions } from './options' - -export const manhattan: Router.Definition> = - function (vertices, options, edgeView) { - return FunctionExt.call( - router, - this, - vertices, - { ...defaults, ...options }, - edgeView, - ) - } diff --git a/packages/x6-core/src/registry/router/manhattan/obstacle-map.ts b/packages/x6-core/src/registry/router/manhattan/obstacle-map.ts deleted file mode 100644 index 2d8136ba87e..00000000000 --- a/packages/x6-core/src/registry/router/manhattan/obstacle-map.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { ArrayExt, KeyValue } from '@antv/x6-common' -import { Rectangle, Point } from '@antv/x6-geometry' -import { Cell, Edge, Model } from '../../../model' -import { ResolvedOptions } from './options' - -/** - * Helper structure to identify whether a point lies inside an obstacle. - */ -export class ObstacleMap { - options: ResolvedOptions - - /** - * How to divide the paper when creating the elements map - */ - mapGridSize: number - - map: KeyValue - - constructor(options: ResolvedOptions) { - this.options = options - this.mapGridSize = 100 - this.map = {} - } - - /** - * Builds a map of all nodes for quicker obstacle queries i.e. is a point - * contained in any obstacle? - * - * A simplified grid search. - */ - build(model: Model, edge: Edge) { - const options = this.options - // source or target node could be excluded from set of obstacles - const excludedTerminals = options.excludeTerminals.reduce( - (memo, type) => { - const terminal = edge[type] - if (terminal) { - const cell = model.getCell((terminal as Edge.TerminalCellData).cell) - if (cell) { - memo.push(cell) - } - } - - return memo - }, - [], - ) - - let excludedAncestors: string[] = [] - - const source = model.getCell(edge.getSourceCellId()) - if (source) { - excludedAncestors = ArrayExt.union( - excludedAncestors, - source.getAncestors().map((cell) => cell.id), - ) - } - - const target = model.getCell(edge.getTargetCellId()) - if (target) { - excludedAncestors = ArrayExt.union( - excludedAncestors, - target.getAncestors().map((cell) => cell.id), - ) - } - - // The graph is divided into smaller cells, where each holds information - // about which node belong to it. When we query whether a point lies - // inside an obstacle we don't need to go through all obstacles, we check - // only those in a particular cell. - const mapGridSize = this.mapGridSize - - model.getNodes().reduce((map, node) => { - const shape = node.shape - const excludeShapes = options.excludeShapes - const excType = shape ? excludeShapes.includes(shape) : false - const excTerminal = excludedTerminals.some((cell) => cell.id === node.id) - const excAncestor = excludedAncestors.includes(node.id) - const excHidden = options.excludeHiddenNodes && !node.isVisible() - const excluded = excType || excTerminal || excAncestor || excHidden - - if (!excluded) { - const bbox = node.getBBox().moveAndExpand(options.paddingBox) - const origin = bbox.getOrigin().snapToGrid(mapGridSize) - const corner = bbox.getCorner().snapToGrid(mapGridSize) - - for (let x = origin.x; x <= corner.x; x += mapGridSize) { - for (let y = origin.y; y <= corner.y; y += mapGridSize) { - const key = new Point(x, y).toString() - if (map[key] == null) { - map[key] = [] - } - map[key].push(bbox) - } - } - } - return map - }, this.map) - - return this - } - - isAccessible(point: Point) { - const key = point.clone().snapToGrid(this.mapGridSize).toString() - - const rects = this.map[key] - return rects ? rects.every((rect) => !rect.containsPoint(point)) : true - } -} diff --git a/packages/x6-core/src/registry/router/manhattan/options.ts b/packages/x6-core/src/registry/router/manhattan/options.ts deleted file mode 100644 index 6da1f8ae214..00000000000 --- a/packages/x6-core/src/registry/router/manhattan/options.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { Point, Rectangle, Angle } from '@antv/x6-geometry' -import { Edge } from '../../../model' -import { EdgeView } from '../../../view' -import { orth } from '../orth' -import { Router } from '../index' - -export type Direction = 'top' | 'right' | 'bottom' | 'left' -type Callable = T | ((this: ManhattanRouterOptions) => T) - -export interface ResolvedOptions { - /** - * The size of step to find a route (the grid of the manhattan pathfinder). - */ - step: number - - /** - * The number of route finding loops that cause the router to abort returns - * fallback route instead. - */ - maxLoopCount: number - - /** - * The number of decimal places to round floating point coordinates. - */ - precision: number - - /** - * The maximum change of direction. - */ - maxDirectionChange: number - - /** - * Should the router use perpendicular edgeView option? Does not connect - * to the anchor of node but rather a point close-by that is orthogonal. - */ - perpendicular: boolean - - /** - * Should the source and/or target not be considered as obstacles? - */ - excludeTerminals: Edge.TerminalType[] - - /** - * Should certain types of nodes not be considered as obstacles? - */ - excludeShapes: string[] - - /** - * Should certain hidden nodes not be considered as obstacles? - */ - excludeHiddenNodes: boolean - - /** - * Possible starting directions from a node. - */ - startDirections: Direction[] - - /** - * Possible ending directions to a node. - */ - endDirections: Direction[] - - /** - * Specify the directions used above and what they mean - */ - directionMap: { - top: Point.PointLike - right: Point.PointLike - bottom: Point.PointLike - left: Point.PointLike - } - - /** - * Returns the cost of an orthogonal step. - */ - cost: number - - /** - * Returns an array of directions to find next points on the route different - * from start/end directions. - */ - directions: { - cost: number - offsetX: number - offsetY: number - angle?: number - gridOffsetX?: number - gridOffsetY?: number - }[] - - /** - * A penalty received for direction change. - */ - penalties: { - [key: number]: number - } - - padding?: { - top: number - right: number - bottom: number - left: number - } - - /** - * The padding applied on the element bounding boxes. - */ - paddingBox: Rectangle.RectangleLike - - fallbackRouter: Router.Definition - - draggingRouter?: - | (( - this: EdgeView, - dragFrom: Point.PointLike, - dragTo: Point.PointLike, - options: ResolvedOptions, - ) => Point[]) - | null - - fallbackRoute?: ( - this: EdgeView, - from: Point, - to: Point, - options: ResolvedOptions, - ) => Point[] | null - - previousDirectionAngle?: number | null -} - -export type ManhattanRouterOptions = { - [Key in keyof ResolvedOptions]: Callable -} - -export const defaults: ManhattanRouterOptions = { - step: 10, - maxLoopCount: 2000, - precision: 1, - maxDirectionChange: 90, - perpendicular: true, - excludeTerminals: [], - excludeShapes: [], // ['text'] - excludeHiddenNodes: false, - startDirections: ['top', 'right', 'bottom', 'left'], - endDirections: ['top', 'right', 'bottom', 'left'], - directionMap: { - top: { x: 0, y: -1 }, - right: { x: 1, y: 0 }, - bottom: { x: 0, y: 1 }, - left: { x: -1, y: 0 }, - }, - - cost() { - const step = resolve(this.step, this) - return step - }, - - directions() { - const step = resolve(this.step, this) - const cost = resolve(this.cost, this) - - return [ - { cost, offsetX: step, offsetY: 0 }, - { cost, offsetX: -step, offsetY: 0 }, - { cost, offsetX: 0, offsetY: step }, - { cost, offsetX: 0, offsetY: -step }, - ] - }, - - penalties() { - const step = resolve(this.step, this) - return { - 0: 0, - 45: step / 2, - 90: step / 2, - } - }, - - paddingBox() { - const step = resolve(this.step, this) - return { - x: -step, - y: -step, - width: 2 * step, - height: 2 * step, - } - }, - - fallbackRouter: orth, - draggingRouter: null, -} - -export function resolve( - input: Callable, - options: ManhattanRouterOptions, -) { - if (typeof input === 'function') { - return input.call(options) - } - return input -} - -export function resolveOptions(options: ManhattanRouterOptions) { - const result = Object.keys(options).reduce( - (memo, key: keyof ResolvedOptions) => { - const ret = memo as any - if ( - key === 'fallbackRouter' || - key === 'draggingRouter' || - key === 'fallbackRoute' - ) { - ret[key] = options[key] - } else { - ret[key] = resolve(options[key], options) - } - return memo - }, - {} as ResolvedOptions, - ) - - if (result.padding) { - const sides = NumberExt.normalizeSides(result.padding) - options.paddingBox = { - x: -sides.left, - y: -sides.top, - width: sides.left + sides.right, - height: sides.top + sides.bottom, - } - } - - result.directions.forEach((direction) => { - const point1 = new Point(0, 0) - const point2 = new Point(direction.offsetX, direction.offsetY) - direction.angle = Angle.normalize(point1.theta(point2)) - }) - - return result -} diff --git a/packages/x6-core/src/registry/router/manhattan/router.ts b/packages/x6-core/src/registry/router/manhattan/router.ts deleted file mode 100644 index 1b640eb341b..00000000000 --- a/packages/x6-core/src/registry/router/manhattan/router.ts +++ /dev/null @@ -1,351 +0,0 @@ -import { FunctionExt, KeyValue } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { EdgeView } from '../../../view' -import { Router } from '../index' -import { SortedSet } from './sorted-set' -import { ObstacleMap } from './obstacle-map' -import * as util from './util' -import { - resolveOptions, - ResolvedOptions, - ManhattanRouterOptions, -} from './options' - -/** - * Finds the route between two points (`from`, `to`). - */ -function findRoute( - edgeView: EdgeView, - from: Point | Rectangle, - to: Point | Rectangle, - map: ObstacleMap, - options: ResolvedOptions, -) { - const precision = options.precision - - let sourceEndpoint - let targetEndpoint - - if (Rectangle.isRectangle(from)) { - sourceEndpoint = util.round( - util.getSourceEndpoint(edgeView, options).clone(), - precision, - ) - } else { - sourceEndpoint = util.round(from.clone(), precision) - } - - if (Rectangle.isRectangle(to)) { - targetEndpoint = util.round( - util.getTargetEndpoint(edgeView, options).clone(), - precision, - ) - } else { - targetEndpoint = util.round(to.clone(), precision) - } - - // Get grid for this route. - const grid = util.getGrid(options.step, sourceEndpoint, targetEndpoint) - - // Get pathfinding points. - // ----------------------- - - const startPoint = sourceEndpoint - const endPoint = targetEndpoint - let startPoints - let endPoints - - if (Rectangle.isRectangle(from)) { - startPoints = util.getRectPoints( - startPoint, - from, - options.startDirections, - grid, - options, - ) - } else { - startPoints = [startPoint] - } - - if (Rectangle.isRectangle(to)) { - endPoints = util.getRectPoints( - targetEndpoint, - to, - options.endDirections, - grid, - options, - ) - } else { - endPoints = [endPoint] - } - - // take into account only accessible rect points (those not under obstacles) - startPoints = startPoints.filter((p) => map.isAccessible(p)) - endPoints = endPoints.filter((p) => map.isAccessible(p)) - - // There is an accessible route point on both sides. - if (startPoints.length > 0 && endPoints.length > 0) { - const openSet = new SortedSet() - // Keeps the actual points for given nodes of the open set. - const points: KeyValue = {} - // Keeps the point that is immediate predecessor of given element. - const parents: KeyValue = {} - // Cost from start to a point along best known path. - const costs: KeyValue = {} - - for (let i = 0, n = startPoints.length; i < n; i += 1) { - // startPoint is assumed to be aligned already - const startPoint = startPoints[i] - const key = util.getKey(startPoint) - openSet.add(key, util.getCost(startPoint, endPoints)) - points[key] = startPoint - costs[key] = 0 - } - - const previousRouteDirectionAngle = options.previousDirectionAngle - // undefined for first route - const isPathBeginning = previousRouteDirectionAngle === undefined - - // directions - let direction - let directionChange - const directions = util.getGridOffsets(grid, options) - const numDirections = directions.length - const endPointsKeys = endPoints.reduce((res, endPoint) => { - const key = util.getKey(endPoint) - res.push(key) - return res - }, []) - - // main route finding loop - const sameStartEndPoints = Point.equalPoints(startPoints, endPoints) - let loopsRemaining = options.maxLoopCount - while (!openSet.isEmpty() && loopsRemaining > 0) { - // Get the closest item and mark it CLOSED - const currentKey = openSet.pop()! - const currentPoint = points[currentKey] - const currentParent = parents[currentKey] - const currentCost = costs[currentKey] - - const isStartPoint = currentPoint.equals(startPoint) - const isRouteBeginning = currentParent == null - - let previousDirectionAngle: number | null | undefined - if (!isRouteBeginning) { - previousDirectionAngle = util.getDirectionAngle( - currentParent, - currentPoint, - numDirections, - grid, - options, - ) - } else if (!isPathBeginning) { - // a vertex on the route - previousDirectionAngle = previousRouteDirectionAngle - } else if (!isStartPoint) { - // beginning of route on the path - previousDirectionAngle = util.getDirectionAngle( - startPoint, - currentPoint, - numDirections, - grid, - options, - ) - } else { - previousDirectionAngle = null - } - - // Check if we reached any endpoint - const skipEndCheck = isRouteBeginning && sameStartEndPoints - if (!skipEndCheck && endPointsKeys.indexOf(currentKey) >= 0) { - options.previousDirectionAngle = previousDirectionAngle - return util.reconstructRoute( - parents, - points, - currentPoint, - startPoint, - endPoint, - ) - } - - // Go over all possible directions and find neighbors - for (let i = 0; i < numDirections; i += 1) { - direction = directions[i] - - const directionAngle = direction.angle! - directionChange = util.getDirectionChange( - previousDirectionAngle!, - directionAngle, - ) - - // Don't use the point changed rapidly. - if ( - !(isPathBeginning && isStartPoint) && - directionChange > options.maxDirectionChange - ) { - continue - } - - const neighborPoint = util.align( - currentPoint - .clone() - .translate(direction.gridOffsetX || 0, direction.gridOffsetY || 0), - grid, - precision, - ) - const neighborKey = util.getKey(neighborPoint) - - // Closed points were already evaluated. - if (openSet.isClose(neighborKey) || !map.isAccessible(neighborPoint)) { - continue - } - - // Neighbor is an end point. - if (endPointsKeys.indexOf(neighborKey) >= 0) { - const isEndPoint = neighborPoint.equals(endPoint) - if (!isEndPoint) { - const endDirectionAngle = util.getDirectionAngle( - neighborPoint, - endPoint, - numDirections, - grid, - options, - ) - - const endDirectionChange = util.getDirectionChange( - directionAngle, - endDirectionAngle, - ) - - if (endDirectionChange > options.maxDirectionChange) { - continue - } - } - } - - // The current direction is ok. - // ---------------------------- - - const neighborCost = direction.cost - const neighborPenalty = isStartPoint - ? 0 - : options.penalties[directionChange] - const costFromStart = currentCost + neighborCost + neighborPenalty - - // Neighbor point has not been processed yet or the cost of - // the path from start is lower than previously calculated. - if ( - !openSet.isOpen(neighborKey) || - costFromStart < costs[neighborKey] - ) { - points[neighborKey] = neighborPoint - parents[neighborKey] = currentPoint - costs[neighborKey] = costFromStart - openSet.add( - neighborKey, - costFromStart + util.getCost(neighborPoint, endPoints), - ) - } - } - - loopsRemaining -= 1 - } - } - - if (options.fallbackRoute) { - return FunctionExt.call( - options.fallbackRoute, - this, - startPoint, - endPoint, - options, - ) - } - - return null -} - -export const router: Router.Definition = function ( - vertices, - optionsRaw, - edgeView, -) { - const options = resolveOptions(optionsRaw) - const sourceBBox = util.getSourceBBox(edgeView, options) - const targetBBox = util.getTargetBBox(edgeView, options) - const sourceEndpoint = util.getSourceEndpoint(edgeView, options) - - // pathfinding - const map = new ObstacleMap(options).build( - edgeView.renderer.model, - edgeView.cell, - ) - - const oldVertices = vertices.map((p) => Point.create(p)) - const newVertices: Point[] = [] - - // The origin of first route's grid, does not need snapping - let tailPoint = sourceEndpoint - - let from - let to - - for (let i = 0, len = oldVertices.length; i <= len; i += 1) { - let partialRoute: Point[] | null = null - - from = to || sourceBBox - to = oldVertices[i] - - // This is the last iteration - if (to == null) { - to = targetBBox - - // If the target is a point, we should use dragging route - // instead of main routing method if it has been provided. - const edge = edgeView.cell - const isEndingAtPoint = - edge.getSourceCellId() == null || edge.getTargetCellId() == null - - if (isEndingAtPoint && typeof options.draggingRouter === 'function') { - const dragFrom = from === sourceBBox ? sourceEndpoint : from - const dragTo = to.getOrigin() - partialRoute = FunctionExt.call( - options.draggingRouter, - edgeView, - dragFrom, - dragTo, - options, - ) - } - } - - // Find the partial route - if (partialRoute == null) { - partialRoute = findRoute(edgeView, from, to, map, options) - } - - // Cannot found the partial route. - if (partialRoute === null) { - return FunctionExt.call( - options.fallbackRouter, - this, - vertices, - options, - edgeView, - ) - } - - // Remove the first point if the previous partial route has - // the same point as last. - const leadPoint = partialRoute[0] - if (leadPoint && leadPoint.equals(tailPoint)) { - partialRoute.shift() - } - - // Save tailPoint for next iteration - tailPoint = partialRoute[partialRoute.length - 1] || tailPoint - newVertices.push(...partialRoute) - } - - return newVertices -} diff --git a/packages/x6-core/src/registry/router/manhattan/sorted-set.ts b/packages/x6-core/src/registry/router/manhattan/sorted-set.ts deleted file mode 100644 index aac56d0385a..00000000000 --- a/packages/x6-core/src/registry/router/manhattan/sorted-set.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ArrayExt } from '@antv/x6-common' - -const OPEN = 1 -const CLOSE = 2 - -export class SortedSet { - items: string[] - hash: { [key: string]: number } - values: { [key: string]: number } - - constructor() { - this.items = [] - this.hash = {} - this.values = {} - } - - add(item: string, value: number) { - if (this.hash[item]) { - // item removal - this.items.splice(this.items.indexOf(item), 1) - } else { - this.hash[item] = OPEN - } - - this.values[item] = value - - const index = ArrayExt.sortedIndexBy( - this.items, - item, - (key) => this.values[key], - ) - - this.items.splice(index, 0, item) - } - - pop() { - const item = this.items.shift() - if (item) { - this.hash[item] = CLOSE - } - return item - } - - isOpen(item: string) { - return this.hash[item] === OPEN - } - - isClose(item: string) { - return this.hash[item] === CLOSE - } - - isEmpty() { - return this.items.length === 0 - } -} diff --git a/packages/x6-core/src/registry/router/manhattan/util.ts b/packages/x6-core/src/registry/router/manhattan/util.ts deleted file mode 100644 index 01d6c474ae3..00000000000 --- a/packages/x6-core/src/registry/router/manhattan/util.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { Point, Line, Angle, Rectangle, Util } from '@antv/x6-geometry' -import { KeyValue } from '@antv/x6-common' -import { EdgeView } from '../../../view/edge' -import { ResolvedOptions, Direction } from './options' - -export function getSourceBBox(view: EdgeView, options: ResolvedOptions) { - const bbox = view.sourceBBox.clone() - if (options && options.paddingBox) { - return bbox.moveAndExpand(options.paddingBox) - } - - return bbox -} - -export function getTargetBBox(view: EdgeView, options: ResolvedOptions) { - const bbox = view.targetBBox.clone() - if (options && options.paddingBox) { - return bbox.moveAndExpand(options.paddingBox) - } - - return bbox -} - -export function getSourceEndpoint(view: EdgeView, options: ResolvedOptions) { - if (view.sourceAnchor) { - return view.sourceAnchor - } - - const sourceBBox = getSourceBBox(view, options) - return sourceBBox.getCenter() -} - -export function getTargetEndpoint(view: EdgeView, options: ResolvedOptions) { - if (view.targetAnchor) { - return view.targetAnchor - } - - const targetBBox = getTargetBBox(view, options) - return targetBBox.getCenter() -} - -// returns a direction index from start point to end point -// corrects for grid deformation between start and end -export function getDirectionAngle( - start: Point, - end: Point, - directionCount: number, - grid: Grid, - options: ResolvedOptions, -) { - const quadrant = 360 / directionCount - const angleTheta = start.theta(fixAngleEnd(start, end, grid, options)) - const normalizedAngle = Angle.normalize(angleTheta + quadrant / 2) - return quadrant * Math.floor(normalizedAngle / quadrant) -} - -function fixAngleEnd( - start: Point, - end: Point, - grid: Grid, - options: ResolvedOptions, -) { - const step = options.step - - const diffX = end.x - start.x - const diffY = end.y - start.y - - const gridStepsX = diffX / grid.x - const gridStepsY = diffY / grid.y - - const distanceX = gridStepsX * step - const distanceY = gridStepsY * step - - return new Point(start.x + distanceX, start.y + distanceY) -} - -/** - * Returns the change in direction between two direction angles. - */ -export function getDirectionChange(angle1: number, angle2: number) { - const change = Math.abs(angle1 - angle2) - return change > 180 ? 360 - change : change -} - -// fix direction offsets according to current grid -export function getGridOffsets(grid: Grid, options: ResolvedOptions) { - const step = options.step - - options.directions.forEach((direction) => { - direction.gridOffsetX = (direction.offsetX / step) * grid.x - direction.gridOffsetY = (direction.offsetY / step) * grid.y - }) - - return options.directions -} - -export interface Grid { - source: Point - x: number - y: number -} - -// get grid size in x and y dimensions, adapted to source and target positions -export function getGrid(step: number, source: Point, target: Point): Grid { - return { - source: source.clone(), - x: getGridDimension(target.x - source.x, step), - y: getGridDimension(target.y - source.y, step), - } -} - -function getGridDimension(diff: number, step: number) { - // return step if diff = 0 - if (!diff) { - return step - } - - const abs = Math.abs(diff) - const count = Math.round(abs / step) - - // return `abs` if less than one step apart - if (!count) { - return abs - } - - // otherwise, return corrected step - const roundedDiff = count * step - const remainder = abs - roundedDiff - const correction = remainder / count - - return step + correction -} - -function snapGrid(point: Point, grid: Grid) { - const source = grid.source - const x = Util.snapToGrid(point.x - source.x, grid.x) + source.x - const y = Util.snapToGrid(point.y - source.y, grid.y) + source.y - - return new Point(x, y) -} - -export function round(point: Point, precision: number) { - return point.round(precision) -} - -export function align(point: Point, grid: Grid, precision: number) { - return round(snapGrid(point.clone(), grid), precision) -} - -export function getKey(point: Point) { - return point.toString() -} - -export function normalizePoint(point: Point.PointLike) { - return new Point( - point.x === 0 ? 0 : Math.abs(point.x) / point.x, - point.y === 0 ? 0 : Math.abs(point.y) / point.y, - ) -} - -export function getCost(from: Point, anchors: Point[]) { - let min = Infinity - - for (let i = 0, len = anchors.length; i < len; i += 1) { - const dist = from.manhattanDistance(anchors[i]) - if (dist < min) { - min = dist - } - } - - return min -} - -// Find points around the bbox taking given directions into account -// lines are drawn from anchor in given directions, intersections recorded -// if anchor is outside bbox, only those directions that intersect get a rect point -// the anchor itself is returned as rect point (representing some directions) -// (since those directions are unobstructed by the bbox) -export function getRectPoints( - anchor: Point, - bbox: Rectangle, - directionList: Direction[], - grid: Grid, - options: ResolvedOptions, -) { - const precision = options.precision - const directionMap = options.directionMap - const centerVector = anchor.diff(bbox.getCenter()) - - const rectPoints = Object.keys(directionMap).reduce( - (res, key: Direction) => { - if (directionList.includes(key)) { - const direction = directionMap[key] - - // Create a line that is guaranteed to intersect the bbox if bbox - // is in the direction even if anchor lies outside of bbox. - const ending = new Point( - anchor.x + direction.x * (Math.abs(centerVector.x) + bbox.width), - anchor.y + direction.y * (Math.abs(centerVector.y) + bbox.height), - ) - const intersectionLine = new Line(anchor, ending) - - // Get the farther intersection, in case there are two - // (that happens if anchor lies next to bbox) - const intersections = intersectionLine.intersect(bbox) || [] - let farthestIntersectionDistance - let farthestIntersection = null - for (let i = 0; i < intersections.length; i += 1) { - const intersection = intersections[i] - const distance = anchor.squaredDistance(intersection) - if ( - farthestIntersectionDistance == null || - distance > farthestIntersectionDistance - ) { - farthestIntersectionDistance = distance - farthestIntersection = intersection - } - } - - // If an intersection was found in this direction, it is our rectPoint - if (farthestIntersection) { - let target = align(farthestIntersection, grid, precision) - // If the rectPoint lies inside the bbox, offset it by one more step - if (bbox.containsPoint(target)) { - target = align( - target.translate(direction.x * grid.x, direction.y * grid.y), - grid, - precision, - ) - } - - res.push(target) - } - } - - return res - }, - [], - ) - - // if anchor lies outside of bbox, add it to the array of points - if (!bbox.containsPoint(anchor)) { - rectPoints.push(align(anchor, grid, precision)) - } - - return rectPoints -} - -// reconstructs a route by concatenating points with their parents -export function reconstructRoute( - parents: KeyValue, - points: KeyValue, - tailPoint: Point, - from: Point, - to: Point, -) { - const route = [] - - let prevDiff = normalizePoint(to.diff(tailPoint)) - - // tailPoint is assumed to be aligned already - let currentKey = getKey(tailPoint) - let parent = parents[currentKey] - - let point - while (parent) { - // point is assumed to be aligned already - point = points[currentKey] - - const diff = normalizePoint(point.diff(parent)) - if (!diff.equals(prevDiff)) { - route.unshift(point) - prevDiff = diff - } - - // parent is assumed to be aligned already - currentKey = getKey(parent) - parent = parents[currentKey] - } - - // leadPoint is assumed to be aligned already - const leadPoint = points[currentKey] - - const fromDiff = normalizePoint(leadPoint.diff(from)) - if (!fromDiff.equals(prevDiff)) { - route.unshift(leadPoint) - } - - return route -} diff --git a/packages/x6-core/src/registry/router/metro.ts b/packages/x6-core/src/registry/router/metro.ts deleted file mode 100644 index 84f40a0ac85..00000000000 --- a/packages/x6-core/src/registry/router/metro.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { FunctionExt } from '@antv/x6-common' -import { Point, Line, Angle } from '@antv/x6-geometry' -import { ManhattanRouterOptions, resolve } from './manhattan/options' -import { manhattan } from './manhattan/index' -import { Router } from './index' - -export interface MetroRouterOptions extends ManhattanRouterOptions {} - -const defaults: Partial = { - maxDirectionChange: 45, - - // an array of directions to find next points on the route - // different from start/end directions - directions() { - const step = resolve(this.step, this) - const cost = resolve(this.cost, this) - const diagonalCost = Math.ceil(Math.sqrt((step * step) << 1)) // eslint-disable-line no-bitwise - - return [ - { cost, offsetX: step, offsetY: 0 }, - { cost: diagonalCost, offsetX: step, offsetY: step }, - { cost, offsetX: 0, offsetY: step }, - { cost: diagonalCost, offsetX: -step, offsetY: step }, - { cost, offsetX: -step, offsetY: 0 }, - { cost: diagonalCost, offsetX: -step, offsetY: -step }, - { cost, offsetX: 0, offsetY: -step }, - { cost: diagonalCost, offsetX: step, offsetY: -step }, - ] - }, - - // a simple route used in situations when main routing method fails - // (exceed max number of loop iterations, inaccessible) - fallbackRoute(from, to, options) { - // Find a route which breaks by 45 degrees ignoring all obstacles. - - const theta = from.theta(to) - - const route = [] - - let a = { x: to.x, y: from.y } - let b = { x: from.x, y: to.y } - - if (theta % 180 > 90) { - const t = a - a = b - b = t - } - - const p1 = theta % 90 < 45 ? a : b - const l1 = new Line(from, p1) - - const alpha = 90 * Math.ceil(theta / 90) - - const p2 = Point.fromPolar(l1.squaredLength(), Angle.toRad(alpha + 135), p1) - const l2 = new Line(to, p2) - - const intersectionPoint = l1.intersectsWithLine(l2) - const point = intersectionPoint || to - - const directionFrom = intersectionPoint ? point : from - - const quadrant = 360 / options.directions.length - const angleTheta = directionFrom.theta(to) - const normalizedAngle = Angle.normalize(angleTheta + quadrant / 2) - const directionAngle = quadrant * Math.floor(normalizedAngle / quadrant) - - options.previousDirectionAngle = directionAngle - - if (point) route.push(point.round()) - route.push(to) - - return route - }, -} - -export const metro: Router.Definition> = function ( - vertices, - options, - linkView, -) { - return FunctionExt.call( - manhattan, - this, - vertices, - { ...defaults, ...options }, - linkView, - ) -} diff --git a/packages/x6-core/src/registry/router/normal.ts b/packages/x6-core/src/registry/router/normal.ts deleted file mode 100644 index 21b3618ffad..00000000000 --- a/packages/x6-core/src/registry/router/normal.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Router } from './index' - -export interface NormalRouterOptions {} - -export const normal: Router.Definition = function ( - vertices, -) { - return [...vertices] -} diff --git a/packages/x6-core/src/registry/router/oneside.ts b/packages/x6-core/src/registry/router/oneside.ts deleted file mode 100644 index 97b5da5d377..00000000000 --- a/packages/x6-core/src/registry/router/oneside.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { PaddingOptions } from './util' -import { Router } from './index' - -export interface OneSideRouterOptions extends PaddingOptions { - side?: 'left' | 'top' | 'right' | 'bottom' -} - -/** - * Routes the edge always to/from a certain side - */ -export const oneSide: Router.Definition = function ( - vertices, - options, - edgeView, -) { - const side = options.side || 'bottom' - const padding = NumberExt.normalizeSides(options.padding || 40) - const sourceBBox = edgeView.sourceBBox - const targetBBox = edgeView.targetBBox - const sourcePoint = sourceBBox.getCenter() - const targetPoint = targetBBox.getCenter() - - let coord: 'x' | 'y' - let dim: 'width' | 'height' - let factor - - switch (side) { - case 'top': - factor = -1 - coord = 'y' - dim = 'height' - break - case 'left': - factor = -1 - coord = 'x' - dim = 'width' - break - case 'right': - factor = 1 - coord = 'x' - dim = 'width' - break - case 'bottom': - default: - factor = 1 - coord = 'y' - dim = 'height' - break - } - - // Move the points from the center of the element to outside of it. - sourcePoint[coord] += factor * (sourceBBox[dim] / 2 + padding[side]) - targetPoint[coord] += factor * (targetBBox[dim] / 2 + padding[side]) - - // Make edge orthogonal (at least the first and last vertex). - if (factor * (sourcePoint[coord] - targetPoint[coord]) > 0) { - targetPoint[coord] = sourcePoint[coord] - } else { - sourcePoint[coord] = targetPoint[coord] - } - - return [sourcePoint.toJSON(), ...vertices, targetPoint.toJSON()] -} diff --git a/packages/x6-core/src/registry/router/orth.ts b/packages/x6-core/src/registry/router/orth.ts deleted file mode 100644 index ea3c4354fcd..00000000000 --- a/packages/x6-core/src/registry/router/orth.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { ArrayExt } from '@antv/x6-common' -import { Point, Rectangle, Line, Angle } from '@antv/x6-geometry' -import { Router } from './index' -import * as Util from './util' - -export interface OrthRouterOptions extends Util.PaddingOptions {} - -/** - * Returns a route with orthogonal line segments. - */ -export const orth: Router.Definition = function ( - vertices, - options, - edgeView, -) { - let sourceBBox = Util.getSourceBBox(edgeView, options) - let targetBBox = Util.getTargetBBox(edgeView, options) - const sourceAnchor = Util.getSourceAnchor(edgeView, options) - const targetAnchor = Util.getTargetAnchor(edgeView, options) - - // If anchor lies outside of bbox, the bbox expands to include it - sourceBBox = sourceBBox.union(Util.getPointBBox(sourceAnchor)) - targetBBox = targetBBox.union(Util.getPointBBox(targetAnchor)) - - const points = vertices.map((p) => Point.create(p)) - points.unshift(sourceAnchor) - points.push(targetAnchor) - - // bearing of previous route segment - let bearing: Private.Bearings | null = null - const result = [] - - for (let i = 0, len = points.length - 1; i < len; i += 1) { - let route = null - - const from = points[i] - const to = points[i + 1] - const isOrthogonal = Private.getBearing(from, to) != null - - if (i === 0) { - // source - - if (i + 1 === len) { - // source -> target - - // Expand one of the nodes by 1px to detect situations when the two - // nodes are positioned next to each other with no gap in between. - if (sourceBBox.intersectsWithRect(targetBBox.clone().inflate(1))) { - route = Private.insideNode(from, to, sourceBBox, targetBBox) - } else if (!isOrthogonal) { - route = Private.nodeToNode(from, to, sourceBBox, targetBBox) - } - } else { - // source -> vertex - if (sourceBBox.containsPoint(to)) { - route = Private.insideNode( - from, - to, - sourceBBox, - Util.getPointBBox(to).moveAndExpand(Util.getPaddingBox(options)), - ) - } else if (!isOrthogonal) { - route = Private.nodeToVertex(from, to, sourceBBox) - } - } - } else if (i + 1 === len) { - // vertex -> target - - // prevent overlaps with previous line segment - const isOrthogonalLoop = - isOrthogonal && Private.getBearing(to, from) === bearing - - if (targetBBox.containsPoint(from) || isOrthogonalLoop) { - route = Private.insideNode( - from, - to, - Util.getPointBBox(from).moveAndExpand(Util.getPaddingBox(options)), - targetBBox, - bearing, - ) - } else if (!isOrthogonal) { - route = Private.vertexToNode(from, to, targetBBox, bearing) - } - } else if (!isOrthogonal) { - // vertex -> vertex - route = Private.vertexToVertex(from, to, bearing) - } - - // set bearing for next iteration - if (route) { - result.push(...route.points) - bearing = route.direction as Private.Bearings - } else { - // orthogonal route and not looped - bearing = Private.getBearing(from, to) - } - - // push `to` point to identified orthogonal vertices array - if (i + 1 < len) { - result.push(to) - } - } - - return result -} - -namespace Private { - /** - * Bearing to opposite bearing map - */ - const opposites = { - N: 'S', - S: 'N', - E: 'W', - W: 'E', - } - - /** - * Bearing to radians map - */ - const radians = { - N: (-Math.PI / 2) * 3, - S: -Math.PI / 2, - E: 0, - W: Math.PI, - } - - /** - * Returns a point `p` where lines p,p1 and p,p2 are perpendicular - * and p is not contained in the given box - */ - function freeJoin(p1: Point, p2: Point, bbox: Rectangle) { - let p = new Point(p1.x, p2.y) - if (bbox.containsPoint(p)) { - p = new Point(p2.x, p1.y) - } - - // kept for reference - // if (bbox.containsPoint(p)) { - // return null - // } - - return p - } - - /** - * Returns either width or height of a bbox based on the given bearing. - */ - export function getBBoxSize(bbox: Rectangle, bearing: Bearings) { - return bbox[bearing === 'W' || bearing === 'E' ? 'width' : 'height'] - } - - export type Bearings = ReturnType - - export function getBearing(from: Point.PointLike, to: Point.PointLike) { - if (from.x === to.x) { - return from.y > to.y ? 'N' : 'S' - } - - if (from.y === to.y) { - return from.x > to.x ? 'W' : 'E' - } - - return null - } - - export function vertexToVertex(from: Point, to: Point, bearing: Bearings) { - const p1 = new Point(from.x, to.y) - const p2 = new Point(to.x, from.y) - const d1 = getBearing(from, p1) - const d2 = getBearing(from, p2) - const opposite = bearing ? opposites[bearing] : null - - const p = - d1 === bearing || (d1 !== opposite && (d2 === opposite || d2 !== bearing)) - ? p1 - : p2 - - return { points: [p], direction: getBearing(p, to) } - } - - export function nodeToVertex(from: Point, to: Point, fromBBox: Rectangle) { - const p = freeJoin(from, to, fromBBox) - - return { points: [p], direction: getBearing(p, to) } - } - - export function vertexToNode( - from: Point, - to: Point, - toBBox: Rectangle, - bearing: Bearings, - ) { - const points = [new Point(from.x, to.y), new Point(to.x, from.y)] - const freePoints = points.filter((p) => !toBBox.containsPoint(p)) - const freeBearingPoints = freePoints.filter( - (p) => getBearing(p, from) !== bearing, - ) - - let p - - if (freeBearingPoints.length > 0) { - // Try to pick a point which bears the same direction as the previous segment. - - p = freeBearingPoints.filter((p) => getBearing(from, p) === bearing).pop() - p = p || freeBearingPoints[0] - - return { - points: [p], - direction: getBearing(p, to), - } - } - - { - // Here we found only points which are either contained in the element or they would create - // a link segment going in opposite direction from the previous one. - // We take the point inside element and move it outside the element in the direction the - // route is going. Now we can join this point with the current end (using freeJoin). - - p = ArrayExt.difference(points, freePoints)[0] - - const p2 = Point.create(to).move(p, -getBBoxSize(toBBox, bearing) / 2) - const p1 = freeJoin(p2, from, toBBox) - - return { - points: [p1, p2], - direction: getBearing(p2, to), - } - } - } - - export function nodeToNode( - from: Point, - to: Point, - fromBBox: Rectangle, - toBBox: Rectangle, - ) { - let route = nodeToVertex(to, from, toBBox) - const p1 = route.points[0] - - if (fromBBox.containsPoint(p1)) { - route = nodeToVertex(from, to, fromBBox) - const p2 = route.points[0] - - if (toBBox.containsPoint(p2)) { - const fromBorder = Point.create(from).move( - p2, - -getBBoxSize(fromBBox, getBearing(from, p2)) / 2, - ) - const toBorder = Point.create(to).move( - p1, - -getBBoxSize(toBBox, getBearing(to, p1)) / 2, - ) - - const mid = new Line(fromBorder, toBorder).getCenter() - const startRoute = nodeToVertex(from, mid, fromBBox) - const endRoute = vertexToVertex( - mid, - to, - startRoute.direction as Bearings, - ) - - route.points = [startRoute.points[0], endRoute.points[0]] - route.direction = endRoute.direction - } - } - - return route - } - - // Finds route for situations where one node is inside the other. - // Typically the route is directed outside the outer node first and - // then back towards the inner node. - export function insideNode( - from: Point, - to: Point, - fromBBox: Rectangle, - toBBox: Rectangle, - bearing?: Bearings, - ) { - const boundary = fromBBox.union(toBBox).inflate(1) - - // start from the point which is closer to the boundary - const center = boundary.getCenter() - const reversed = center.distance(to) > center.distance(from) - const start = reversed ? to : from - const end = reversed ? from : to - - let p1: Point - let p2: Point - let p3: Point - - if (bearing) { - // Points on circle with radius equals 'W + H` are always outside the rectangle - // with width W and height H if the center of that circle is the center of that rectangle. - p1 = Point.fromPolar( - boundary.width + boundary.height, - radians[bearing], - start, - ) - p1 = boundary.getNearestPointToPoint(p1).move(p1, -1) - } else { - p1 = boundary.getNearestPointToPoint(start).move(start, 1) - } - - p2 = freeJoin(p1, end, boundary) - - let points: Point[] - - if (p1.round().equals(p2.round())) { - p2 = Point.fromPolar( - boundary.width + boundary.height, - Angle.toRad(p1.theta(start)) + Math.PI / 2, - end, - ) - p2 = boundary.getNearestPointToPoint(p2).move(end, 1).round() - p3 = freeJoin(p1, p2, boundary) - points = reversed ? [p2, p3, p1] : [p1, p3, p2] - } else { - points = reversed ? [p2, p1] : [p1, p2] - } - - const direction = reversed ? getBearing(p1, to) : getBearing(p2, to) - - return { - points, - direction, - } - } -} diff --git a/packages/x6-core/src/registry/router/util.ts b/packages/x6-core/src/registry/router/util.ts deleted file mode 100644 index 91d47a70d8e..00000000000 --- a/packages/x6-core/src/registry/router/util.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { EdgeView } from '../../view/edge' - -export interface PaddingOptions { - padding?: NumberExt.SideOptions -} - -export function getPointBBox(p: Point) { - return new Rectangle(p.x, p.y, 0, 0) -} - -export function getPaddingBox(options: PaddingOptions = {}) { - const sides = NumberExt.normalizeSides(options.padding || 20) - - return { - x: -sides.left, - y: -sides.top, - width: sides.left + sides.right, - height: sides.top + sides.bottom, - } -} - -export function getSourceBBox(view: EdgeView, options: PaddingOptions = {}) { - return view.sourceBBox.clone().moveAndExpand(getPaddingBox(options)) -} - -export function getTargetBBox(view: EdgeView, options: PaddingOptions = {}) { - return view.targetBBox.clone().moveAndExpand(getPaddingBox(options)) -} - -export function getSourceAnchor(view: EdgeView, options: PaddingOptions = {}) { - if (view.sourceAnchor) { - return view.sourceAnchor - } - const bbox = getSourceBBox(view, options) - return bbox.getCenter() -} - -export function getTargetAnchor(view: EdgeView, options: PaddingOptions = {}) { - if (view.targetAnchor) { - return view.targetAnchor - } - - const bbox = getTargetBBox(view, options) - return bbox.getCenter() -} diff --git a/packages/x6-core/src/registry/tool/anchor.ts b/packages/x6-core/src/registry/tool/anchor.ts deleted file mode 100644 index cdca398e038..00000000000 --- a/packages/x6-core/src/registry/tool/anchor.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { Dom, FunctionExt } from '@antv/x6-common' -import { Point } from '@antv/x6-geometry' -import { Attr } from '../attr' -import { Edge } from '../../model/edge' -import { Node } from '../../model/node' -import { EdgeView } from '../../view/edge' -import { CellView } from '../../view/cell' -import { ToolsView } from '../../view/tool' -import * as Util from './util' - -class Anchor extends ToolsView.ToolItem { - protected get type() { - return this.options.type! - } - - protected onRender() { - Dom.addClass( - this.container, - this.prefixClassName(`edge-tool-${this.type}-anchor`), - ) - - this.toggleArea(false) - this.update() - } - - update() { - const type = this.type - const edgeView = this.cellView - const terminalView = edgeView.getTerminalView(type) - if (terminalView) { - this.updateAnchor() - this.updateArea() - this.container.style.display = '' - } else { - this.container.style.display = 'none' - } - return this - } - - protected updateAnchor() { - const childNodes = this.childNodes - if (!childNodes) { - return - } - - const anchorNode = childNodes.anchor - if (!anchorNode) { - return - } - - const type = this.type - const edgeView = this.cellView - const options = this.options - const position = edgeView.getTerminalAnchor(type) - const customAnchor = edgeView.cell.prop([type, 'anchor']) - anchorNode.setAttribute( - 'transform', - `translate(${position.x}, ${position.y})`, - ) - - const anchorAttrs = customAnchor - ? options.customAnchorAttrs - : options.defaultAnchorAttrs - - if (anchorAttrs) { - Object.keys(anchorAttrs).forEach((attrName) => { - anchorNode.setAttribute(attrName, anchorAttrs[attrName] as string) - }) - } - } - - protected updateArea() { - const childNodes = this.childNodes - if (!childNodes) { - return - } - - const areaNode = childNodes.area - if (!areaNode) { - return - } - - const type = this.type - const edgeView = this.cellView - const terminalView = edgeView.getTerminalView(type) - if (terminalView) { - const terminalCell = terminalView.cell as Node - const magnet = edgeView.getTerminalMagnet(type) - let padding = this.options.areaPadding || 0 - if (!Number.isFinite(padding)) { - padding = 0 - } - - let bbox - let angle - let center - if (terminalView.isEdgeElement(magnet)) { - bbox = terminalView.getBBox() - angle = 0 - center = bbox.getCenter() - } else { - bbox = terminalView.getUnrotatedBBoxOfElement(magnet as SVGElement) - angle = terminalCell.getAngle() - center = bbox.getCenter() - if (angle) { - center.rotate(-angle, terminalCell.getBBox().getCenter()) - } - } - - bbox.inflate(padding) - - Dom.attr(areaNode, { - x: -bbox.width / 2, - y: -bbox.height / 2, - width: bbox.width, - height: bbox.height, - transform: `translate(${center.x}, ${center.y}) rotate(${angle})`, - }) - } - } - - protected toggleArea(visible?: boolean) { - if (this.childNodes) { - const elem = this.childNodes.area as HTMLElement - if (elem) { - elem.style.display = visible ? '' : 'none' - } - } - } - - protected onMouseDown(evt: Dom.MouseDownEvent) { - if (this.guard(evt)) { - return - } - evt.stopPropagation() - evt.preventDefault() - this.renderer.graphView.undelegateEvents() - if (this.options.documentEvents) { - this.delegateDocumentEvents(this.options.documentEvents) - } - this.focus() - this.toggleArea(this.options.restrictArea) - this.cell.startBatch('move-anchor', { - ui: true, - toolId: this.cid, - }) - } - - protected resetAnchor(anchor?: Edge.TerminalCellData['anchor']) { - const type = this.type - const cell = this.cell - if (anchor) { - cell.prop([type, 'anchor'], anchor, { - rewrite: true, - ui: true, - toolId: this.cid, - }) - } else { - cell.removeProp([type, 'anchor'], { - ui: true, - toolId: this.cid, - }) - } - } - - protected onMouseMove(evt: Dom.MouseMoveEvent) { - const terminalType = this.type - const edgeView = this.cellView - const terminalView = edgeView.getTerminalView(terminalType) - if (terminalView == null) { - return - } - - const e = this.normalizeEvent(evt) - const terminalCell = terminalView.cell - const terminalMagnet = edgeView.getTerminalMagnet(terminalType)! - let coords = this.renderer.coord.clientToLocalPoint(e.clientX, e.clientY) - - const snapFn = this.options.snap - if (typeof snapFn === 'function') { - const tmp = FunctionExt.call( - snapFn, - edgeView, - coords, - terminalView, - terminalMagnet, - terminalType, - edgeView, - this, - ) - coords = Point.create(tmp) - } - - if (this.options.restrictArea) { - if (terminalView.isEdgeElement(terminalMagnet)) { - const pointAtConnection = (terminalView as EdgeView).getClosestPoint( - coords, - ) - if (pointAtConnection) { - coords = pointAtConnection - } - } else { - const bbox = terminalView.getUnrotatedBBoxOfElement( - terminalMagnet as SVGElement, - ) - const angle = (terminalCell as Node).getAngle() - const origin = terminalCell.getBBox().getCenter() - const rotatedCoords = coords.clone().rotate(angle, origin) - if (!bbox.containsPoint(rotatedCoords)) { - coords = bbox - .getNearestPointToPoint(rotatedCoords) - .rotate(-angle, origin) - } - } - } - - let anchor - const anchorFn = this.options.anchor - if (typeof anchorFn === 'function') { - anchor = FunctionExt.call( - anchorFn, - edgeView, - coords, - terminalView, - terminalMagnet, - terminalType, - edgeView, - this, - ) as Edge.TerminalCellData['anchor'] - } - - this.resetAnchor(anchor) - this.update() - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected onMouseUp(evt: Dom.MouseUpEvent) { - this.renderer.graphView.delegateEvents() - this.undelegateDocumentEvents() - this.blur() - this.toggleArea(false) - const edgeView = this.cellView - if (this.options.removeRedundancies) { - edgeView.removeRedundantLinearVertices({ ui: true, toolId: this.cid }) - } - this.cell.stopBatch('move-anchor', { ui: true, toolId: this.cid }) - } - - protected onDblClick() { - const anchor = this.options.resetAnchor - if (anchor) { - this.resetAnchor(anchor === true ? undefined : anchor) - } - this.update() - } -} - -namespace Anchor { - export interface Options extends ToolsView.ToolItem.Options { - type?: Edge.TerminalType - snapRadius?: number - areaPadding?: number - restrictArea?: boolean - resetAnchor?: boolean | Edge.TerminalCellData['anchor'] - removeRedundancies?: boolean - defaultAnchorAttrs?: Attr.SimpleAttrs - customAnchorAttrs?: Attr.SimpleAttrs - snap?: ( - this: EdgeView, - pos: Point, - terminalView: CellView, - terminalMagnet: Element | null, - terminalType: Edge.TerminalType, - edgeView: EdgeView, - toolView: Anchor, - ) => Point.PointLike - anchor?: ( - this: EdgeView, - pos: Point, - terminalView: CellView, - terminalMagnet: Element | null, - terminalType: Edge.TerminalType, - edgeView: EdgeView, - toolView: Anchor, - ) => Edge.TerminalCellData['anchor'] - } -} - -namespace Anchor { - Anchor.config({ - tagName: 'g', - markup: [ - { - tagName: 'circle', - selector: 'anchor', - attrs: { - cursor: 'pointer', - }, - }, - { - tagName: 'rect', - selector: 'area', - attrs: { - 'pointer-events': 'none', - fill: 'none', - stroke: '#33334F', - 'stroke-dasharray': '2,4', - rx: 5, - ry: 5, - }, - }, - ], - events: { - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - dblclick: 'onDblClick', - }, - documentEvents: { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - }, - customAnchorAttrs: { - 'stroke-width': 4, - stroke: '#33334F', - fill: '#FFFFFF', - r: 5, - }, - defaultAnchorAttrs: { - 'stroke-width': 2, - stroke: '#FFFFFF', - fill: '#33334F', - r: 6, - }, - areaPadding: 6, - snapRadius: 10, - resetAnchor: true, - restrictArea: true, - removeRedundancies: true, - anchor: Util.getAnchor, - snap(pos, terminalView, terminalMagnet, terminalType, edgeView, toolView) { - const snapRadius = toolView.options.snapRadius || 0 - const isSource = terminalType === 'source' - const refIndex = isSource ? 0 : -1 - const ref = - this.cell.getVertexAt(refIndex) || - this.getTerminalAnchor(isSource ? 'target' : 'source') - if (ref) { - if (Math.abs(ref.x - pos.x) < snapRadius) pos.x = ref.x - if (Math.abs(ref.y - pos.y) < snapRadius) pos.y = ref.y - } - return pos - }, - }) -} - -export const SourceAnchor = Anchor.define({ - name: 'source-anchor', - type: 'source', -}) - -export const TargetAnchor = Anchor.define({ - name: 'target-anchor', - type: 'target', -}) diff --git a/packages/x6-core/src/registry/tool/arrowhead.ts b/packages/x6-core/src/registry/tool/arrowhead.ts deleted file mode 100644 index 176c6b24fe8..00000000000 --- a/packages/x6-core/src/registry/tool/arrowhead.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Point } from '@antv/x6-geometry' -import { Attr } from '../attr' -import { Edge } from '../../model/edge' -import { EdgeView } from '../../view/edge' -import { ToolsView } from '../../view/tool' - -class Arrowhead extends ToolsView.ToolItem { - protected get type() { - return this.options.type! - } - - protected get ratio() { - return this.options.ratio! - } - - protected init() { - if (this.options.attrs) { - const { class: className, ...attrs } = this.options.attrs - this.setAttrs(attrs, this.container) - if (className) { - Dom.addClass(this.container, className as string) - } - } - } - - protected onRender() { - Dom.addClass( - this.container, - this.prefixClassName(`edge-tool-${this.type}-arrowhead`), - ) - this.update() - } - - update() { - const ratio = this.ratio - const edgeView = this.cellView as EdgeView - const tangent = edgeView.getTangentAtRatio(ratio) - const position = tangent ? tangent.start : edgeView.getPointAtRatio(ratio) - const angle = - (tangent && tangent.vector().vectorAngle(new Point(1, 0))) || 0 - - if (!position) { - return this - } - - const matrix = Dom.createSVGMatrix() - .translate(position.x, position.y) - .rotate(angle) - - Dom.transform(this.container as SVGElement, matrix, { absolute: true }) - - return this - } - - protected onMouseDown(evt: Dom.MouseDownEvent) { - if (this.guard(evt)) { - return - } - - evt.stopPropagation() - evt.preventDefault() - - const edgeView = this.cellView as EdgeView - - if (edgeView.can('arrowheadMovable')) { - edgeView.cell.startBatch('move-arrowhead', { - ui: true, - toolId: this.cid, - }) - - const coords = this.renderer.snapToGrid(evt.clientX, evt.clientY) - const data = edgeView.prepareArrowheadDragging(this.type, { - x: coords.x, - y: coords.y, - options: { - toolId: this.cid, - }, - }) - this.cellView.setEventData(evt, data) - this.delegateDocumentEvents(this.options.documentEvents!, evt.data) - edgeView.renderer.graphView.undelegateEvents() - - this.container.style.pointerEvents = 'none' - } - - this.focus() - } - - protected onMouseMove(evt: Dom.MouseMoveEvent) { - const e = this.normalizeEvent(evt) - const coords = this.renderer.snapToGrid(e.clientX, e.clientY) - this.cellView.onMouseMove(e, coords.x, coords.y) - this.update() - } - - protected onMouseUp(evt: Dom.MouseUpEvent) { - this.undelegateDocumentEvents() - const e = this.normalizeEvent(evt) - const edgeView = this.cellView - const coords = this.renderer.snapToGrid(e.clientX, e.clientY) - edgeView.onMouseUp(e, coords.x, coords.y) - this.renderer.graphView.delegateEvents() - this.blur() - this.container.style.pointerEvents = '' - edgeView.cell.stopBatch('move-arrowhead', { - ui: true, - toolId: this.cid, - }) - } -} - -namespace Arrowhead { - export interface Options extends ToolsView.ToolItem.Options { - attrs?: Attr.SimpleAttrs - type?: Edge.TerminalType - ratio?: number - } -} - -namespace Arrowhead { - Arrowhead.config({ - tagName: 'path', - isSVGElement: true, - events: { - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - }, - documentEvents: { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - }, - }) -} - -export const SourceArrowhead = Arrowhead.define({ - name: 'source-arrowhead', - type: 'source', - ratio: 0, - attrs: { - d: 'M 10 -8 -10 0 10 8 Z', - fill: '#333', - stroke: '#fff', - 'stroke-width': 2, - cursor: 'move', - }, -}) - -export const TargetArrowhead = Arrowhead.define({ - name: 'target-arrowhead', - type: 'target', - ratio: 1, - attrs: { - d: 'M -10 -8 10 0 -10 8 Z', - fill: '#333', - stroke: '#fff', - 'stroke-width': 2, - cursor: 'move', - }, -}) diff --git a/packages/x6-core/src/registry/tool/boundary.ts b/packages/x6-core/src/registry/tool/boundary.ts deleted file mode 100644 index b0e3d19ab65..00000000000 --- a/packages/x6-core/src/registry/tool/boundary.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { NumberExt, Dom } from '@antv/x6-common' -import { Attr } from '../attr' -import { NodeView } from '../../view/node' -import { EdgeView } from '../../view/edge' -import { ToolsView } from '../../view/tool' -import * as Util from './util' - -export class Boundary extends ToolsView.ToolItem< - EdgeView | NodeView, - Boundary.Options -> { - protected onRender() { - Dom.addClass(this.container, this.prefixClassName('cell-tool-boundary')) - - if (this.options.attrs) { - const { class: className, ...attrs } = this.options.attrs - Dom.attr(this.container, Dom.kebablizeAttrs(attrs)) - if (className) { - Dom.addClass(this.container, className as string) - } - } - this.update() - } - - update() { - const view = this.cellView - const options = this.options - const { useCellGeometry, rotate } = options - const padding = NumberExt.normalizeSides(options.padding) - let bbox = Util.getViewBBox(view, useCellGeometry).moveAndExpand({ - x: -padding.left, - y: -padding.top, - width: padding.left + padding.right, - height: padding.top + padding.bottom, - }) - - const cell = view.cell - if (cell.isNode()) { - const angle = cell.getAngle() - if (angle) { - if (rotate) { - const origin = cell.getBBox().getCenter() - Dom.rotate(this.container, angle, origin.x, origin.y, { - absolute: true, - }) - } else { - bbox = bbox.bbox(angle) - } - } - } - - Dom.attr(this.container, bbox.toJSON()) - - return this - } -} - -export namespace Boundary { - export interface Options extends ToolsView.ToolItem.Options { - padding?: NumberExt.SideOptions - rotate?: boolean - useCellGeometry?: boolean - attrs?: Attr.SimpleAttrs - } -} - -export namespace Boundary { - Boundary.config({ - name: 'boundary', - tagName: 'rect', - padding: 10, - attrs: { - fill: 'none', - stroke: '#333', - 'stroke-width': 0.5, - 'stroke-dasharray': '5, 5', - 'pointer-events': 'none', - }, - }) -} diff --git a/packages/x6-core/src/registry/tool/button.ts b/packages/x6-core/src/registry/tool/button.ts deleted file mode 100644 index 5c8fbc14fc5..00000000000 --- a/packages/x6-core/src/registry/tool/button.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Dom, NumberExt, FunctionExt } from '@antv/x6-common' -import { CellView } from '../../view/cell' -import { NodeView } from '../../view/node' -import { EdgeView } from '../../view/edge' -import { ToolsView } from '../../view/tool' -import * as Util from './util' -import { Cell } from '../../model' - -export class Button extends ToolsView.ToolItem< - EdgeView | NodeView, - Button.Options -> { - protected onRender() { - Dom.addClass(this.container, this.prefixClassName('cell-tool-button')) - this.update() - } - - update() { - this.updatePosition() - return this - } - - protected updatePosition() { - const view = this.cellView - const matrix = view.cell.isEdge() - ? this.getEdgeMatrix() - : this.getNodeMatrix() - Dom.transform(this.container as SVGElement, matrix, { absolute: true }) - } - - protected getNodeMatrix() { - const view = this.cellView as NodeView - const options = this.options - - let { x = 0, y = 0 } = options - const { offset, useCellGeometry, rotate } = options - - let bbox = Util.getViewBBox(view, useCellGeometry) - const angle = view.cell.getAngle() - if (!rotate) { - bbox = bbox.bbox(angle) - } - - let offsetX = 0 - let offsetY = 0 - if (typeof offset === 'number') { - offsetX = offset - offsetY = offset - } else if (typeof offset === 'object') { - offsetX = offset.x - offsetY = offset.y - } - - x = NumberExt.normalizePercentage(x, bbox.width) - y = NumberExt.normalizePercentage(y, bbox.height) - - let matrix = Dom.createSVGMatrix().translate( - bbox.x + bbox.width / 2, - bbox.y + bbox.height / 2, - ) - - if (rotate) { - matrix = matrix.rotate(angle) - } - - matrix = matrix.translate( - x + offsetX - bbox.width / 2, - y + offsetY - bbox.height / 2, - ) - - return matrix - } - - protected getEdgeMatrix() { - const view = this.cellView as EdgeView - const options = this.options - const { offset = 0, distance = 0, rotate } = options - - let tangent - let position - let angle - if (NumberExt.isPercentage(distance)) { - tangent = view.getTangentAtRatio(parseFloat(distance) / 100) - } else { - tangent = view.getTangentAtLength(distance) - } - - if (tangent) { - position = tangent.start - angle = tangent.vector().vectorAngle(new Point(1, 0)) || 0 - } else { - position = view.getConnection()!.start! - angle = 0 - } - - let matrix = Dom.createSVGMatrix() - .translate(position.x, position.y) - .rotate(angle) - - if (typeof offset === 'object') { - matrix = matrix.translate(offset.x || 0, offset.y || 0) - } else { - matrix = matrix.translate(0, offset) - } - - if (!rotate) { - matrix = matrix.rotate(-angle) - } - - return matrix - } - - protected onMouseDown(e: Dom.MouseDownEvent) { - if (this.guard(e)) { - return - } - - e.stopPropagation() - e.preventDefault() - - const onClick = this.options.onClick - if (typeof onClick === 'function') { - FunctionExt.call(onClick, this.cellView, { - e, - view: this.cellView, - cell: this.cellView.cell, - btn: this, - }) - } - } -} - -export namespace Button { - export interface Options extends ToolsView.ToolItem.Options { - x?: number - y?: number - distance?: number - offset?: number | Point.PointLike - rotate?: boolean - useCellGeometry?: boolean - onClick?: ( - this: CellView, - args: { - e: Dom.MouseDownEvent - cell: Cell - view: CellView - btn: Button - }, - ) => any - } -} - -export namespace Button { - Button.config({ - name: 'button', - events: { - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - }, - }) -} - -export namespace Button { - export const Remove = Button.define({ - name: 'button-remove', - markup: [ - { - tagName: 'circle', - selector: 'button', - attrs: { - r: 7, - fill: '#FF1D00', - cursor: 'pointer', - }, - }, - { - tagName: 'path', - selector: 'icon', - attrs: { - d: 'M -3 -3 3 3 M -3 3 3 -3', - fill: 'none', - stroke: '#FFFFFF', - 'stroke-width': 2, - 'pointer-events': 'none', - }, - }, - ], - distance: 60, - offset: 0, - onClick({ view, btn }) { - btn.parent.remove() - view.cell.remove({ ui: true, toolId: btn.cid }) - }, - }) -} diff --git a/packages/x6-core/src/registry/tool/index.ts b/packages/x6-core/src/registry/tool/index.ts deleted file mode 100644 index 874c9545c5c..00000000000 --- a/packages/x6-core/src/registry/tool/index.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { Registry, KeyValue } from '@antv/x6-common' -import { ToolsView } from '../../view/tool' -import { Button } from './button' -import { Boundary } from './boundary' -import { Vertices } from './vertices' -import { Segments } from './segments' -import { SourceAnchor, TargetAnchor } from './anchor' -import { SourceArrowhead, TargetArrowhead } from './arrowhead' -// import { CellEditor } from './editor' - -export namespace NodeTool { - export const presets = { - boundary: Boundary, - button: Button, - 'button-remove': Button.Remove, - // 'node-editor': CellEditor.NodeEditor, - } - - export type Definition = ToolsView.ToolItem.Definition - - export const registry = Registry.create< - Definition, - Presets, - ToolsView.ToolItem.Options & { inherit?: string } & KeyValue - >({ - type: 'node tool', - process(name, options) { - if (typeof options === 'function') { - return options - } - - let parent = ToolsView.ToolItem - const { inherit, ...others } = options - if (inherit) { - const base = this.get(inherit) - if (base == null) { - this.onNotFound(inherit, 'inherited') - } else { - parent = base - } - } - - if (others.name == null) { - others.name = name - } - - return parent.define.call(parent, others) - }, - }) - - registry.register(presets, true) -} - -export namespace NodeTool { - export type Presets = typeof NodeTool['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: ConstructorParameters[0] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: ToolsView.ToolItem.Options - } -} - -export namespace EdgeTool { - export const presets = { - boundary: Boundary, - vertices: Vertices, - segments: Segments, - button: Button, - 'button-remove': Button.Remove, - 'source-anchor': SourceAnchor, - 'target-anchor': TargetAnchor, - 'source-arrowhead': SourceArrowhead, - 'target-arrowhead': TargetArrowhead, - // 'edge-editor': CellEditor.EdgeEditor, - } - - export type Definition = NodeTool.Definition - - export const registry = Registry.create< - Definition, - Presets, - ToolsView.ToolItem.Options & { inherit?: string } & KeyValue - >({ - type: 'edge tool', - process(name, options) { - if (typeof options === 'function') { - return options - } - - let parent = ToolsView.ToolItem - const { inherit, ...others } = options - if (inherit) { - const base = this.get(inherit) - if (base == null) { - this.onNotFound(inherit, 'inherited') - } else { - parent = base - } - } - - if (others.name == null) { - others.name = name - } - - return parent.define.call(parent, others) - }, - }) - - registry.register(presets, true) -} - -export namespace EdgeTool { - export type Presets = typeof EdgeTool['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: ConstructorParameters[0] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: ToolsView.ToolItem.Options - } -} diff --git a/packages/x6-core/src/registry/tool/segments.ts b/packages/x6-core/src/registry/tool/segments.ts deleted file mode 100644 index 3818d78d5cc..00000000000 --- a/packages/x6-core/src/registry/tool/segments.ts +++ /dev/null @@ -1,523 +0,0 @@ -import { Dom, ObjectExt, FunctionExt } from '@antv/x6-common' -import { Point, Line } from '@antv/x6-geometry' -import { View } from '../../view/view' -import { ToolsView } from '../../view/tool' -import * as Util from './util' -import { Attr } from '../attr' -import { CellView } from '../../view/cell' -import { EdgeView } from '../../view/edge' -import { Edge } from '../../model/edge' -import { Renderer } from '../../renderer' - -export class Segments extends ToolsView.ToolItem { - protected handles: Segments.Handle[] = [] - - protected get vertices() { - return this.cellView.cell.getVertices() - } - - update() { - this.render() - return this - } - - protected onRender() { - Dom.addClass(this.container, this.prefixClassName('edge-tool-segments')) - this.resetHandles() - const edgeView = this.cellView - const vertices = [...this.vertices] - vertices.unshift(edgeView.sourcePoint) - vertices.push(edgeView.targetPoint) - - for (let i = 0, l = vertices.length; i < l - 1; i += 1) { - const vertex = vertices[i] - const nextVertex = vertices[i + 1] - const handle = this.renderHandle(vertex, nextVertex, i) - this.stamp(handle.container) - this.handles.push(handle) - } - return this - } - - protected renderHandle( - vertex: Point.PointLike, - nextVertex: Point.PointLike, - index: number, - ) { - const handle = this.options.createHandle!({ - index, - renderer: this.renderer, - guard: (evt) => this.guard(evt), - attrs: this.options.attrs || {}, - }) - - if (this.options.processHandle) { - this.options.processHandle(handle) - } - - this.renderer.options.onToolItemCreated({ - name: 'segments', - cell: this.cell, - view: this.cellView, - tool: handle, - }) - - this.updateHandle(handle, vertex, nextVertex) - this.container.appendChild(handle.container) - this.startHandleListening(handle) - return handle - } - - protected startHandleListening(handle: Segments.Handle) { - handle.on('change', this.onHandleChange, this) - handle.on('changing', this.onHandleChanging, this) - handle.on('changed', this.onHandleChanged, this) - } - - protected stopHandleListening(handle: Segments.Handle) { - handle.off('change', this.onHandleChange, this) - handle.off('changing', this.onHandleChanging, this) - handle.off('changed', this.onHandleChanged, this) - } - - protected resetHandles() { - const handles = this.handles - this.handles = [] - if (handles) { - handles.forEach((handle) => { - this.stopHandleListening(handle) - handle.remove() - }) - } - } - - protected shiftHandleIndexes(delta: number) { - const handles = this.handles - for (let i = 0, n = handles.length; i < n; i += 1) { - handles[i].options.index! += delta - } - } - - protected resetAnchor( - type: Edge.TerminalType, - anchor: Edge.TerminalCellData['anchor'], - ) { - const edge = this.cellView.cell - const options = { - ui: true, - toolId: this.cid, - } - - if (anchor) { - edge.prop([type, 'anchor'], anchor, options) - } else { - edge.removeProp([type, 'anchor'], options) - } - } - - protected snapHandle( - handle: Segments.Handle, - position: Point.PointLike, - data: Segments.EventData, - ) { - const axis = handle.options.axis! - const index = handle.options.index! - const edgeView = this.cellView - const edge = edgeView.cell - const vertices = edge.getVertices() - const prev = vertices[index - 2] || data.sourceAnchor - const next = vertices[index + 1] || data.targetAnchor - const snapRadius = this.options.snapRadius - if (Math.abs(position[axis] - prev[axis]) < snapRadius) { - position[axis] = prev[axis] - } else if (Math.abs(position[axis] - next[axis]) < snapRadius) { - position[axis] = next[axis] - } - return position - } - - protected onHandleChanging({ - handle, - e, - }: Segments.Handle.EventArgs['changing']) { - const renderer = this.renderer - const options = this.options - const edgeView = this.cellView - const anchorFn = options.anchor - - const axis = handle.options.axis! - const index = handle.options.index! - 1 - - const data = this.getEventData(e) - const evt = this.normalizeEvent(e) - const coords = renderer.snapToGrid(evt.clientX, evt.clientY) - const position = this.snapHandle(handle, coords.clone(), data) - const vertices = ObjectExt.cloneDeep(this.vertices) - let vertex = vertices[index] - let nextVertex = vertices[index + 1] - - // First Segment - const sourceView = edgeView.sourceView - const sourceBBox = edgeView.sourceBBox - let changeSourceAnchor = false - let deleteSourceAnchor = false - - if (!vertex) { - vertex = edgeView.sourceAnchor.toJSON() - vertex[axis] = position[axis] - if (sourceBBox.containsPoint(vertex)) { - changeSourceAnchor = true - } else { - vertices.unshift(vertex) - this.shiftHandleIndexes(1) - deleteSourceAnchor = true - } - } else if (index === 0) { - if (sourceBBox.containsPoint(vertex)) { - vertices.shift() - this.shiftHandleIndexes(-1) - changeSourceAnchor = true - } else { - vertex[axis] = position[axis] - deleteSourceAnchor = true - } - } else { - vertex[axis] = position[axis] - } - - if (typeof anchorFn === 'function' && sourceView) { - if (changeSourceAnchor) { - const sourceAnchorPosition = data.sourceAnchor.clone() - sourceAnchorPosition[axis] = position[axis] - const sourceAnchor = FunctionExt.call( - anchorFn, - edgeView, - sourceAnchorPosition, - sourceView, - edgeView.sourceMagnet || sourceView.container, - 'source', - edgeView, - this, - ) - this.resetAnchor('source', sourceAnchor) - } - - if (deleteSourceAnchor) { - this.resetAnchor('source', data.sourceAnchorDef) - } - } - - // Last segment - const targetView = edgeView.targetView - const targetBBox = edgeView.targetBBox - let changeTargetAnchor = false - let deleteTargetAnchor = false - if (!nextVertex) { - nextVertex = edgeView.targetAnchor.toJSON() - nextVertex[axis] = position[axis] - if (targetBBox.containsPoint(nextVertex)) { - changeTargetAnchor = true - } else { - vertices.push(nextVertex) - deleteTargetAnchor = true - } - } else if (index === vertices.length - 2) { - if (targetBBox.containsPoint(nextVertex)) { - vertices.pop() - changeTargetAnchor = true - } else { - nextVertex[axis] = position[axis] - deleteTargetAnchor = true - } - } else { - nextVertex[axis] = position[axis] - } - - if (typeof anchorFn === 'function' && targetView) { - if (changeTargetAnchor) { - const targetAnchorPosition = data.targetAnchor.clone() - targetAnchorPosition[axis] = position[axis] - const targetAnchor = FunctionExt.call( - anchorFn, - edgeView, - targetAnchorPosition, - targetView, - edgeView.targetMagnet || targetView.container, - 'target', - edgeView, - this, - ) - this.resetAnchor('target', targetAnchor) - } - if (deleteTargetAnchor) { - this.resetAnchor('target', data.targetAnchorDef) - } - } - - if (!Point.equalPoints(vertices, this.vertices)) { - this.cellView.cell.setVertices(vertices, { ui: true, toolId: this.cid }) - } - - this.updateHandle(handle, vertex, nextVertex, 0) - if (!options.stopPropagation) { - edgeView.notifyMouseMove(evt, coords.x, coords.y) - } - } - - protected onHandleChange({ handle, e }: Segments.Handle.EventArgs['change']) { - const options = this.options - const handles = this.handles - const edgeView = this.cellView - - const index = handle.options.index - if (!Array.isArray(handles)) { - return - } - - for (let i = 0, n = handles.length; i < n; i += 1) { - if (i !== index) { - handles[i].hide() - } - } - - this.focus() - this.setEventData(e, { - sourceAnchor: edgeView.sourceAnchor.clone(), - targetAnchor: edgeView.targetAnchor.clone(), - sourceAnchorDef: ObjectExt.cloneDeep( - this.cell.prop(['source', 'anchor']), - ), - targetAnchorDef: ObjectExt.cloneDeep( - this.cell.prop(['target', 'anchor']), - ), - }) - - this.cell.startBatch('move-segment', { ui: true, toolId: this.cid }) - - if (!options.stopPropagation) { - const normalizedEvent = this.normalizeEvent(e) - const coords = this.renderer.snapToGrid( - normalizedEvent.clientX, - normalizedEvent.clientY, - ) - edgeView.notifyMouseDown(normalizedEvent, coords.x, coords.y) - } - } - - protected onHandleChanged({ e }: Segments.Handle.EventArgs['changed']) { - const options = this.options - const edgeView = this.cellView - if (options.removeRedundancies) { - edgeView.removeRedundantLinearVertices({ ui: true, toolId: this.cid }) - } - - const normalizedEvent = this.normalizeEvent(e) - const coords = this.renderer.snapToGrid( - normalizedEvent.clientX, - normalizedEvent.clientY, - ) - - this.render() - this.blur() - - this.cell.stopBatch('move-segment', { ui: true, toolId: this.cid }) - if (!options.stopPropagation) { - edgeView.notifyMouseUp(normalizedEvent, coords.x, coords.y) - } - edgeView.checkMouseleave(normalizedEvent) - - options.onChanged && options.onChanged({ edge: edgeView.cell, edgeView }) - } - - protected updateHandle( - handle: Segments.Handle, - vertex: Point.PointLike, - nextVertex: Point.PointLike, - offset = 0, - ) { - const precision = this.options.precision || 0 - const vertical = Math.abs(vertex.x - nextVertex.x) < precision - const horizontal = Math.abs(vertex.y - nextVertex.y) < precision - if (vertical || horizontal) { - const segmentLine = new Line(vertex, nextVertex) - const length = segmentLine.length() - if (length < this.options.threshold) { - handle.hide() - } else { - const position = segmentLine.getCenter() - const axis = vertical ? 'x' : 'y' - position[axis] += offset || 0 - const angle = segmentLine.vector().vectorAngle(new Point(1, 0)) - handle.updatePosition(position.x, position.y, angle, this.cellView) - handle.show() - handle.options.axis = axis - } - } else { - handle.hide() - } - } - - protected onRemove() { - this.resetHandles() - } -} - -export namespace Segments { - export interface Options extends ToolsView.ToolItem.Options { - threshold: number - precision?: number - snapRadius: number - stopPropagation: boolean - removeRedundancies: boolean - attrs: Attr.SimpleAttrs | ((handle: Handle) => Attr.SimpleAttrs) - anchor?: ( - this: EdgeView, - pos: Point, - terminalView: CellView, - terminalMagnet: Element | null, - terminalType: Edge.TerminalType, - edgeView: EdgeView, - toolView: Segments, - ) => Edge.TerminalCellData['anchor'] - createHandle?: (options: Handle.Options) => Handle - processHandle?: (handle: Handle) => void - onChanged?: (options: { edge: Edge; edgeView: EdgeView }) => void - } - - export interface EventData { - sourceAnchor: Point - targetAnchor: Point - sourceAnchorDef: Edge.TerminalCellData['anchor'] - targetAnchorDef: Edge.TerminalCellData['anchor'] - } -} - -export namespace Segments { - export class Handle extends View { - public container: SVGRectElement - - constructor(public options: Handle.Options) { - super() - this.render() - this.delegateEvents({ - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - }) - } - - render() { - this.container = View.createElement('rect', true) as SVGRectElement - const attrs = this.options.attrs - if (typeof attrs === 'function') { - const defaults = Segments.getDefaults() - this.setAttrs({ - ...defaults.attrs, - ...attrs(this), - }) - } else { - this.setAttrs(attrs) - } - this.addClass(this.prefixClassName('edge-tool-segment')) - } - - updatePosition(x: number, y: number, angle: number, view: EdgeView) { - const p = view.getClosestPoint(new Point(x, y)) || new Point(x, y) - let matrix = Dom.createSVGMatrix().translate(p.x, p.y) - if (!p.equals({ x, y })) { - const line = new Line(x, y, p.x, p.y) - let deg = line.vector().vectorAngle(new Point(1, 0)) - if (deg !== 0) { - deg += 90 - } - matrix = matrix.rotate(deg) - } else { - matrix = matrix.rotate(angle) - } - - this.setAttrs({ - transform: Dom.matrixToTransformString(matrix), - cursor: angle % 180 === 0 ? 'row-resize' : 'col-resize', - }) - } - - protected onMouseDown(evt: Dom.MouseDownEvent) { - if (this.options.guard(evt)) { - return - } - - this.trigger('change', { e: evt, handle: this }) - - evt.stopPropagation() - evt.preventDefault() - this.options.renderer.graphView.undelegateEvents() - this.delegateDocumentEvents( - { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - }, - evt.data, - ) - } - - protected onMouseMove(evt: Dom.MouseMoveEvent) { - this.emit('changing', { e: evt, handle: this }) - } - - protected onMouseUp(evt: Dom.MouseUpEvent) { - this.emit('changed', { e: evt, handle: this }) - this.undelegateDocumentEvents() - this.options.renderer.graphView.delegateEvents() - } - - show() { - this.container.style.display = '' - } - - hide() { - this.container.style.display = 'none' - } - } - - export namespace Handle { - export interface Options { - renderer: Renderer - guard: (evt: Dom.EventObject) => boolean - attrs: Attr.SimpleAttrs | ((handle: Handle) => Attr.SimpleAttrs) - index?: number - axis?: 'x' | 'y' - } - - export interface EventArgs { - change: { e: Dom.MouseDownEvent; handle: Handle } - changing: { e: Dom.MouseMoveEvent; handle: Handle } - changed: { e: Dom.MouseUpEvent; handle: Handle } - } - } -} - -export namespace Segments { - Segments.config({ - name: 'segments', - precision: 0.5, - threshold: 40, - snapRadius: 10, - stopPropagation: true, - removeRedundancies: true, - attrs: { - width: 20, - height: 8, - x: -10, - y: -4, - rx: 4, - ry: 4, - fill: '#333', - stroke: '#fff', - 'stroke-width': 2, - }, - createHandle: (options) => new Handle(options), - anchor: Util.getAnchor, - }) -} diff --git a/packages/x6-core/src/registry/tool/util.ts b/packages/x6-core/src/registry/tool/util.ts deleted file mode 100644 index e3a261aa4c2..00000000000 --- a/packages/x6-core/src/registry/tool/util.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { FunctionExt } from '@antv/x6-common' -import { Point } from '@antv/x6-geometry' -import { ConnectionStrategy } from '../connection-strategy' -import { Edge } from '../../model/edge' -import { CellView } from '../../view/cell' -import { EdgeView } from '../../view/edge' - -export function getAnchor( - this: EdgeView, - pos: Point.PointLike, - terminalView: CellView, - terminalMagnet: Element, - type: Edge.TerminalType, -) { - const end = FunctionExt.call( - ConnectionStrategy.presets.pinRelative, - this.renderer, - {} as Edge.TerminalCellData, - terminalView, - terminalMagnet, - pos, - this.cell, - type, - {}, - ) - - return end.anchor -} - -export function getViewBBox(view: CellView, quick?: boolean) { - if (quick) { - return view.cell.getBBox() - } - - return view.cell.isEdge() - ? (view as EdgeView).getConnection()!.bbox()! - : view.getUnrotatedBBoxOfElement(view.container as SVGElement) -} diff --git a/packages/x6-core/src/registry/tool/vertices.ts b/packages/x6-core/src/registry/tool/vertices.ts deleted file mode 100644 index 2a1a3f6b085..00000000000 --- a/packages/x6-core/src/registry/tool/vertices.ts +++ /dev/null @@ -1,422 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Dom } from '@antv/x6-common' -import { Config } from '../../common' -import { View } from '../../view/view' -import { ToolsView } from '../../view/tool' -import { EdgeView } from '../../view/edge' -import { Edge } from '../../model/edge' -import { Attr } from '../attr' -import { Renderer } from '../../renderer' - -export class Vertices extends ToolsView.ToolItem { - protected handles: Vertices.Handle[] = [] - - protected get vertices() { - return this.cellView.cell.getVertices() - } - - protected onRender() { - this.addClass(this.prefixClassName('edge-tool-vertices')) - if (this.options.addable) { - this.updatePath() - } - this.resetHandles() - this.renderHandles() - return this - } - - update() { - const vertices = this.vertices - if (vertices.length === this.handles.length) { - this.updateHandles() - } else { - this.resetHandles() - this.renderHandles() - } - - if (this.options.addable) { - this.updatePath() - } - - return this - } - - protected resetHandles() { - const handles = this.handles - this.handles = [] - if (handles) { - handles.forEach((handle) => { - this.stopHandleListening(handle) - handle.remove() - }) - } - } - - protected renderHandles() { - const vertices = this.vertices - for (let i = 0, l = vertices.length; i < l; i += 1) { - const vertex = vertices[i] - const createHandle = this.options.createHandle! - const processHandle = this.options.processHandle - const handle = createHandle({ - index: i, - renderer: this.renderer, - guard: (evt: Dom.EventObject) => this.guard(evt), // eslint-disable-line no-loop-func - attrs: this.options.attrs || {}, - }) - - if (processHandle) { - processHandle(handle) - } - - this.renderer.options.onToolItemCreated({ - name: 'vertices', - cell: this.cell, - view: this.cellView, - tool: handle, - }) - - handle.updatePosition(vertex.x, vertex.y) - this.stamp(handle.container) - this.container.appendChild(handle.container) - this.handles.push(handle) - this.startHandleListening(handle) - } - } - - protected updateHandles() { - const vertices = this.vertices - for (let i = 0, l = vertices.length; i < l; i += 1) { - const vertex = vertices[i] - const handle = this.handles[i] - if (handle) { - handle.updatePosition(vertex.x, vertex.y) - } - } - } - - protected updatePath() { - const connection = this.childNodes.connection - if (connection) { - connection.setAttribute('d', this.cellView.getConnectionPathData()) - } - } - - protected startHandleListening(handle: Vertices.Handle) { - const edgeView = this.cellView - if (edgeView.can('vertexMovable')) { - handle.on('change', this.onHandleChange, this) - handle.on('changing', this.onHandleChanging, this) - handle.on('changed', this.onHandleChanged, this) - } - - if (edgeView.can('vertexDeletable')) { - handle.on('remove', this.onHandleRemove, this) - } - } - - protected stopHandleListening(handle: Vertices.Handle) { - const edgeView = this.cellView - if (edgeView.can('vertexMovable')) { - handle.off('change', this.onHandleChange, this) - handle.off('changing', this.onHandleChanging, this) - handle.off('changed', this.onHandleChanged, this) - } - - if (edgeView.can('vertexDeletable')) { - handle.off('remove', this.onHandleRemove, this) - } - } - - protected getNeighborPoints(index: number) { - const edgeView = this.cellView - const vertices = this.vertices - const prev = index > 0 ? vertices[index - 1] : edgeView.sourceAnchor - const next = - index < vertices.length - 1 ? vertices[index + 1] : edgeView.targetAnchor - return { - prev: Point.create(prev), - next: Point.create(next), - } - } - - protected getMouseEventArgs(evt: T) { - const e = this.normalizeEvent(evt) - const { x, y } = this.renderer.snapToGrid(e.clientX!, e.clientY!) - return { e, x, y } - } - - protected onHandleChange({ e }: Vertices.Handle.EventArgs['change']) { - this.focus() - const edgeView = this.cellView - edgeView.cell.startBatch('move-vertex', { ui: true, toolId: this.cid }) - if (!this.options.stopPropagation) { - const { e: evt, x, y } = this.getMouseEventArgs(e) - edgeView.notifyMouseDown(evt, x, y) - } - } - - protected onHandleChanging({ - handle, - e, - }: Vertices.Handle.EventArgs['changing']) { - const edgeView = this.cellView - const index = handle.options.index - const { e: evt, x, y } = this.getMouseEventArgs(e) - const vertex = { x, y } - this.snapVertex(vertex, index) - edgeView.cell.setVertexAt(index, vertex, { ui: true, toolId: this.cid }) - handle.updatePosition(vertex.x, vertex.y) - if (!this.options.stopPropagation) { - edgeView.notifyMouseMove(evt, x, y) - } - } - - protected onHandleChanged({ e }: Vertices.Handle.EventArgs['changed']) { - const options = this.options - const edgeView = this.cellView - - if (options.addable) { - this.updatePath() - } - - if (!options.removeRedundancies) { - return - } - - const verticesRemoved = edgeView.removeRedundantLinearVertices({ - ui: true, - toolId: this.cid, - }) - - if (verticesRemoved) { - this.render() - } - - this.blur() - - edgeView.cell.stopBatch('move-vertex', { ui: true, toolId: this.cid }) - - if (this.eventData(e).vertexAdded) { - edgeView.cell.stopBatch('add-vertex', { ui: true, toolId: this.cid }) - } - - const { e: evt, x, y } = this.getMouseEventArgs(e) - - if (!this.options.stopPropagation) { - edgeView.notifyMouseUp(evt, x, y) - } - - edgeView.checkMouseleave(evt) - - options.onChanged && options.onChanged({ edge: edgeView.cell, edgeView }) - } - - protected snapVertex(vertex: Point.PointLike, index: number) { - const snapRadius = this.options.snapRadius || 0 - if (snapRadius > 0) { - const neighbors = this.getNeighborPoints(index) - const prev = neighbors.prev - const next = neighbors.next - if (Math.abs(vertex.x - prev.x) < snapRadius) { - vertex.x = prev.x - } else if (Math.abs(vertex.x - next.x) < snapRadius) { - vertex.x = next.x - } - - if (Math.abs(vertex.y - prev.y) < snapRadius) { - vertex.y = neighbors.prev.y - } else if (Math.abs(vertex.y - next.y) < snapRadius) { - vertex.y = next.y - } - } - } - - protected onHandleRemove({ handle, e }: Vertices.Handle.EventArgs['remove']) { - if (this.options.removable) { - const index = handle.options.index - const edgeView = this.cellView - edgeView.cell.removeVertexAt(index, { ui: true }) - if (this.options.addable) { - this.updatePath() - } - edgeView.checkMouseleave(this.normalizeEvent(e)) - } - } - - protected onPathMouseDown(evt: Dom.MouseDownEvent) { - const edgeView = this.cellView - - if ( - this.guard(evt) || - !this.options.addable || - !edgeView.can('vertexAddable') - ) { - return - } - - evt.stopPropagation() - evt.preventDefault() - - const e = this.normalizeEvent(evt) - const vertex = this.renderer.snapToGrid(e.clientX, e.clientY).toJSON() - edgeView.cell.startBatch('add-vertex', { ui: true, toolId: this.cid }) - const index = edgeView.getVertexIndex(vertex.x, vertex.y) - this.snapVertex(vertex, index) - edgeView.cell.insertVertex(vertex, index, { - ui: true, - toolId: this.cid, - }) - this.render() - const handle = this.handles[index] - this.eventData(e, { vertexAdded: true }) - handle.onMouseDown(e) - } - - protected onRemove() { - this.resetHandles() - } -} - -export namespace Vertices { - export interface Options extends ToolsView.ToolItem.Options { - snapRadius?: number - addable?: boolean - removable?: boolean - removeRedundancies?: boolean - stopPropagation?: boolean - attrs?: Attr.SimpleAttrs | ((handle: Handle) => Attr.SimpleAttrs) - createHandle?: (options: Handle.Options) => Handle - processHandle?: (handle: Handle) => void - onChanged?: (options: { edge: Edge; edgeView: EdgeView }) => void - } -} - -export namespace Vertices { - export class Handle extends View { - protected get renderer() { - return this.options.renderer - } - - constructor(public readonly options: Handle.Options) { - super() - this.render() - this.delegateEvents({ - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - dblclick: 'onDoubleClick', - }) - } - - render() { - this.container = View.createElement('circle', true) - const attrs = this.options.attrs - if (typeof attrs === 'function') { - const defaults = Vertices.getDefaults() - this.setAttrs({ - ...defaults.attrs, - ...attrs(this), - }) - } else { - this.setAttrs(attrs) - } - - this.addClass(this.prefixClassName('edge-tool-vertex')) - } - - updatePosition(x: number, y: number) { - this.setAttrs({ cx: x, cy: y }) - } - - onMouseDown(evt: Dom.MouseDownEvent) { - if (this.options.guard(evt)) { - return - } - - evt.stopPropagation() - evt.preventDefault() - this.renderer.graphView.undelegateEvents() - - this.delegateDocumentEvents( - { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - }, - evt.data, - ) - - this.emit('change', { e: evt, handle: this }) - } - - protected onMouseMove(evt: Dom.MouseMoveEvent) { - this.emit('changing', { e: evt, handle: this }) - } - - protected onMouseUp(evt: Dom.MouseUpEvent) { - this.emit('changed', { e: evt, handle: this }) - this.undelegateDocumentEvents() - this.renderer.graphView.delegateEvents() - } - - protected onDoubleClick(evt: Dom.DoubleClickEvent) { - this.emit('remove', { e: evt, handle: this }) - } - } - - export namespace Handle { - export interface Options { - renderer: Renderer - index: number - guard: (evt: Dom.EventObject) => boolean - attrs: Attr.SimpleAttrs | ((handle: Handle) => Attr.SimpleAttrs) - } - - export interface EventArgs { - change: { e: Dom.MouseDownEvent; handle: Handle } - changing: { e: Dom.MouseMoveEvent; handle: Handle } - changed: { e: Dom.MouseUpEvent; handle: Handle } - remove: { e: Dom.DoubleClickEvent; handle: Handle } - } - } -} - -export namespace Vertices { - const pathClassName = Config.prefix('edge-tool-vertex-path') - - Vertices.config({ - name: 'vertices', - snapRadius: 20, - addable: true, - removable: true, - removeRedundancies: true, - stopPropagation: true, - attrs: { - r: 6, - fill: '#333', - stroke: '#fff', - cursor: 'move', - 'stroke-width': 2, - }, - createHandle: (options) => new Handle(options), - markup: [ - { - tagName: 'path', - selector: 'connection', - className: pathClassName, - attrs: { - fill: 'none', - stroke: 'transparent', - 'stroke-width': 10, - cursor: 'pointer', - }, - }, - ], - events: { - [`mousedown .${pathClassName}`]: 'onPathMouseDown', - [`touchstart .${pathClassName}`]: 'onPathMouseDown', - }, - }) -} diff --git a/packages/x6-core/src/renderer/coord.ts b/packages/x6-core/src/renderer/coord.ts deleted file mode 100644 index 94602d500f9..00000000000 --- a/packages/x6-core/src/renderer/coord.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { Util } from '../util' -import { Renderer } from './renderer' - -// client: window -// page: document -// local: stage -// graph: svg -export class CoordManager { - private renderer: Renderer - protected viewportMatrix: DOMMatrix | null - protected viewportTransformString: string | null - - constructor(renderer: Renderer) { - this.renderer = renderer - } - - get svg() { - return this.renderer.graphView.svg - } - - protected get viewport() { - return this.renderer.graphView.viewport - } - - get stage() { - return this.renderer.graphView.stage - } - - getGraphMatrix() { - const transform = this.viewport.getAttribute('transform') - if (transform !== this.viewportTransformString) { - this.viewportMatrix = this.viewport.getCTM() - this.viewportTransformString = transform - } - return Dom.createSVGMatrix(this.viewportMatrix) - } - - getLocalMatrix() { - return Dom.createSVGMatrix(this.stage.getScreenCTM()) - } - - /** - * Returns coordinates of the graph viewport, relative to the window. - */ - getGraphOffset() { - // see: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect - const rect = this.svg.getBoundingClientRect() - return new Point(rect.left, rect.top) - } - - /** - * Returns coordinates of the graph viewport, relative to the document. - */ - getPageOffset() { - // see: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect - return this.getGraphOffset().translate(window.scrollX, window.scrollY) - } - - snapToGrid(x: number | Point | Point.PointLike, y?: number) { - const p = - typeof x === 'number' - ? this.clientToLocalPoint(x, y as number) - : this.clientToLocalPoint(x.x, x.y) - return p.snapToGrid(this.renderer.options.getGridSize()) - } - - localToGraphPoint(x: number | Point | Point.PointLike, y?: number) { - const localPoint = Point.create(x, y) - return Util.transformPoint(localPoint, this.getGraphMatrix()) - } - - localToClientPoint(x: number | Point | Point.PointLike, y?: number) { - const localPoint = Point.create(x, y) - return Util.transformPoint(localPoint, this.getLocalMatrix()) - } - - localToPagePoint(x: number | Point | Point.PointLike, y?: number) { - const p = - typeof x === 'number' - ? this.localToGraphPoint(x, y!) - : this.localToGraphPoint(x) - return p.translate(this.getPageOffset()) - } - - localToGraphRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const localRect = Rectangle.create(x, y, width, height) - return Util.transformRectangle(localRect, this.getGraphMatrix()) - } - - localToClientRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const localRect = Rectangle.create(x, y, width, height) - return Util.transformRectangle(localRect, this.getLocalMatrix()) - } - - localToPageRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const rect = - typeof x === 'number' - ? this.localToGraphRect(x, y!, width!, height!) - : this.localToGraphRect(x) - return rect.translate(this.getPageOffset()) - } - - graphToLocalPoint(x: number | Point | Point.PointLike, y?: number) { - const graphPoint = Point.create(x, y) - return Util.transformPoint(graphPoint, this.getGraphMatrix().inverse()) - } - - clientToLocalPoint(x: number | Point | Point.PointLike, y?: number) { - const clientPoint = Point.create(x, y) - return Util.transformPoint(clientPoint, this.getLocalMatrix().inverse()) - } - - clientToGraphPoint(x: number | Point | Point.PointLike, y?: number) { - const clientPoint = Point.create(x, y) - return Util.transformPoint( - clientPoint, - this.getGraphMatrix().multiply(this.getLocalMatrix().inverse()), - ) - } - - pageToLocalPoint(x: number | Point | Point.PointLike, y?: number) { - const pagePoint = Point.create(x, y) - const graphPoint = pagePoint.diff(this.getPageOffset()) - return this.graphToLocalPoint(graphPoint) - } - - graphToLocalRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const graphRect = Rectangle.create(x, y, width, height) - return Util.transformRectangle(graphRect, this.getGraphMatrix().inverse()) - } - - clientToLocalRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const clientRect = Rectangle.create(x, y, width, height) - return Util.transformRectangle(clientRect, this.getLocalMatrix().inverse()) - } - - clientToGraphRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const clientRect = Rectangle.create(x, y, width, height) - return Util.transformRectangle( - clientRect, - this.getGraphMatrix().multiply(this.getLocalMatrix().inverse()), - ) - } - - pageToLocalRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const graphRect = Rectangle.create(x, y, width, height) - const pageOffset = this.getPageOffset() - graphRect.x -= pageOffset.x - graphRect.y -= pageOffset.y - return this.graphToLocalRect(graphRect) - } -} - -export namespace CoordManager {} diff --git a/packages/x6-core/src/renderer/defs.ts b/packages/x6-core/src/renderer/defs.ts deleted file mode 100644 index 3426a7759fb..00000000000 --- a/packages/x6-core/src/renderer/defs.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { StringExt, Dom, Vector } from '@antv/x6-common' -import { Attr, Filter, Marker } from '../registry' -import { Markup } from '../view' -import { Renderer } from './renderer' - -export class DefsManager { - private renderer: Renderer - - protected get cid() { - return 'v0' - } - - protected get svg() { - return this.renderer.graphView.svg - } - - protected get defs() { - return this.renderer.graphView.defs - } - - constructor(renderer: Renderer) { - this.renderer = renderer - } - - protected isDefined(id: string) { - return this.svg.getElementById(id) != null - } - - filter(options: DefsManager.FilterOptions) { - let filterId = options.id - const name = options.name - if (!filterId) { - filterId = `filter-${name}-${this.cid}-${StringExt.hashcode( - JSON.stringify(options), - )}` - } - - if (!this.isDefined(filterId)) { - const fn = Filter.registry.get(name) - if (fn == null) { - return Filter.registry.onNotFound(name) - } - - const markup = fn(options.args || {}) - - // Set the filter area to be 3x the bounding box of the cell - // and center the filter around the cell. - const attrs = { - x: -1, - y: -1, - width: 3, - height: 3, - filterUnits: 'objectBoundingBox', - ...options.attrs, - id: filterId, - } - Vector.create(Markup.sanitize(markup), attrs).appendTo(this.defs) - } - - return filterId - } - - gradient(options: DefsManager.GradientOptions) { - let id = options.id - const type = options.type - if (!id) { - id = `gradient-${type}-${this.cid}-${StringExt.hashcode( - JSON.stringify(options), - )}` - } - - if (!this.isDefined(id)) { - const stops = options.stops - const arr = stops.map((stop) => { - const opacity = - stop.opacity != null && Number.isFinite(stop.opacity) - ? stop.opacity - : 1 - - return `` - }) - - const markup = `<${type}>${arr.join('')}` - const attrs = { id, ...options.attrs } - Vector.create(markup, attrs).appendTo(this.defs) - } - - return id - } - - marker(options: DefsManager.MarkerOptions) { - const { - id, - refX, - refY, - markerUnits, - markerOrient, - tagName, - children, - ...attrs - } = options - let markerId = id - if (!markerId) { - markerId = `marker-${this.cid}-${StringExt.hashcode( - JSON.stringify(options), - )}` - } - - if (!this.isDefined(markerId)) { - if (tagName !== 'path') { - // remove unnecessary d attribute inherit from standard edge. - delete attrs.d - } - - const pathMarker = Vector.create( - 'marker', - { - refX, - refY, - id: markerId, - overflow: 'visible', - orient: markerOrient != null ? markerOrient : 'auto', - markerUnits: markerUnits || 'userSpaceOnUse', - }, - children - ? children.map(({ tagName, ...other }) => - Vector.create( - `${tagName}` || 'path', - Dom.kebablizeAttrs({ - ...attrs, - ...other, - }), - ), - ) - : [Vector.create(tagName || 'path', Dom.kebablizeAttrs(attrs))], - ) - - this.defs.appendChild(pathMarker.node) - } - - return markerId - } - - remove(id: string) { - const elem = this.svg.getElementById(id) - if (elem && elem.parentNode) { - elem.parentNode.removeChild(elem) - } - } -} - -export namespace DefsManager { - export type MarkerOptions = Marker.Result - - export interface GradientOptions { - id?: string - type: string - stops: { - offset: number - color: string - opacity?: number - }[] - attrs?: Attr.SimpleAttrs - } - - export type FilterOptions = (Filter.NativeItem | Filter.ManaualItem) & { - id?: string - attrs?: Attr.SimpleAttrs - } -} diff --git a/packages/x6-core/src/renderer/highlight.ts b/packages/x6-core/src/renderer/highlight.ts deleted file mode 100644 index c6997bc8585..00000000000 --- a/packages/x6-core/src/renderer/highlight.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { Dom, KeyValue } from '@antv/x6-common' -import { CellView } from '../view' -import { Highlighter } from '../registry' -import { Renderer } from '../renderer' - -export class HighlightManager { - private renderer: Renderer - protected readonly highlights: KeyValue = {} - - constructor(renderer: Renderer) { - this.renderer = renderer - this.init() - } - - protected init() { - this.startListening() - } - - protected startListening() { - this.renderer.on('cell:highlight', this.onCellHighlight, this) - this.renderer.on('cell:unhighlight', this.onCellUnhighlight, this) - } - - protected stopListening() { - this.renderer.off('cell:highlight', this.onCellHighlight, this) - this.renderer.off('cell:unhighlight', this.onCellUnhighlight, this) - } - - protected onCellHighlight({ - view: cellView, - magnet, - options = {}, - }: Renderer.EventArgs['cell:highlight']) { - const resolved = this.resolveHighlighter(options) - if (!resolved) { - return - } - - const key = this.getHighlighterId(magnet, resolved) - if (!this.highlights[key]) { - const highlighter = resolved.highlighter - highlighter.highlight(cellView, magnet, { ...resolved.args }) - - this.highlights[key] = { - cellView, - magnet, - highlighter, - args: resolved.args, - } - } - } - - protected onCellUnhighlight({ - magnet, - options = {}, - }: Renderer.EventArgs['cell:unhighlight']) { - const resolved = this.resolveHighlighter(options) - if (!resolved) { - return - } - - const id = this.getHighlighterId(magnet, resolved) - this.unhighlight(id) - } - - protected resolveHighlighter(options: CellView.HighlightOptions) { - const rendererOptions = this.renderer.options - let highlighterDef: string | undefined | Highlighter.ManaualItem = - options.highlighter - - if (highlighterDef == null) { - // check for built-in types - const type = options.type - highlighterDef = - (type && rendererOptions.highlighting[type]) || - rendererOptions.highlighting.default - } - - if (highlighterDef == null) { - return null - } - - const def: Highlighter.ManaualItem = - typeof highlighterDef === 'string' - ? { - name: highlighterDef, - } - : highlighterDef - - const name = def.name - const highlighter = Highlighter.registry.get(name) - if (highlighter == null) { - return Highlighter.registry.onNotFound(name) - } - - Highlighter.check(name, highlighter) - - return { - name, - highlighter, - args: def.args || {}, - } - } - - protected getHighlighterId( - magnet: Element, - options: NonNullable< - ReturnType - >, - ) { - Dom.ensureId(magnet) - return options.name + magnet.id + JSON.stringify(options.args) - } - - protected unhighlight(id: string) { - const highlight = this.highlights[id] - if (highlight) { - highlight.highlighter.unhighlight( - highlight.cellView, - highlight.magnet, - highlight.args, - ) - - delete this.highlights[id] - } - } - - dispose() { - Object.keys(this.highlights).forEach((id) => this.unhighlight(id)) - this.stopListening() - } -} - -export namespace HighlightManager { - export interface Cache { - highlighter: Highlighter.Definition - cellView: CellView - magnet: Element - args: KeyValue - } - - export type Options = Highlighter.NativeItem | Highlighter.ManaualItem -} diff --git a/packages/x6-core/src/renderer/index.ts b/packages/x6-core/src/renderer/index.ts deleted file mode 100644 index a0764d34b6b..00000000000 --- a/packages/x6-core/src/renderer/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './renderer' -export * from './options' diff --git a/packages/x6-core/src/renderer/options.ts b/packages/x6-core/src/renderer/options.ts deleted file mode 100644 index c4cb9a4a913..00000000000 --- a/packages/x6-core/src/renderer/options.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { Dom, Nilable } from '@antv/x6-common' -import { Rectangle } from '@antv/x6-geometry' -import { View, CellView, NodeView, EdgeView } from '../view' -import { Model, Cell, Node, Edge } from '../model' -import { - Router, - Connector, - NodeAnchor, - EdgeAnchor, - ConnectionPoint, -} from '../registry' -import { Renderer } from '../renderer' -import { HighlightManager } from '../renderer/highlight' - -export namespace Options { - export interface Common { - container: HTMLElement - model?: Model - - moveThreshold: 0 - clickThreshold: number - magnetThreshold: number | 'onleave' - preventDefaultDblClick: boolean - preventDefaultContextMenu: boolean - preventDefaultMouseDown: boolean - preventDefaultBlankAction: boolean - - interacting: CellView.Interacting - - getGraphArea: () => Rectangle - getGridSize: () => number - guard: (e: Dom.EventObject, view?: CellView | null) => boolean - onToolItemCreated: (args: { - name: string - cell: Cell - view: CellView - tool: View - }) => void - } - export interface ManualBooleans { - embedding: boolean | Partial - } - export interface Manual extends Partial, Partial { - connecting?: Partial - translating?: Partial - highlighting?: Partial - } - export interface Definition extends Common { - embedding: Embedding - connecting: Connecting - translating: Translating - highlighting: Highlighting - } -} - -export namespace Options { - type OptionItem = S | ((this: Renderer, arg: T) => S) - - type NodeAnchorOptions = - | string - | NodeAnchor.NativeItem - | NodeAnchor.ManaualItem - type EdgeAnchorOptions = - | string - | EdgeAnchor.NativeItem - | EdgeAnchor.ManaualItem - type ConnectionPointOptions = - | string - | ConnectionPoint.NativeItem - | ConnectionPoint.ManaualItem - - export interface Connecting { - /** - * Snap edge to the closest node/port in the given radius on dragging. - */ - snap: boolean | { radius: number } - - /** - * Specify whether connect to point on the graph is allowed. - */ - allowBlank?: - | boolean - | ((this: Renderer, args: ValidateConnectionArgs) => boolean) - - /** - * When set to `false`, edges can not be connected to the same node, - * meaning the source and target of the edge can not be the same node. - */ - allowLoop: - | boolean - | ((this: Renderer, args: ValidateConnectionArgs) => boolean) - - /** - * Specify whether connect to node(not the port on the node) is allowed. - */ - allowNode: - | boolean - | ((this: Renderer, args: ValidateConnectionArgs) => boolean) - - /** - * Specify whether connect to edge is allowed. - */ - allowEdge: - | boolean - | ((this: Renderer, args: ValidateConnectionArgs) => boolean) - - /** - * Specify whether connect to port is allowed. - */ - allowPort: - | boolean - | ((this: Renderer, args: ValidateConnectionArgs) => boolean) - - /** - * Specify whether more than one edge connected to the same source and - * target node is allowed. - */ - allowMulti?: - | boolean - | 'withPort' - | ((this: Renderer, args: ValidateConnectionArgs) => boolean) - - /** - * Highlights all the available magnets or nodes when a edge is - * dragging(reconnecting). This gives a hint to the user to what - * other nodes/ports this edge can be connected. What magnets/cells - * are available is determined by the `validateConnection` function. - */ - highlight: boolean - - anchor: NodeAnchorOptions - sourceAnchor?: NodeAnchorOptions - targetAnchor?: NodeAnchorOptions - edgeAnchor: EdgeAnchorOptions - sourceEdgeAnchor?: EdgeAnchorOptions - targetEdgeAnchor?: EdgeAnchorOptions - - connectionPoint: ConnectionPointOptions - sourceConnectionPoint?: ConnectionPointOptions - targetConnectionPoint?: ConnectionPointOptions - - router: string | Router.NativeItem | Router.ManaualItem - connector: string | Connector.NativeItem | Connector.ManaualItem - - /** - * Check whether to add a new edge to the graph when user clicks - * on an a magnet. - */ - validateMagnet?: ( - this: Renderer, - args: { - cell: Cell - view: CellView - magnet: Element - e: Dom.MouseDownEvent | Dom.MouseEnterEvent - }, - ) => boolean - - createEdge?: ( - this: Renderer, - args: { - sourceCell: Cell - sourceView: CellView - sourceMagnet: Element - }, - ) => Nilable | void - - /** - * Custom validation on stop draggin the edge arrowhead(source/target). - * If the function returns `false`, the edge is either removed(edges - * which are created during the interaction) or reverted to the state - * before the interaction. - */ - validateEdge?: ( - this: Renderer, - args: { - edge: Edge - type: Edge.TerminalType - previous: Edge.TerminalData - }, - ) => boolean - - /** - * Check whether to allow or disallow the edge connection while an - * arrowhead end (source/target) being changed. - */ - validateConnection: ( - this: Renderer, - args: ValidateConnectionArgs, - ) => boolean - } - - export interface ValidateConnectionArgs { - type?: Edge.TerminalType | null - edge?: Edge | null - edgeView?: EdgeView | null - sourceCell?: Cell | null - targetCell?: Cell | null - sourceView?: CellView | null - targetView?: CellView | null - sourcePort?: string | null - targetPort?: string | null - sourceMagnet?: Element | null - targetMagnet?: Element | null - } - - export interface Translating { - /** - * Restrict the translation (movement) of nodes by a given bounding box. - * If set to `true`, the user will not be able to move nodes outside the - * boundary of the graph area. - */ - restrict: - | boolean - | OptionItem - } - - export interface Embedding { - enabled?: boolean - - /** - * Determines the way how a cell finds a suitable parent when it's dragged - * over the graph. The cell with the highest z-index (visually on the top) - * will be chosen. - */ - findParent?: - | 'bbox' - | 'center' - | 'topLeft' - | 'topRight' - | 'bottomLeft' - | 'bottomRight' - | ((this: Renderer, args: { node: Node; view: NodeView }) => Cell[]) - - /** - * If enabled only the node on the very front is taken into account for the - * embedding. If disabled the nodes under the dragged view are tested one by - * one (from front to back) until a valid parent found. - */ - frontOnly?: boolean - - /** - * Check whether to allow or disallow the node embedding while it's being - * translated. By default, all nodes can be embedded into all other nodes. - */ - validate: ( - this: Renderer, - args: { - child: Node - parent: Node - childView: CellView - parentView: CellView - }, - ) => boolean - } - - /** - * Configure which highlighter to use (and with which options) for - * each type of interaction. - */ - export interface Highlighting { - /** - * The default highlighter to use (and options) when none is specified - */ - default: HighlightManager.Options - /** - * When a cell is dragged over another cell in embedding mode. - */ - embedding?: HighlightManager.Options | null - /** - * When showing all nodes to which a valid connection can be made. - */ - nodeAvailable?: HighlightManager.Options | null - /** - * When showing all magnets to which a valid connection can be made. - */ - magnetAvailable?: HighlightManager.Options | null - /** - * When a valid edge connection can be made to an node. - */ - magnetAdsorbed?: HighlightManager.Options | null - } -} diff --git a/packages/x6-core/src/renderer/queueJob.ts b/packages/x6-core/src/renderer/queueJob.ts deleted file mode 100644 index b8ece943e09..00000000000 --- a/packages/x6-core/src/renderer/queueJob.ts +++ /dev/null @@ -1,137 +0,0 @@ -export interface Job { - id: string - priority: JOB_PRIORITY - cb: () => void -} - -export enum JOB_PRIORITY { - Manual = 1, - Render = 2, -} - -let isFlushing = false -let isFlushPending = false -let scheduleId = 0 -const queue: Job[] = [] -const frameInterval = 33 -let time = 0 - -let getCurrentTime: () => number -const hasPerformanceNow = - typeof performance === 'object' && typeof performance.now === 'function' - -if (hasPerformanceNow) { - const localPerformance = performance - getCurrentTime = () => localPerformance.now() -} else { - const localDate = Date - const initialTime = localDate.now() - getCurrentTime = () => localDate.now() - initialTime -} - -let scheduleJob: () => void -let cancelScheduleJob: () => void -if ('requestIdleCallback' in window) { - scheduleJob = () => { - if (scheduleId) { - cancelScheduleJob() - } - scheduleId = window.requestIdleCallback(flushJobs, { timeout: 100 }) - } - cancelScheduleJob = () => { - if (scheduleId) { - cancelIdleCallback(scheduleId) - } - scheduleId = 0 - } -} else { - scheduleJob = () => { - if (scheduleId) { - cancelScheduleJob() - } - scheduleId = window.setTimeout(flushJobs) - } - cancelScheduleJob = () => { - if (scheduleId) { - clearTimeout(scheduleId) - } - scheduleId = 0 - } -} - -export function queueJob(job: Job) { - const index = findInsertionIndex(job) - if (index >= 0) { - queue.splice(index, 0, job) - } -} - -export function queueFlush() { - if (!isFlushing && !isFlushPending) { - isFlushPending = true - scheduleJob() - } -} - -export function clearJobs() { - queue.length = 0 - isFlushing = false - isFlushPending = false - cancelScheduleJob() -} - -export function resetTimer() { - time = performance.now() -} - -function flushJobs() { - isFlushPending = false - isFlushing = true - - const startTime = getCurrentTime() - - let job - while ((job = queue.shift())) { - try { - job.cb() - } catch (error) { - // pass - } - if (getCurrentTime() - startTime >= frameInterval) { - break - } - } - - isFlushing = false - - if (queue.length) { - queueFlush() - } else { - console.log('spend', performance.now() - time) // eslint-disable-line - } -} - -function findInsertionIndex(job: Job) { - let start = 0 - while (queue[start] && queue[start].priority <= job.priority) { - start += 1 - } - return start -} - -// function findInsertionIndex(job: Job) { -// let start = 0 -// for (let i = 0, len = queue.length; i < len; i += 1) { -// const j = queue[i] -// if (j.id === job.id) { -// console.log('xx', j.bit, job.bit) -// } -// if (j.id === job.id && (job.bit ^ (job.bit & j.bit)) === 0) { -// return -1 -// } -// if (j.priority <= job.priority) { -// start += 1 -// } -// } -// return start -// } diff --git a/packages/x6-core/src/renderer/renderer.ts b/packages/x6-core/src/renderer/renderer.ts deleted file mode 100644 index ce7b1392131..00000000000 --- a/packages/x6-core/src/renderer/renderer.ts +++ /dev/null @@ -1,185 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { Point, Rectangle } from '@antv/x6-geometry' -import { Basecoat } from '../common' -import { Model, Cell } from '../model' -import { Options } from './options' -import { Scheduler } from './scheduler' -import { CellView, GraphView } from '../view' -import { HighlightManager } from './highlight' -import { DefsManager } from './defs' -import { CoordManager } from './coord' -import { Util } from '../util' - -export class Renderer extends Basecoat { - private readonly schedule: Scheduler - - public readonly options: Options.Definition - public readonly model: Model - public readonly graphView: GraphView - public readonly defs: DefsManager - public readonly highlight: HighlightManager - public readonly coord: CoordManager - - public get container() { - return this.options.container - } - - constructor(options: Options.Definition) { - super() - this.options = options - - this.graphView = new GraphView(this) - - if (this.options.model) { - this.model = this.options.model - } else { - this.model = new Model() - this.model.renderer = this - } - - this.defs = new DefsManager(this) - this.highlight = new HighlightManager(this) - this.coord = new CoordManager(this) - - this.schedule = new Scheduler(this) - } - - getCellById(id: string) { - return this.model.getCell(id) - } - - setRenderArea(area?: Rectangle) { - this.schedule.setRenderArea(area) - } - - defineFilter(options: DefsManager.FilterOptions) { - return this.defs.filter(options) - } - - defineGradient(options: DefsManager.GradientOptions) { - return this.defs.gradient(options) - } - - defineMarker(options: DefsManager.MarkerOptions) { - return this.defs.marker(options) - } - - snapToGrid(p: Point.PointLike): Point - snapToGrid(x: number, y: number): Point - snapToGrid(x: number | Point.PointLike, y?: number) { - return this.coord.snapToGrid(x, y) - } - - findViewByElem(elem: string | Element | undefined | null) { - if (elem == null) { - return null - } - const container = this.options.container - const target = - typeof elem === 'string' - ? container.querySelector(elem) - : elem instanceof Element - ? elem - : elem[0] - - if (target) { - const id = this.graphView.findAttr('data-cell-id', target) - if (id) { - const views = this.schedule.views - if (views[id]) { - return views[id].view - } - } - } - - return null - } - - findViewByCell(cellId: string | number): CellView | null - findViewByCell(cell: Cell | null): CellView | null - findViewByCell( - cell: Cell | string | number | null | undefined, - ): CellView | null { - if (cell == null) { - return null - } - const id = Cell.isCell(cell) ? cell.id : cell - const views = this.schedule.views - return views[id].view - } - - requestViewUpdate(view: CellView, flag: number, options: any = {}) { - this.schedule.requestViewUpdate(view, flag, options) - } - - isAsync() { - return false - } - - dumpView(view: CellView, options: any = {}) { - // todo - } - - isViewMounted(view: CellView) { - // todo - return true - } - - findViewsInArea( - rect: Rectangle.RectangleLike, - options: { strict?: boolean } = {}, - ) { - const area = Rectangle.create(rect) - return this.model - .getNodes() - .map((node) => this.findViewByCell(node)) - .filter((view) => { - if (view) { - const bbox = Util.getBBox(view.container as SVGElement, { - target: this.graphView.stage, - }) - return options.strict - ? area.containsRect(bbox) - : area.isIntersectWithRect(bbox) - } - return false - }) as CellView[] - } - - dispose() { - this.graphView.dispose() - this.schedule.dispose() - this.highlight.dispose() - } -} - -export namespace Renderer { - interface CommonEventArgs { - e: E - } - interface PositionEventArgs extends CommonEventArgs { - x: number - y: number - } - export interface EventArgs - extends Omit, - CellView.EventArgs { - 'model:sorted'?: Model.EventArgs['sorted'] - 'model:updated': Model.EventArgs['updated'] - 'model:reseted': Model.EventArgs['reseted'] - - 'blank:click': PositionEventArgs - 'blank:dblclick': PositionEventArgs - 'blank:contextmenu': PositionEventArgs - 'blank:mousedown': PositionEventArgs - 'blank:mousemove': PositionEventArgs - 'blank:mouseup': PositionEventArgs - 'blank:mouseout': CommonEventArgs - 'blank:mouseover': CommonEventArgs - 'graph:mouseenter': CommonEventArgs - 'graph:mouseleave': CommonEventArgs - 'blank:mousewheel': PositionEventArgs & { - delta: number - } - } -} diff --git a/packages/x6-core/src/renderer/scheduler.ts b/packages/x6-core/src/renderer/scheduler.ts deleted file mode 100644 index 8bd8f38f4f0..00000000000 --- a/packages/x6-core/src/renderer/scheduler.ts +++ /dev/null @@ -1,439 +0,0 @@ -import { KeyValue, Dom } from '@antv/x6-common' -import { Rectangle } from '@antv/x6-geometry' -import { Model, Cell } from '../model' -import { View, CellView, NodeView, EdgeView } from '../view' -import { - queueJob, - queueFlush, - clearJobs, - JOB_PRIORITY, - resetTimer, -} from './queueJob' -import { FlagManager } from '../view/flag' -import { Renderer } from '../renderer' - -export class Scheduler { - public views: KeyValue = {} - protected zPivots: KeyValue - private renderer: Renderer - private renderArea?: Rectangle - - get model() { - return this.renderer.model - } - - get container() { - return this.renderer.graphView.stage - } - - constructor(renderer: Renderer) { - this.renderer = renderer - this.init() - } - - protected init() { - this.startListening() - } - - protected startListening() { - this.model.on('reseted', this.onModelReseted, this) - this.model.on('cell:added', this.onCellAdded, this) - this.model.on('cell:removed', this.onCellRemoved, this) - this.model.on('cell:change:zIndex', this.onCellZIndexChanged, this) - this.model.on('cell:change:visible', this.onCellVisibleChanged, this) - } - - protected stopListening() { - this.model.off('reseted', this.onModelReseted, this) - this.model.off('cell:added', this.onCellAdded, this) - this.model.off('cell:removed', this.onCellRemoved, this) - this.model.off('cell:change:zIndex', this.onCellZIndexChanged, this) - this.model.off('cell:change:visible', this.onCellVisibleChanged, this) - } - - protected onModelReseted({ options }: Model.EventArgs['reseted']) { - clearJobs() - this.removeZPivots() - this.removeViews() - this.renderViews(this.model.getCells(), options) - } - - protected onCellAdded({ cell, options }: Model.EventArgs['cell:added']) { - this.renderViews([cell], options) - } - - protected onCellRemoved({ cell, options }: Model.EventArgs['cell:removed']) { - const viewItem = this.views[cell.id] - if (viewItem) { - const view = viewItem.view - this.requestViewUpdate(view, Scheduler.FLAG_REMOVE, options) - } - } - - protected onCellZIndexChanged({ - cell, - options, - }: Model.EventArgs['cell:change:zIndex']) { - const viewItem = this.views[cell.id] - if (viewItem) { - this.requestViewUpdate( - viewItem.view, - Scheduler.FLAG_INSERT, - options, - JOB_PRIORITY.Render, - true, - ) - } - } - - protected onCellVisibleChanged({ - cell, - current, - }: Model.EventArgs['cell:change:visible']) { - this.toggleVisible(cell, !!current) - } - - requestViewUpdate( - view: CellView, - flag: number, - options: any = {}, - priority: JOB_PRIORITY = JOB_PRIORITY.Manual, - flush = true, - ) { - const id = view.cell.id - const viewItem = this.views[id] - - if (!viewItem) { - return - } - - viewItem.flag = flag - viewItem.options = options - - queueJob({ - id, - priority, - cb: () => { - this.renderViewInArea(view, flag, options) - }, - }) - - const effectedEdges = this.getEffectedEdges(view) - effectedEdges.forEach((edge) => { - queueJob({ - id: edge.id, - priority, - cb: () => { - this.renderViewInArea(edge.view, edge.flag, options) - }, - }) - }) - - viewItem.state = Scheduler.ViewState.REQUESTED - - if (flush) { - queueFlush() - } - } - - setRenderArea(area?: Rectangle) { - this.renderArea = area - this.flushWaittingViews() - } - - protected renderViews(cells: Cell[], options: any = {}) { - cells.sort((c1, c2) => { - if (c1.isNode() && c2.isEdge()) { - return -1 - } - return 0 - }) - - cells.forEach((cell) => { - const id = cell.id - const views = this.views - let flag = 0 - let viewItem = views[id] - - if (viewItem) { - flag = Scheduler.FLAG_INSERT - } else { - const cellView = this.createCellView(cell) - if (cellView) { - cellView.renderer = this.renderer - flag = Scheduler.FLAG_INSERT | cellView.getBootstrapFlag() - viewItem = { - view: cellView, - flag, - options, - state: Scheduler.ViewState.CREATED, - } - this.views[id] = viewItem - } - } - - this.requestViewUpdate( - viewItem.view, - flag, - options, - JOB_PRIORITY.Render, - false, - ) - }) - - resetTimer() - queueFlush() - } - - protected renderViewInArea(view: CellView, flag: number, options: any = {}) { - const cell = view.cell - const id = cell.id - const viewItem = this.views[id] - - if (!viewItem) { - return - } - - let result = 0 - if (this.isInRenderArea(view)) { - result = this.updateView(view, flag, options) - viewItem.flag = result - } else { - if (viewItem.state === Scheduler.ViewState.MOUNTED) { - result = this.updateView(view, flag, options) - viewItem.flag = result - } else { - viewItem.state = Scheduler.ViewState.WAITTING - } - } - - if (result) { - console.log('left flag', result) // eslint-disable-line - } - } - - protected flushWaittingViews() { - const ids = Object.keys(this.views) - for (let i = 0, len = ids.length; i < len; i += 1) { - const viewItem = this.views[ids[i]] - if (viewItem && viewItem.state === Scheduler.ViewState.WAITTING) { - const { view, flag, options } = viewItem - this.requestViewUpdate(view, flag, options, JOB_PRIORITY.Render, false) - } - } - - resetTimer() - queueFlush() - } - - protected updateView(view: View, flag: number, options: any = {}) { - if (view == null) { - return 0 - } - - if (CellView.isCellView(view)) { - if (flag & Scheduler.FLAG_REMOVE) { - this.removeView(view.cell as any) - return 0 - } - - if (flag & Scheduler.FLAG_INSERT) { - this.insertView(view) - flag ^= Scheduler.FLAG_INSERT // eslint-disable-line - } - } - - if (!flag) { - return 0 - } - - return view.confirmUpdate(flag, options) - } - - protected insertView(view: CellView) { - const viewItem = this.views[view.cell.id] - if (viewItem) { - const zIndex = view.cell.getZIndex() - const pivot = this.addZPivot(zIndex) - this.container.insertBefore(view.container, pivot) - viewItem.state = Scheduler.ViewState.MOUNTED - } - } - - protected removeViews() { - Object.keys(this.views).forEach((id) => { - const viewItem = this.views[id] - if (viewItem) { - this.removeView(viewItem.view.cell) - } - }) - this.views = {} - } - - protected removeView(cell: Cell) { - const viewItem = this.views[cell.id] - if (viewItem) { - viewItem.view.remove() - delete this.views[cell.id] - } - return viewItem.view - } - - protected toggleVisible(cell: Cell, visible: boolean) { - const edges = this.model.getConnectedEdges(cell) - - for (let i = 0, len = edges.length; i < len; i += 1) { - const edge = edges[i] - if (visible) { - const source = edge.getSourceCell() - const target = edge.getTargetCell() - if ( - (source && !source.isVisible()) || - (target && !target.isVisible()) - ) { - continue - } - this.toggleVisible(edge, true) - } else { - this.toggleVisible(edge, false) - } - } - - const viewItem = this.views[cell.id] - if (viewItem) { - Dom.css(viewItem.view.container, { - display: visible ? 'unset' : 'none', - }) - } - } - - protected addZPivot(zIndex = 0) { - if (this.zPivots == null) { - this.zPivots = {} - } - - const pivots = this.zPivots - let pivot = pivots[zIndex] - if (pivot) { - return pivot - } - - pivot = pivots[zIndex] = document.createComment(`z-index:${zIndex + 1}`) - let neighborZ = -Infinity - // eslint-disable-next-line - for (const key in pivots) { - const currentZ = +key - if (currentZ < zIndex && currentZ > neighborZ) { - neighborZ = currentZ - if (neighborZ === zIndex - 1) { - continue - } - } - } - - const layer = this.container - if (neighborZ !== -Infinity) { - const neighborPivot = pivots[neighborZ] - layer.insertBefore(pivot, neighborPivot.nextSibling) - } else { - layer.insertBefore(pivot, layer.firstChild) - } - return pivot - } - - protected removeZPivots() { - if (this.zPivots) { - Object.keys(this.zPivots).forEach((z) => { - const elem = this.zPivots[z] - if (elem && elem.parentNode) { - elem.parentNode.removeChild(elem) - } - }) - } - this.zPivots = {} - } - - protected createCellView(cell: Cell) { - const options = { renderer: this.renderer } - - const view = cell.view - if (view != null && typeof view === 'string') { - const def = CellView.registry.get(view) - if (def) { - return new def(cell, options) // eslint-disable-line new-cap - } - - return CellView.registry.onNotFound(view) - } - - if (cell.isNode()) { - return new NodeView(cell, options) - } - - if (cell.isEdge()) { - return new EdgeView(cell, options) - } - - return null - } - - protected getEffectedEdges(view: CellView) { - const effectedEdges: { id: string; view: CellView; flag: number }[] = [] - const cell = view.cell - const edges = this.model.getConnectedEdges(cell) - - for (let i = 0, n = edges.length; i < n; i += 1) { - const edge = edges[i] - const viewItem = this.views[edge.id] - if (!viewItem) { - continue - } - const edgeView = viewItem.view - const flagLabels: FlagManager.Action[] = ['update'] - if (edge.getTargetCell() === cell) { - flagLabels.push('target') - } - if (edge.getSourceCell() === cell) { - flagLabels.push('source') - } - effectedEdges.push({ - id: edge.id, - view: edgeView, - flag: edgeView.getFlag(flagLabels), - }) - } - - return effectedEdges - } - - protected isInRenderArea(view: CellView) { - return ( - !this.renderArea || - this.renderArea.isIntersectWithRect(view.cell.getBBox()) - ) - } - - dispose() { - this.stopListening() - } -} -export namespace Scheduler { - export const FLAG_INSERT = 1 << 30 - export const FLAG_REMOVE = 1 << 29 - export const FLAG_RENDER = (1 << 26) - 1 -} - -export namespace Scheduler { - export enum ViewState { - CREATED, - MOUNTED, - REQUESTED, - WAITTING, - } - export interface View { - view: CellView - flag: number - options: any - state: ViewState - } -} diff --git a/packages/x6-core/src/util/index.ts b/packages/x6-core/src/util/index.ts deleted file mode 100644 index a78dae8adf6..00000000000 --- a/packages/x6-core/src/util/index.ts +++ /dev/null @@ -1,405 +0,0 @@ -import { - Point, - Line, - Rectangle, - Polyline, - Ellipse, - Path, -} from '@antv/x6-geometry' -import { Dom, PointData, PointLike } from '@antv/x6-common' - -export namespace Util { - const svgDocument = Dom.createSvgElement('svg') as SVGSVGElement - /** - * Transforms point by an SVG transformation represented by `matrix`. - */ - export function transformPoint(point: Point.PointLike, matrix: DOMMatrix) { - const ret = Dom.createSVGPoint(point.x, point.y).matrixTransform(matrix) - return new Point(ret.x, ret.y) - } - - /** - * Transforms line by an SVG transformation represented by `matrix`. - */ - export function transformLine(line: Line, matrix: DOMMatrix) { - return new Line( - transformPoint(line.start, matrix), - transformPoint(line.end, matrix), - ) - } - - /** - * Transforms polyline by an SVG transformation represented by `matrix`. - */ - export function transformPolyline(polyline: Polyline, matrix: DOMMatrix) { - let points = polyline instanceof Polyline ? polyline.points : polyline - if (!Array.isArray(points)) { - points = [] - } - - return new Polyline(points.map((p) => transformPoint(p, matrix))) - } - - export function transformRectangle( - rect: Rectangle.RectangleLike, - matrix: DOMMatrix, - ) { - const p = svgDocument.createSVGPoint() - - p.x = rect.x - p.y = rect.y - const corner1 = p.matrixTransform(matrix) - - p.x = rect.x + rect.width - p.y = rect.y - const corner2 = p.matrixTransform(matrix) - - p.x = rect.x + rect.width - p.y = rect.y + rect.height - const corner3 = p.matrixTransform(matrix) - - p.x = rect.x - p.y = rect.y + rect.height - const corner4 = p.matrixTransform(matrix) - - const minX = Math.min(corner1.x, corner2.x, corner3.x, corner4.x) - const maxX = Math.max(corner1.x, corner2.x, corner3.x, corner4.x) - const minY = Math.min(corner1.y, corner2.y, corner3.y, corner4.y) - const maxY = Math.max(corner1.y, corner2.y, corner3.y, corner4.y) - - return new Rectangle(minX, minY, maxX - minX, maxY - minY) - } - - /** - * Returns the bounding box of the element after transformations are - * applied. If `withoutTransformations` is `true`, transformations of - * the element will not be considered when computing the bounding box. - * If `target` is specified, bounding box will be computed relatively - * to the `target` element. - */ - export function bbox( - elem: SVGElement, - withoutTransformations?: boolean, - target?: SVGElement, - ): Rectangle { - let box - const ownerSVGElement = elem.ownerSVGElement - - // If the element is not in the live DOM, it does not have a bounding - // box defined and so fall back to 'zero' dimension element. - if (!ownerSVGElement) { - return new Rectangle(0, 0, 0, 0) - } - - try { - box = (elem as SVGGraphicsElement).getBBox() - } catch (e) { - // Fallback for IE. - box = { - x: elem.clientLeft, - y: elem.clientTop, - width: elem.clientWidth, - height: elem.clientHeight, - } - } - - if (withoutTransformations) { - return Rectangle.create(box) - } - - const matrix = Dom.getTransformToElement(elem, target || ownerSVGElement) - return transformRectangle(box, matrix) - } - - /** - * Returns the bounding box of the element after transformations are - * applied. Unlike `bbox()`, this function fixes a browser implementation - * bug to return the correct bounding box if this elemenent is a group of - * svg elements (if `options.recursive` is specified). - */ - export function getBBox( - elem: SVGElement, - options: { - target?: SVGElement | null - recursive?: boolean - } = {}, - ): Rectangle { - let outputBBox - const ownerSVGElement = elem.ownerSVGElement - - // If the element is not in the live DOM, it does not have a bounding box - // defined and so fall back to 'zero' dimension element. - // If the element is not an SVGGraphicsElement, we could not measure the - // bounding box either - if (!ownerSVGElement || !Dom.isSVGGraphicsElement(elem)) { - if (Dom.isHTMLElement(elem)) { - // If the element is a HTMLElement, return the position relative to the body - const { left, top, width, height } = getBoundingOffsetRect(elem as any) - return new Rectangle(left, top, width, height) - } - return new Rectangle(0, 0, 0, 0) - } - - let target = options.target - const recursive = options.recursive - - if (!recursive) { - try { - outputBBox = elem.getBBox() - } catch (e) { - outputBBox = { - x: elem.clientLeft, - y: elem.clientTop, - width: elem.clientWidth, - height: elem.clientHeight, - } - } - - if (!target) { - return Rectangle.create(outputBBox) - } - - // transform like target - const matrix = Dom.getTransformToElement(elem, target) - return transformRectangle(outputBBox, matrix) - } - - // recursive - { - const children = elem.childNodes - const n = children.length - - if (n === 0) { - return getBBox(elem, { - target, - }) - } - - if (!target) { - target = elem // eslint-disable-line - } - - for (let i = 0; i < n; i += 1) { - const child = children[i] as SVGElement - let childBBox - - if (child.childNodes.length === 0) { - childBBox = getBBox(child, { - target, - }) - } else { - // if child is a group element, enter it with a recursive call - childBBox = getBBox(child, { - target, - recursive: true, - }) - } - - if (!outputBBox) { - outputBBox = childBBox - } else { - outputBBox = outputBBox.union(childBBox) - } - } - - return outputBBox as Rectangle - } - } - - export function getBoundingOffsetRect(elem: HTMLElement) { - let left = 0 - let top = 0 - let width = 0 - let height = 0 - if (elem) { - let current = elem as any - while (current) { - left += current.offsetLeft - top += current.offsetTop - current = current.offsetParent - if (current) { - left += parseInt(Dom.getComputedStyle(current, 'borderLeft'), 10) - top += parseInt(Dom.getComputedStyle(current, 'borderTop'), 10) - } - } - width = elem.offsetWidth - height = elem.offsetHeight - } - return { - left, - top, - width, - height, - } - } - - /** - * Convert the SVGElement to an equivalent geometric shape. The element's - * transformations are not taken into account. - * - * SVGRectElement => Rectangle - * - * SVGLineElement => Line - * - * SVGCircleElement => Ellipse - * - * SVGEllipseElement => Ellipse - * - * SVGPolygonElement => Polyline - * - * SVGPolylineElement => Polyline - * - * SVGPathElement => Path - * - * others => Rectangle - */ - export function toGeometryShape(elem: SVGElement) { - const attr = (name: string) => { - const s = elem.getAttribute(name) - const v = s ? parseFloat(s) : 0 - return Number.isNaN(v) ? 0 : v - } - - switch (elem instanceof SVGElement && elem.nodeName.toLowerCase()) { - case 'rect': - return new Rectangle( - attr('x'), - attr('y'), - attr('width'), - attr('height'), - ) - case 'circle': - return new Ellipse(attr('cx'), attr('cy'), attr('r'), attr('r')) - case 'ellipse': - return new Ellipse(attr('cx'), attr('cy'), attr('rx'), attr('ry')) - case 'polyline': { - const points = Dom.getPointsFromSvgElement(elem as SVGPolylineElement) - return new Polyline(points) - } - case 'polygon': { - const points = Dom.getPointsFromSvgElement(elem as SVGPolygonElement) - if (points.length > 1) { - points.push(points[0]) - } - return new Polyline(points) - } - case 'path': { - let d = elem.getAttribute('d') as string - if (!Path.isValid(d)) { - d = Path.normalize(d) - } - return Path.parse(d) - } - case 'line': { - return new Line(attr('x1'), attr('y1'), attr('x2'), attr('y2')) - } - default: - break - } - - // Anything else is a rectangle - return getBBox(elem) - } - - export function translateAndAutoOrient( - elem: SVGElement, - position: PointLike | PointData, - reference: PointLike | PointData, - target?: SVGElement, - ) { - const pos = Point.create(position) - const ref = Point.create(reference) - - if (!target) { - const svg = elem instanceof SVGSVGElement ? elem : elem.ownerSVGElement! - target = svg // eslint-disable-line - } - - // Clean-up previously set transformations except the scale. - // If we didn't clean up the previous transformations then they'd - // add up with the old ones. Scale is an exception as it doesn't - // add up, consider: `this.scale(2).scale(2).scale(2)`. The result - // is that the element is scaled by the factor 2, not 8. - const s = Dom.scale(elem) - elem.setAttribute('transform', '') - const bbox = getBBox(elem, { - target, - }).scale(s.sx, s.sy) - - // 1. Translate to origin. - const translateToOrigin = Dom.createSVGTransform() - translateToOrigin.setTranslate( - -bbox.x - bbox.width / 2, - -bbox.y - bbox.height / 2, - ) - - // 2. Rotate around origin. - const rotateAroundOrigin = Dom.createSVGTransform() - const angle = pos.angleBetween(ref, pos.clone().translate(1, 0)) - if (angle) rotateAroundOrigin.setRotate(angle, 0, 0) - - // 3. Translate to the `position` + the offset (half my width) - // towards the `reference` point. - const translateFromOrigin = Dom.createSVGTransform() - const finalPosition = pos.clone().move(ref, bbox.width / 2) - translateFromOrigin.setTranslate( - 2 * pos.x - finalPosition.x, - 2 * pos.y - finalPosition.y, - ) - - // 4. Get the current transformation matrix of this node - const ctm = Dom.getTransformToElement(elem, target) - - // 5. Apply transformations and the scale - const transform = Dom.createSVGTransform() - transform.setMatrix( - translateFromOrigin.matrix.multiply( - rotateAroundOrigin.matrix.multiply( - translateToOrigin.matrix.multiply(ctm.scale(s.sx, s.sy)), - ), - ), - ) - - elem.setAttribute( - 'transform', - Dom.matrixToTransformString(transform.matrix), - ) - } - - export function findShapeNode(magnet: Element) { - if (magnet == null) { - return null - } - - let node = magnet - do { - let tagName = node.tagName - if (typeof tagName !== 'string') return null - tagName = tagName.toUpperCase() - if (tagName === 'G') { - node = node.firstElementChild as Element - } else if (tagName === 'TITLE') { - node = node.nextElementSibling as Element - } else break - } while (node) - - return node - } - - // BBox is calculated by the attribute and shape of the node. - // Because of the reduction in DOM API calls, there is a significant performance improvement. - export function getBBoxV2(elem: SVGElement) { - const node = findShapeNode(elem) - - if (!Dom.isSVGGraphicsElement(node)) { - if (Dom.isHTMLElement(elem)) { - const { left, top, width, height } = getBoundingOffsetRect(elem as any) - return new Rectangle(left, top, width, height) - } - return new Rectangle(0, 0, 0, 0) - } - - const shape = toGeometryShape(node) - return shape.bbox() || Rectangle.create() - } -} diff --git a/packages/x6-core/src/view/attr.ts b/packages/x6-core/src/view/attr.ts deleted file mode 100644 index 4812f733cff..00000000000 --- a/packages/x6-core/src/view/attr.ts +++ /dev/null @@ -1,510 +0,0 @@ -import { - ObjectExt, - ArrayExt, - Dom, - FunctionExt, - Dictionary, - StringExt, -} from '@antv/x6-common' -import { Rectangle, Point } from '@antv/x6-geometry' -import { Attr } from '../registry/attr' -import { View } from './view' -import { Markup } from './markup' -import { CellView } from './cell' -import { Util } from '../util' - -export class AttrManager { - constructor(protected view: CellView) {} - - protected get cell() { - return this.view.cell - } - - protected getDefinition(attrName: string): Attr.Definition | null { - return this.cell.getAttrDefinition(attrName) - } - - protected processAttrs( - elem: Element, - raw: Attr.ComplexAttrs, - ): AttrManager.ProcessedAttrs { - let normal: Attr.SimpleAttrs | undefined - let set: Attr.ComplexAttrs | undefined - let offset: Attr.ComplexAttrs | undefined - let position: Attr.ComplexAttrs | undefined - - const specials: { name: string; definition: Attr.Definition }[] = [] - - // divide the attributes between normal and special - Object.keys(raw).forEach((name) => { - const val = raw[name] - const definition = this.getDefinition(name) - const isValid = FunctionExt.call( - Attr.isValidDefinition, - this.view, - definition, - val, - { - elem, - attrs: raw, - cell: this.cell, - view: this.view, - }, - ) - - if (definition && isValid) { - if (typeof definition === 'string') { - if (normal == null) { - normal = {} - } - normal[definition] = val as Attr.SimpleAttrValue - } else if (val !== null) { - specials.push({ name, definition }) - } - } else { - if (normal == null) { - normal = {} - } - const normalName = StringExt.kebabCase(name) - normal[normalName] = val as Attr.SimpleAttrValue - } - }) - - specials.forEach(({ name, definition }) => { - const val = raw[name] - - const setDefine = definition as Attr.SetDefinition - if (typeof setDefine.set === 'function') { - if (set == null) { - set = {} - } - set[name] = val - } - - const offsetDefine = definition as Attr.OffsetDefinition - if (typeof offsetDefine.offset === 'function') { - if (offset == null) { - offset = {} - } - offset[name] = val - } - - const positionDefine = definition as Attr.PositionDefinition - if (typeof positionDefine.position === 'function') { - if (position == null) { - position = {} - } - position[name] = val - } - }) - - return { - raw, - normal, - set, - offset, - position, - } - } - - protected mergeProcessedAttrs( - allProcessedAttrs: AttrManager.ProcessedAttrs, - roProcessedAttrs: AttrManager.ProcessedAttrs, - ) { - allProcessedAttrs.set = { - ...allProcessedAttrs.set, - ...roProcessedAttrs.set, - } - - allProcessedAttrs.position = { - ...allProcessedAttrs.position, - ...roProcessedAttrs.position, - } - - allProcessedAttrs.offset = { - ...allProcessedAttrs.offset, - ...roProcessedAttrs.offset, - } - - // Handle also the special transform property. - const transform = - allProcessedAttrs.normal && allProcessedAttrs.normal.transform - if (transform != null && roProcessedAttrs.normal) { - roProcessedAttrs.normal.transform = transform - } - allProcessedAttrs.normal = roProcessedAttrs.normal - } - - protected findAttrs( - cellAttrs: Attr.CellAttrs, - rootNode: Element, - selectorCache: { [selector: string]: Element[] }, - selectors: Markup.Selectors, - ) { - const merge: Element[] = [] - const result: Dictionary< - Element, - { - elem: Element - array: boolean - priority: number | number[] - attrs: Attr.ComplexAttrs | Attr.ComplexAttrs[] - } - > = new Dictionary() - - Object.keys(cellAttrs).forEach((selector) => { - const attrs = cellAttrs[selector] - if (!ObjectExt.isPlainObject(attrs)) { - return - } - - const { isCSSSelector, elems } = View.find(selector, rootNode, selectors) - selectorCache[selector] = elems - for (let i = 0, l = elems.length; i < l; i += 1) { - const elem = elems[i] - const unique = selectors && selectors[selector] === elem - const prev = result.get(elem) - if (prev) { - if (!prev.array) { - merge.push(elem) - prev.array = true - prev.attrs = [prev.attrs as Attr.ComplexAttrs] - prev.priority = [prev.priority as number] - } - - const attributes = prev.attrs as Attr.ComplexAttrs[] - const selectedLength = prev.priority as number[] - if (unique) { - // node referenced by `selector` - attributes.unshift(attrs) - selectedLength.unshift(-1) - } else { - // node referenced by `groupSelector` or CSSSelector - const sortIndex = ArrayExt.sortedIndex( - selectedLength, - isCSSSelector ? -1 : l, - ) - - attributes.splice(sortIndex, 0, attrs) - selectedLength.splice(sortIndex, 0, l) - } - } else { - result.set(elem, { - elem, - attrs, - priority: unique ? -1 : l, - array: false, - }) - } - } - }) - - merge.forEach((node) => { - const item = result.get(node)! - const arr = item.attrs as Attr.ComplexAttrs[] - item.attrs = arr.reduceRight( - (memo, attrs) => ObjectExt.merge(memo, attrs), - {}, - ) - }) - - return result as Dictionary< - Element, - { - elem: Element - array: boolean - priority: number | number[] - attrs: Attr.ComplexAttrs - } - > - } - - protected updateRelativeAttrs( - elem: Element, - processedAttrs: AttrManager.ProcessedAttrs, - refBBox: Rectangle, - ) { - const rawAttrs = processedAttrs.raw || {} - let nodeAttrs = processedAttrs.normal || {} - const setAttrs = processedAttrs.set - const positionAttrs = processedAttrs.position - const offsetAttrs = processedAttrs.offset - const getOptions = () => ({ - elem, - cell: this.cell, - view: this.view, - attrs: rawAttrs, - refBBox: refBBox.clone(), - }) - - if (setAttrs != null) { - Object.keys(setAttrs).forEach((name) => { - const val = setAttrs[name] - const def = this.getDefinition(name) - if (def != null) { - const ret = FunctionExt.call( - (def as Attr.SetDefinition).set, - this.view, - val, - getOptions(), - ) - if (typeof ret === 'object') { - nodeAttrs = { - ...nodeAttrs, - ...ret, - } - } else if (ret != null) { - nodeAttrs[name] = ret - } - } - }) - } - - if (elem instanceof HTMLElement) { - // TODO: setting the `transform` attribute on HTMLElements - // via `node.style.transform = 'matrix(...)';` would introduce - // a breaking change (e.g. basic.TextBlock). - this.view.setAttrs(nodeAttrs, elem) - return - } - - // The final translation of the subelement. - const nodeTransform = nodeAttrs.transform - const transform = nodeTransform ? `${nodeTransform}` : null - const nodeMatrix = Dom.transformStringToMatrix(transform) - const nodePosition = new Point(nodeMatrix.e, nodeMatrix.f) - if (nodeTransform) { - delete nodeAttrs.transform - nodeMatrix.e = 0 - nodeMatrix.f = 0 - } - - let positioned = false - if (positionAttrs != null) { - Object.keys(positionAttrs).forEach((name) => { - const val = positionAttrs[name] - const def = this.getDefinition(name) - if (def != null) { - const ts = FunctionExt.call( - (def as Attr.PositionDefinition).position, - this.view, - val, - getOptions(), - ) - - if (ts != null) { - positioned = true - nodePosition.translate(Point.create(ts)) - } - } - }) - } - - // The node bounding box could depend on the `size` - // set from the previous loop. - this.view.setAttrs(nodeAttrs, elem) - - let offseted = false - if (offsetAttrs != null) { - // Check if the node is visible - const nodeBoundingRect = this.view.getBoundingRectOfElement(elem) - if (nodeBoundingRect.width > 0 && nodeBoundingRect.height > 0) { - const nodeBBox = Util.transformRectangle(nodeBoundingRect, nodeMatrix) - - Object.keys(offsetAttrs).forEach((name) => { - const val = offsetAttrs[name] - const def = this.getDefinition(name) - if (def != null) { - const ts = FunctionExt.call( - (def as Attr.OffsetDefinition).offset, - this.view, - val, - { - elem, - cell: this.cell, - view: this.view, - attrs: rawAttrs, - refBBox: nodeBBox, - }, - ) - - if (ts != null) { - offseted = true - nodePosition.translate(Point.create(ts)) - } - } - }) - } - } - - if (nodeTransform != null || positioned || offseted) { - nodePosition.round(1) - nodeMatrix.e = nodePosition.x - nodeMatrix.f = nodePosition.y - elem.setAttribute('transform', Dom.matrixToTransformString(nodeMatrix)) - } - } - - update( - rootNode: Element, - attrs: Attr.CellAttrs, - options: AttrManager.UpdateOptions, - ) { - const selectorCache: { [selector: string]: Element[] } = {} - const nodesAttrs = this.findAttrs( - options.attrs || attrs, - rootNode, - selectorCache, - options.selectors, - ) - - // `nodesAttrs` are different from all attributes, when - // rendering only attributes sent to this method. - const nodesAllAttrs = options.attrs - ? this.findAttrs(attrs, rootNode, selectorCache, options.selectors) - : nodesAttrs - - const specialItems: { - node: Element - refNode: Element | null - attributes: Attr.ComplexAttrs | null - processedAttributes: AttrManager.ProcessedAttrs - }[] = [] - - nodesAttrs.each((data) => { - const node = data.elem - const nodeAttrs = data.attrs - const processed = this.processAttrs(node, nodeAttrs) - if ( - processed.set == null && - processed.position == null && - processed.offset == null - ) { - this.view.setAttrs(processed.normal, node) - } else { - const data = nodesAllAttrs.get(node) - const nodeAllAttrs = data ? data.attrs : null - const refSelector = - nodeAllAttrs && nodeAttrs.ref == null - ? nodeAllAttrs.ref - : nodeAttrs.ref - - let refNode: Element | null - if (refSelector) { - refNode = (selectorCache[refSelector as string] || - this.view.find( - refSelector as string, - rootNode, - options.selectors, - ))[0] - if (!refNode) { - throw new Error(`"${refSelector}" reference does not exist.`) - } - } else { - refNode = null - } - - const item = { - node, - refNode, - attributes: nodeAllAttrs, - processedAttributes: processed, - } - - // If an element in the list is positioned relative to this one, then - // we want to insert this one before it in the list. - const index = specialItems.findIndex((item) => item.refNode === node) - if (index > -1) { - specialItems.splice(index, 0, item) - } else { - specialItems.push(item) - } - } - }) - - const bboxCache: Dictionary = new Dictionary() - let rotatableMatrix: DOMMatrix - specialItems.forEach((item) => { - const node = item.node - const refNode = item.refNode - - let unrotatedRefBBox: Rectangle | undefined - const isRefNodeRotatable = - refNode != null && - options.rotatableNode != null && - Dom.contains(options.rotatableNode, refNode) - - // Find the reference element bounding box. If no reference was - // provided, we use the optional bounding box. - if (refNode) { - unrotatedRefBBox = bboxCache.get(refNode) - } - - if (!unrotatedRefBBox) { - const target = ( - isRefNodeRotatable ? options.rotatableNode! : rootNode - ) as SVGElement - - unrotatedRefBBox = refNode - ? Util.getBBox(refNode as SVGElement, { target }) - : options.rootBBox - - if (refNode) { - bboxCache.set(refNode, unrotatedRefBBox!) - } - } - - let processedAttrs - if (options.attrs && item.attributes) { - // If there was a special attribute affecting the position amongst - // passed-in attributes we have to merge it with the rest of the - // element's attributes as they are necessary to update the position - // relatively (i.e `ref-x` && 'ref-dx'). - processedAttrs = this.processAttrs(node, item.attributes) - this.mergeProcessedAttrs(processedAttrs, item.processedAttributes) - } else { - processedAttrs = item.processedAttributes - } - - let refBBox = unrotatedRefBBox! - if ( - isRefNodeRotatable && - options.rotatableNode != null && - !options.rotatableNode.contains(node) - ) { - // If the referenced node is inside the rotatable group while the - // updated node is outside, we need to take the rotatable node - // transformation into account. - if (!rotatableMatrix) { - rotatableMatrix = Dom.transformStringToMatrix( - Dom.attr(options.rotatableNode, 'transform'), - ) - } - refBBox = Util.transformRectangle(unrotatedRefBBox!, rotatableMatrix) - } - - this.updateRelativeAttrs(node, processedAttrs, refBBox) - }) - } -} - -export namespace AttrManager { - export interface UpdateOptions { - rootBBox: Rectangle - selectors: Markup.Selectors - scalableNode?: Element | null - rotatableNode?: Element | null - /** - * Rendering only the specified attributes. - */ - attrs?: Attr.CellAttrs | null - } - - export interface ProcessedAttrs { - raw: Attr.ComplexAttrs - normal?: Attr.SimpleAttrs | undefined - set?: Attr.ComplexAttrs | undefined - offset?: Attr.ComplexAttrs | undefined - position?: Attr.ComplexAttrs | undefined - } -} diff --git a/packages/x6-core/src/view/cache.ts b/packages/x6-core/src/view/cache.ts deleted file mode 100644 index 5a9869c74a1..00000000000 --- a/packages/x6-core/src/view/cache.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Dictionary, JSONObject, Dom } from '@antv/x6-common' -import { - Line, - Rectangle, - Ellipse, - Polyline, - Path, - Segment, -} from '@antv/x6-geometry' -import { Util } from '../util' -import { CellView } from './cell' - -export class Cache { - protected elemCache: Dictionary - - public pathCache: { - data?: string - length?: number - segmentSubdivisions?: Segment[][] - } - - constructor(protected view: CellView) { - this.clean() - } - - clean() { - if (this.elemCache) { - this.elemCache.dispose() - } - this.elemCache = new Dictionary() - this.pathCache = {} - } - - get(elem: Element) { - const cache = this.elemCache - if (!cache.has(elem)) { - this.elemCache.set(elem, {}) - } - return this.elemCache.get(elem)! - } - - getData(elem: Element) { - const meta = this.get(elem) - if (!meta.data) { - meta.data = {} - } - return meta.data - } - - getMatrix(elem: Element) { - const meta = this.get(elem) - if (meta.matrix == null) { - const target = this.view.container - meta.matrix = Dom.getTransformToParentElement( - elem as any, - target as SVGElement, - ) - } - - return Dom.createSVGMatrix(meta.matrix) - } - - getShape(elem: Element) { - const meta = this.get(elem) - if (meta.shape == null) { - meta.shape = Util.toGeometryShape(elem as SVGElement) - } - return meta.shape.clone() - } - - getBoundingRect(elem: Element) { - const meta = this.get(elem) - if (meta.boundingRect == null) { - meta.boundingRect = Util.getBBoxV2(elem as SVGElement) - } - return meta.boundingRect.clone() - } -} - -export namespace Cache { - export interface Item { - data?: JSONObject - matrix?: DOMMatrix - boundingRect?: Rectangle - shape?: Rectangle | Ellipse | Polyline | Path | Line - } -} diff --git a/packages/x6-core/src/view/cell.ts b/packages/x6-core/src/view/cell.ts deleted file mode 100644 index 69abcce4b99..00000000000 --- a/packages/x6-core/src/view/cell.ts +++ /dev/null @@ -1,981 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { Rectangle } from '@antv/x6-geometry' -import { - ArrayExt, - ObjectExt, - Dom, - FunctionExt, - Registry, - Nilable, - KeyValue, -} from '@antv/x6-common' -import { View } from './view' -import { Cache } from './cache' -import { Markup } from './markup' -import { ToolsView } from './tool' -import { AttrManager } from './attr' -import { FlagManager } from './flag' -import { Util } from '../util' -import { Attr } from '../registry/attr' -import { Cell } from '../model/cell' -import { Edge } from '../model/edge' -import { Model } from '../model/model' -import { EdgeView } from './edge' -import { NodeView } from './node' -import { Renderer } from '../renderer' - -export class CellView< - Entity extends Cell = Cell, - Options extends CellView.Options = CellView.Options, -> extends View { - protected static defaults: Partial = { - isSvgElement: true, - rootSelector: 'root', - priority: 0, - bootstrap: [], - actions: {}, - } - - public static getDefaults() { - return this.defaults - } - - public static config( - options: Partial, - ) { - this.defaults = this.getOptions(options) - } - - public static getOptions( - options: Partial, - ): T { - const mergeActions = (arr1: T | T[], arr2?: T | T[]) => { - if (arr2 != null) { - return ArrayExt.uniq([ - ...(Array.isArray(arr1) ? arr1 : [arr1]), - ...(Array.isArray(arr2) ? arr2 : [arr2]), - ]) - } - return Array.isArray(arr1) ? [...arr1] : [arr1] - } - - const ret = ObjectExt.cloneDeep(this.getDefaults()) as T - const { bootstrap, actions, events, documentEvents, ...others } = options - - if (bootstrap) { - ret.bootstrap = mergeActions(ret.bootstrap, bootstrap) - } - - if (actions) { - Object.keys(actions).forEach((key) => { - const val = actions[key] - const raw = ret.actions[key] - if (val && raw) { - ret.actions[key] = mergeActions(raw, val) - } else if (val) { - ret.actions[key] = mergeActions(val) - } - }) - } - - if (events) { - ret.events = { ...ret.events, ...events } - } - - if (options.documentEvents) { - ret.documentEvents = { ...ret.documentEvents, ...documentEvents } - } - - return ObjectExt.merge(ret, others) as T - } - - public renderer: Renderer - public cell: Entity - protected selectors: Markup.Selectors - protected readonly options: Options - protected readonly flag: FlagManager - protected readonly attr: AttrManager - protected readonly cache: Cache - - protected get [Symbol.toStringTag]() { - return CellView.toStringTag - } - - constructor(cell: Entity, options: Partial = {}) { - super() - - this.cell = cell - this.options = this.ensureOptions(options) - this.renderer = this.options.renderer - this.attr = new AttrManager(this) - this.flag = new FlagManager( - this, - this.options.actions, - this.options.bootstrap, - ) - this.cache = new Cache(this) - - this.setContainer(this.ensureContainer()) - this.setup() - - this.init() - } - - protected init() {} - - protected onRemove() { - this.removeTools() - } - - public get priority() { - return this.options.priority - } - - protected get rootSelector() { - return this.options.rootSelector - } - - protected getConstructor() { - return this.constructor as any as T - } - - protected ensureOptions(options: Partial) { - return this.getConstructor().getOptions(options) as Options - } - - protected getContainerTagName(): string { - return this.options.isSvgElement ? 'g' : 'div' - } - - protected getContainerStyle(): Nilable< - Record - > | void {} - - protected getContainerAttrs(): Nilable { - return { - 'data-cell-id': this.cell.id, - 'data-shape': this.cell.shape, - } - } - - protected getContainerClassName(): Nilable { - return this.prefixClassName('cell') - } - - protected ensureContainer() { - return View.createElement( - this.getContainerTagName(), - this.options.isSvgElement, - ) - } - - protected setContainer(container: Element) { - if (this.container !== container) { - this.undelegateEvents() - this.container = container - - if (this.options.events != null) { - this.delegateEvents(this.options.events) - } - - const attrs = this.getContainerAttrs() - if (attrs != null) { - this.setAttrs(attrs, container) - } - - const style = this.getContainerStyle() - if (style != null) { - this.setStyle(style, container) - } - - const className = this.getContainerClassName() - if (className != null) { - this.addClass(className, container) - } - } - - return this - } - - isNodeView(): this is NodeView { - return false - } - - isEdgeView(): this is EdgeView { - return false - } - - render() { - return this - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - confirmUpdate(flag: number, options: any = {}) { - return 0 - } - - getBootstrapFlag() { - return this.flag.getBootstrapFlag() - } - - getFlag(actions: FlagManager.Actions) { - return this.flag.getFlag(actions) - } - - hasAction(flag: number, actions: FlagManager.Actions) { - return this.flag.hasAction(flag, actions) - } - - removeAction(flag: number, actions: FlagManager.Actions) { - return this.flag.removeAction(flag, actions) - } - - handleAction( - flag: number, - action: FlagManager.Action, - handle: () => void, - additionalRemovedActions?: FlagManager.Actions | null, - ) { - if (this.hasAction(flag, action)) { - handle() - const removedFlags = [action] - if (additionalRemovedActions) { - if (typeof additionalRemovedActions === 'string') { - removedFlags.push(additionalRemovedActions) - } else { - removedFlags.push(...additionalRemovedActions) - } - } - return this.removeAction(flag, removedFlags) - } - return flag - } - - protected setup() { - this.cell.on('changed', ({ options }) => this.onAttrsChange(options)) - } - - protected onAttrsChange(options: Cell.MutateOptions) { - let flag = this.flag.getChangedFlag() - if (options.updated || !flag) { - return - } - - if (options.dirty && this.hasAction(flag, 'update')) { - flag |= this.getFlag('render') // eslint-disable-line no-bitwise - } - - // tool changes should be sync render - if (options.toolId) { - options.async = false - } - - if (this.renderer != null) { - this.renderer.requestViewUpdate(this, flag, options) - } - } - - parseJSONMarkup( - markup: Markup.JSONMarkup | Markup.JSONMarkup[], - rootElem?: Element, - ) { - const result = Markup.parseJSONMarkup(markup) - const selectors = result.selectors - const rootSelector = this.rootSelector - if (rootElem && rootSelector) { - if (selectors[rootSelector]) { - throw new Error('Invalid root selector') - } - selectors[rootSelector] = rootElem - } - return result - } - - can(feature: CellView.InteractionNames): boolean { - let interacting = this.renderer.options.interacting - - if (typeof interacting === 'function') { - interacting = FunctionExt.call(interacting, this, this) - } - - if (typeof interacting === 'object') { - let val = interacting[feature] - if (typeof val === 'function') { - val = FunctionExt.call(val, this, this) - } - return val !== false - } - - if (typeof interacting === 'boolean') { - return interacting - } - - return false - } - - cleanCache() { - this.cache.clean() - return this - } - - getCache(elem: Element) { - return this.cache.get(elem) - } - - getDataOfElement(elem: Element) { - return this.cache.getData(elem) - } - - getMatrixOfElement(elem: Element) { - return this.cache.getMatrix(elem) - } - - getShapeOfElement(elem: SVGElement) { - return this.cache.getShape(elem) - } - - getBoundingRectOfElement(elem: Element) { - return this.cache.getBoundingRect(elem) - } - - getBBoxOfElement(elem: Element) { - const rect = this.getBoundingRectOfElement(elem) - const matrix = this.getMatrixOfElement(elem) - const rm = this.getRootRotatedMatrix() - const tm = this.getRootTranslatedMatrix() - return Util.transformRectangle(rect, tm.multiply(rm).multiply(matrix)) - } - - getUnrotatedBBoxOfElement(elem: SVGElement) { - const rect = this.getBoundingRectOfElement(elem) - const matrix = this.getMatrixOfElement(elem) - const tm = this.getRootTranslatedMatrix() - return Util.transformRectangle(rect, tm.multiply(matrix)) - } - - getBBox(options: { useCellGeometry?: boolean } = {}) { - let bbox - if (options.useCellGeometry) { - const cell = this.cell - const angle = cell.isNode() ? cell.getAngle() : 0 - bbox = cell.getBBox().bbox(angle) - } else { - bbox = this.getBBoxOfElement(this.container) - } - - return this.renderer.coord.localToGraphRect(bbox) - } - - getRootTranslatedMatrix() { - const cell = this.cell - const pos = cell.isNode() ? cell.getPosition() : { x: 0, y: 0 } - return Dom.createSVGMatrix().translate(pos.x, pos.y) - } - - getRootRotatedMatrix() { - let matrix = Dom.createSVGMatrix() - const cell = this.cell - const angle = cell.isNode() ? cell.getAngle() : 0 - if (angle) { - const bbox = cell.getBBox() - const cx = bbox.width / 2 - const cy = bbox.height / 2 - matrix = matrix.translate(cx, cy).rotate(angle).translate(-cx, -cy) - } - return matrix - } - - findMagnet(elem: Element = this.container) { - return this.findByAttr('magnet', elem) - } - - updateAttrs( - rootNode: Element, - attrs: Attr.CellAttrs, - options: Partial = {}, - ) { - if (options.rootBBox == null) { - options.rootBBox = new Rectangle() - } - - if (options.selectors == null) { - options.selectors = this.selectors - } - - this.attr.update(rootNode, attrs, options as AttrManager.UpdateOptions) - } - - isEdgeElement(magnet?: Element | null) { - return this.cell.isEdge() && (magnet == null || magnet === this.container) - } - - // #region highlight - - protected prepareHighlight( - elem?: Element | null, - options: CellView.HighlightOptions = {}, - ) { - const magnet = elem || this.container - options.partial = magnet === this.container - return magnet - } - - highlight(elem?: Element | null, options: CellView.HighlightOptions = {}) { - const magnet = this.prepareHighlight(elem, options) - this.notify('cell:highlight', { - magnet, - options, - view: this, - cell: this.cell, - }) - if (this.isEdgeView()) { - this.notify('edge:highlight', { - magnet, - options, - view: this, - edge: this.cell, - cell: this.cell, - }) - } else if (this.isNodeView()) { - this.notify('node:highlight', { - magnet, - options, - view: this, - node: this.cell, - cell: this.cell, - }) - } - return this - } - - unhighlight(elem?: Element | null, options: CellView.HighlightOptions = {}) { - const magnet = this.prepareHighlight(elem, options) - this.notify('cell:unhighlight', { - magnet, - options, - view: this, - cell: this.cell, - }) - if (this.isNodeView()) { - this.notify('node:unhighlight', { - magnet, - options, - view: this, - node: this.cell, - cell: this.cell, - }) - } else if (this.isEdgeView()) { - this.notify('edge:unhighlight', { - magnet, - options, - view: this, - edge: this.cell, - cell: this.cell, - }) - } - return this - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - notifyUnhighlight(magnet: Element, options: CellView.HighlightOptions) {} - - // #endregion - - getEdgeTerminal( - magnet: Element, - x: number, - y: number, - edge: Edge, - type: Edge.TerminalType, - ) { - const cell = this.cell - const portId = this.findAttr('port', magnet) - const selector = magnet.getAttribute('data-selector') - const terminal: Edge.TerminalCellData = { cell: cell.id } - - if (selector != null) { - terminal.magnet = selector - } - - if (portId != null) { - terminal.port = portId - if (cell.isNode()) { - if (!cell.hasPort(portId) && selector == null) { - // port created via the `port` attribute (not API) - terminal.selector = this.getSelector(magnet) - } - } - } else if (selector == null && this.container !== magnet) { - terminal.selector = this.getSelector(magnet) - } - - return terminal - } - - getMagnetFromEdgeTerminal(terminal: Edge.TerminalData) { - const cell = this.cell - const root = this.container - const portId = (terminal as Edge.TerminalCellData).port - let selector = terminal.magnet - let magnet - if (portId != null && cell.isNode() && cell.hasPort(portId)) { - magnet = (this as any).findPortElem(portId, selector) || root - } else { - if (!selector) { - selector = terminal.selector - } - if (!selector && portId != null) { - selector = `[port="${portId}"]` - } - magnet = this.findOne(selector, root, this.selectors) - } - - return magnet - } - - // #region animate todo - - // animate(elem: SVGElement | string, options: Dom.AnimationOptions) { - // const target = typeof elem === 'string' ? this.findOne(elem) : elem - // if (target == null) { - // throw new Error('Invalid animation element.') - // } - - // const parent = target.parentNode - // const revert = () => { - // if (!parent) { - // Dom.remove(target) - // } - // } - - // const vTarget = Vector.create(target as SVGElement) - // if (!parent) { - // vTarget.appendTo(this.graph.view.stage) - // } - - // const onComplete = options.complete - // options.complete = (e: Event) => { - // revert() - - // if (onComplete) { - // onComplete(e) - // } - // } - - // return vTarget.animate(options) - // } - - // animateTransform(elem: SVGElement | string, options: Dom.AnimationOptions) { - // const target = typeof elem === 'string' ? this.findOne(elem) : elem - // if (target == null) { - // throw new Error('Invalid animation element.') - // } - - // const parent = target.parentNode - // const revert = () => { - // if (!parent) { - // Dom.remove(target) - // } - // } - - // const vTarget = Vector.create(target as SVGElement) - // if (!parent) { - // vTarget.appendTo(this.graph.view.stage) - // } - - // const onComplete = options.complete - // options.complete = (e: Event) => { - // revert() - - // if (onComplete) { - // onComplete(e) - // } - // } - - // return vTarget.animateTransform(options) - // } - - // #endregion - - // #region tools - - protected tools: ToolsView | null - - hasTools(name?: string) { - const tools = this.tools - if (tools == null) { - return false - } - - if (name == null) { - return true - } - - return tools.name === name - } - - addTools(options: ToolsView.Options | null): this - addTools(tools: ToolsView | null): this - addTools(config: ToolsView | ToolsView.Options | null) { - if (!this.can('toolsAddable')) { - return this - } - this.removeTools() - if (config) { - const tools = ToolsView.isToolsView(config) - ? config - : new ToolsView(config) - this.tools = tools - tools.config({ view: this }) - tools.mount() - } - return this - } - - updateTools(options: ToolsView.UpdateOptions = {}) { - if (this.tools) { - this.tools.update(options) - } - return this - } - - removeTools() { - if (this.tools) { - this.tools.remove() - this.tools = null - } - return this - } - - hideTools() { - if (this.tools) { - this.tools.hide() - } - return this - } - - showTools() { - if (this.tools) { - this.tools.show() - } - return this - } - - protected renderTools() { - const tools = this.cell.getTools() - this.addTools(tools as ToolsView.Options) - return this - } - - // #endregion - - // #region events - - notify( - name: Key, - args: CellView.EventArgs[Key], - ): this - notify(name: Exclude, args: any): this - notify( - name: Key, - args: CellView.EventArgs[Key], - ) { - this.trigger(name, args) - // todo - this.renderer.trigger(name, args) - return this - } - - protected getEventArgs(e: E): CellView.MouseEventArgs - protected getEventArgs( - e: E, - x: number, - y: number, - ): CellView.MousePositionEventArgs - protected getEventArgs(e: E, x?: number, y?: number) { - const view = this // eslint-disable-line @typescript-eslint/no-this-alias - const cell = view.cell - if (x == null || y == null) { - return { e, view, cell } as CellView.MouseEventArgs - } - return { e, x, y, view, cell } as CellView.MousePositionEventArgs - } - - onClick(e: Dom.ClickEvent, x: number, y: number) { - this.notify('cell:click', this.getEventArgs(e, x, y)) - } - - onDblClick(e: Dom.DoubleClickEvent, x: number, y: number) { - this.notify('cell:dblclick', this.getEventArgs(e, x, y)) - } - - onContextMenu(e: Dom.ContextMenuEvent, x: number, y: number) { - this.notify('cell:contextmenu', this.getEventArgs(e, x, y)) - } - - protected cachedModelForMouseEvent: Model | null - - onMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - if (this.cell.model) { - this.cachedModelForMouseEvent = this.cell.model - this.cachedModelForMouseEvent.startBatch('mouse') - } - - this.notify('cell:mousedown', this.getEventArgs(e, x, y)) - } - - onMouseUp(e: Dom.MouseUpEvent, x: number, y: number) { - this.notify('cell:mouseup', this.getEventArgs(e, x, y)) - - if (this.cachedModelForMouseEvent) { - this.cachedModelForMouseEvent.stopBatch('mouse', { cell: this.cell }) - this.cachedModelForMouseEvent = null - } - } - - onMouseMove(e: Dom.MouseMoveEvent, x: number, y: number) { - this.notify('cell:mousemove', this.getEventArgs(e, x, y)) - } - - onMouseOver(e: Dom.MouseOverEvent) { - this.notify('cell:mouseover', this.getEventArgs(e)) - } - - onMouseOut(e: Dom.MouseOutEvent) { - this.notify('cell:mouseout', this.getEventArgs(e)) - } - - onMouseEnter(e: Dom.MouseEnterEvent) { - this.notify('cell:mouseenter', this.getEventArgs(e)) - } - - onMouseLeave(e: Dom.MouseLeaveEvent) { - this.notify('cell:mouseleave', this.getEventArgs(e)) - } - - onMouseWheel(e: Dom.EventObject, x: number, y: number, delta: number) { - this.notify('cell:mousewheel', { - delta, - ...this.getEventArgs(e, x, y), - }) - } - - onCustomEvent(e: Dom.MouseDownEvent, name: string, x: number, y: number) { - this.notify('cell:customevent', { name, ...this.getEventArgs(e, x, y) }) - this.notify(name, { ...this.getEventArgs(e, x, y) }) - } - - onMagnetMouseDown( - e: Dom.MouseDownEvent, - magnet: Element, - x: number, - y: number, - ) {} - - onMagnetDblClick( - e: Dom.DoubleClickEvent, - magnet: Element, - x: number, - y: number, - ) {} - - onMagnetContextMenu( - e: Dom.ContextMenuEvent, - magnet: Element, - x: number, - y: number, - ) {} - - onLabelMouseDown(e: Dom.MouseDownEvent, x: number, y: number) {} - - checkMouseleave(e: Dom.EventObject) { - const renderer = this.renderer - if (renderer.isAsync()) { - // Do the updates of the current view synchronously now - renderer.dumpView(this) - } - const target = this.getEventTarget(e, { fromPoint: true }) - const view = renderer.findViewByElem(target) - if (view === this) { - return - } - - // Leaving the current view - this.onMouseLeave(e as Dom.MouseLeaveEvent) - if (!view) { - return - } - - // Entering another view - view.onMouseEnter(e as Dom.MouseEnterEvent) - } - - // #endregion -} - -export namespace CellView { - export interface Options { - renderer: Renderer - priority: number - isSvgElement: boolean - rootSelector: string - bootstrap: FlagManager.Actions - actions: KeyValue - events?: View.Events | null - documentEvents?: View.Events | null - } - - type Interactable = boolean | ((this: any, cellView: CellView) => boolean) // todo - - interface InteractionMap { - // edge - edgeMovable?: Interactable - edgeLabelMovable?: Interactable - arrowheadMovable?: Interactable - vertexMovable?: Interactable - vertexAddable?: Interactable - vertexDeletable?: Interactable - useEdgeTools?: Interactable - - // node - nodeMovable?: Interactable - magnetConnectable?: Interactable - stopDelegateOnDragging?: Interactable - - // general - toolsAddable?: Interactable - } - - export type InteractionNames = keyof InteractionMap - - export type Interacting = - | boolean - | InteractionMap - | ((this: any, cellView: CellView) => InteractionMap | boolean) // todo - - export interface HighlightOptions { - highlighter?: - | string - | { - name: string - args: KeyValue - } - - type?: 'embedding' | 'nodeAvailable' | 'magnetAvailable' | 'magnetAdsorbed' - - partial?: boolean - } -} - -export namespace CellView { - export interface PositionEventArgs { - x: number - y: number - } - - export interface MouseDeltaEventArgs { - delta: number - } - - export interface MouseEventArgs { - e: E - view: CellView - cell: Cell - } - - export interface MousePositionEventArgs - extends MouseEventArgs, - PositionEventArgs {} - - export interface EventArgs extends NodeView.EventArgs, EdgeView.EventArgs { - 'cell:click': MousePositionEventArgs - 'cell:dblclick': MousePositionEventArgs - 'cell:contextmenu': MousePositionEventArgs - 'cell:mousedown': MousePositionEventArgs - 'cell:mousemove': MousePositionEventArgs - 'cell:mouseup': MousePositionEventArgs - 'cell:mouseover': MouseEventArgs - 'cell:mouseout': MouseEventArgs - 'cell:mouseenter': MouseEventArgs - 'cell:mouseleave': MouseEventArgs - 'cell:mousewheel': MousePositionEventArgs & - MouseDeltaEventArgs - 'cell:customevent': MousePositionEventArgs & { - name: string - } - 'cell:highlight': { - magnet: Element - view: CellView - cell: Cell - options: CellView.HighlightOptions - } - 'cell:unhighlight': EventArgs['cell:highlight'] - } -} - -export namespace CellView { - export const Flag = FlagManager - export const Attr = AttrManager -} - -export namespace CellView { - export const toStringTag = `X6.${CellView.name}` - - export function isCellView(instance: any): instance is CellView { - if (instance == null) { - return false - } - - if (instance instanceof CellView) { - return true - } - - const tag = instance[Symbol.toStringTag] - const view = instance as CellView - - if ( - (tag == null || tag === toStringTag) && - typeof view.isNodeView === 'function' && - typeof view.isEdgeView === 'function' && - typeof view.confirmUpdate === 'function' - ) { - return true - } - - return false - } -} - -// decorators -// ---- -export namespace CellView { - export function priority(value: number) { - return function (ctor: Definition) { - ctor.config({ priority: value }) - } - } - - export function bootstrap(actions: FlagManager.Actions) { - return function (ctor: Definition) { - ctor.config({ bootstrap: actions }) - } - } -} - -export namespace CellView { - type CellViewClass = typeof CellView - - export interface Definition extends CellViewClass { - new < - Entity extends Cell = Cell, - Options extends CellView.Options = CellView.Options, - >( - cell: Entity, - options: Partial, - ): CellView - } - - export const registry = Registry.create({ - type: 'view', - }) -} diff --git a/packages/x6-core/src/view/edge.ts b/packages/x6-core/src/view/edge.ts deleted file mode 100644 index 2400666671f..00000000000 --- a/packages/x6-core/src/view/edge.ts +++ /dev/null @@ -1,2509 +0,0 @@ -import { - Rectangle, - Polyline, - Point, - Angle, - Path, - Line, -} from '@antv/x6-geometry' -import { - ObjectExt, - NumberExt, - FunctionExt, - Dom, - Vector, - KeyValue, -} from '@antv/x6-common' -import { - Router, - Connector, - NodeAnchor, - EdgeAnchor, - ConnectionPoint, -} from '../registry' -import { Cell } from '../model/cell' -import { Edge } from '../model/edge' -import { Markup } from './markup' -import { CellView } from './cell' -import { NodeView } from './node' -import { ToolsView } from './tool' -import { Renderer } from '../renderer' -import { Options as RendererOptions } from '../renderer/options' - -export class EdgeView< - Entity extends Edge = Edge, - Options extends EdgeView.Options = EdgeView.Options, -> extends CellView { - protected readonly POINT_ROUNDING = 2 - public path: Path - public routePoints: Point[] - public sourceAnchor: Point - public targetAnchor: Point - public sourcePoint: Point - public targetPoint: Point - public sourceMarkerPoint: Point - public targetMarkerPoint: Point - public sourceView: CellView | null - public targetView: CellView | null - public sourceMagnet: Element | null - public targetMagnet: Element | null - - protected labelContainer: Element | null - protected labelCache: { [index: number]: Element } - protected labelSelectors: { [index: number]: Markup.Selectors } - - protected get [Symbol.toStringTag]() { - return EdgeView.toStringTag - } - - protected getContainerClassName() { - return [super.getContainerClassName(), this.prefixClassName('edge')].join( - ' ', - ) - } - - get sourceBBox() { - const sourceView = this.sourceView - if (!sourceView) { - const sourceDef = this.cell.getSource() as Edge.TerminalPointData - return new Rectangle(sourceDef.x, sourceDef.y) - } - const sourceMagnet = this.sourceMagnet - if (sourceView.isEdgeElement(sourceMagnet)) { - return new Rectangle(this.sourceAnchor.x, this.sourceAnchor.y) - } - return sourceView.getBBoxOfElement(sourceMagnet || sourceView.container) - } - - get targetBBox() { - const targetView = this.targetView - if (!targetView) { - const targetDef = this.cell.getTarget() as Edge.TerminalPointData - return new Rectangle(targetDef.x, targetDef.y) - } - const targetMagnet = this.targetMagnet - if (targetView.isEdgeElement(targetMagnet)) { - return new Rectangle(this.targetAnchor.x, this.targetAnchor.y) - } - return targetView.getBBoxOfElement(targetMagnet || targetView.container) - } - - isEdgeView(): this is EdgeView { - return true - } - - confirmUpdate(flag: number, options: any = {}) { - let ref = flag - if (this.hasAction(ref, 'source')) { - if (!this.updateTerminalProperties('source')) { - return ref - } - ref = this.removeAction(ref, 'source') - } - - if (this.hasAction(ref, 'target')) { - if (!this.updateTerminalProperties('target')) { - return ref - } - ref = this.removeAction(ref, 'target') - } - - const renderer = this.renderer - const sourceView = this.sourceView - const targetView = this.targetView - - // todo - if ( - renderer && - ((sourceView && !renderer.isViewMounted(sourceView)) || - (targetView && !renderer.isViewMounted(targetView))) - ) { - // Wait for the sourceView and targetView to be rendered. - return ref - } - - if (this.hasAction(ref, 'render')) { - this.render() - ref = this.removeAction(ref, ['render', 'update', 'labels', 'tools']) - return ref - } - ref = this.handleAction(ref, 'update', () => this.update(options)) - ref = this.handleAction(ref, 'labels', () => this.onLabelsChange(options)) - ref = this.handleAction(ref, 'tools', () => this.renderTools()) - - return ref - } - - // #region render - render() { - this.empty() - - this.renderMarkup() - - this.labelContainer = null - this.renderLabels() - - this.update() - this.renderTools() - - return this - } - - protected renderMarkup() { - const markup = this.cell.markup - if (markup) { - if (typeof markup === 'string') { - throw new TypeError('Not support string markup.') - } - return this.renderJSONMarkup(markup) - } - throw new TypeError('Invalid edge markup.') - } - - protected renderJSONMarkup(markup: Markup.JSONMarkup | Markup.JSONMarkup[]) { - const ret = this.parseJSONMarkup(markup, this.container) - this.selectors = ret.selectors - this.container.append(ret.fragment) - } - - // protected customizeLabels() { - // if (this.containers.labels) { - // const edge = this.cell - // const labels = edge.labels - // for (let i = 0, n = labels.length; i < n; i += 1) { - // const label = labels[i] - // const container = this.labelCache[i] - // const selectors = this.labelSelectors[i] - // this.graph.hook.onEdgeLabelRendered({ - // edge, - // label, - // container, - // selectors, - // }) - // } - // } - // } - - protected renderLabels() { - const edge = this.cell - const labels = edge.getLabels() - const count = labels.length - let container = this.labelContainer - - this.labelCache = {} - this.labelSelectors = {} - - if (count <= 0) { - if (container && container.parentNode) { - container.parentNode.removeChild(container) - } - return this - } - - if (container) { - this.empty(container) - } else { - container = Dom.createSvgElement('g') - this.addClass(this.prefixClassName('edge-labels'), container) - this.labelContainer = container - } - - for (let i = 0, ii = labels.length; i < ii; i += 1) { - const label = labels[i] - const normalized = this.normalizeLabelMarkup( - this.parseLabelMarkup(label.markup), - ) - let labelNode - let selectors - if (normalized) { - labelNode = normalized.node - selectors = normalized.selectors - } else { - const defaultLabel = edge.getDefaultLabel() - const normalized = this.normalizeLabelMarkup( - this.parseLabelMarkup(defaultLabel.markup), - )! - - labelNode = normalized.node - selectors = normalized.selectors - } - - labelNode.setAttribute('data-index', `${i}`) - container.appendChild(labelNode) - - const rootSelector = this.rootSelector - if (selectors[rootSelector]) { - throw new Error('Ambiguous label root selector.') - } - selectors[rootSelector] = labelNode - - this.labelCache[i] = labelNode - this.labelSelectors[i] = selectors - } - - if (container.parentNode == null) { - this.container.appendChild(container) - } - - this.updateLabels() - // todo - // this.customizeLabels() - - return this - } - - onLabelsChange(options: any = {}) { - if (this.shouldRerenderLabels(options)) { - this.renderLabels() - } else { - this.updateLabels() - } - - this.updateLabelPositions() - } - - protected shouldRerenderLabels(options: any = {}) { - const previousLabels = this.cell.previous('labels') - if (previousLabels == null) { - return true - } - - // Here is an optimization for cases when we know, that change does - // not require re-rendering of all labels. - if ('propertyPathArray' in options && 'propertyValue' in options) { - // The label is setting by `prop()` method - const pathArray = options.propertyPathArray || [] - const pathLength = pathArray.length - if (pathLength > 1) { - // We are changing a single label here e.g. 'labels/0/position' - const index = pathArray[1] - if (previousLabels[index]) { - if (pathLength === 2) { - // We are changing the entire label. Need to check if the - // markup is also being changed. - return ( - typeof options.propertyValue === 'object' && - ObjectExt.has(options.propertyValue, 'markup') - ) - } - - // We are changing a label property but not the markup - if (pathArray[2] !== 'markup') { - return false - } - } - } - } - - return true - } - - protected parseLabelMarkup(markup?: Markup) { - if (markup) { - if (typeof markup === 'string') { - return this.parseLabelStringMarkup(markup) - } - return this.parseJSONMarkup(markup) - } - - return null - } - - protected parseLabelStringMarkup(labelMarkup: string) { - const children = Vector.createVectors(labelMarkup) - const fragment = document.createDocumentFragment() - for (let i = 0, n = children.length; i < n; i += 1) { - const currentChild = children[i].node - fragment.appendChild(currentChild) - } - - return { fragment, selectors: {} } - } - - protected normalizeLabelMarkup( - markup?: { - fragment: DocumentFragment - selectors: Markup.Selectors - } | null, - ) { - if (markup == null) { - return - } - - const fragment = markup.fragment - if (!(fragment instanceof DocumentFragment) || !fragment.hasChildNodes()) { - throw new Error('Invalid label markup.') - } - - let vel - const childNodes = fragment.childNodes - if (childNodes.length > 1 || childNodes[0].nodeName.toUpperCase() !== 'G') { - vel = Vector.create('g').append(fragment) - } else { - vel = Vector.create(childNodes[0] as SVGElement) - } - - vel.addClass(this.prefixClassName('edge-label')) - - return { - node: vel.node, - selectors: markup.selectors, - } - } - - protected updateLabels() { - if (this.labelContainer) { - const edge = this.cell - const labels = edge.labels - const canLabelMove = this.can('edgeLabelMovable') - const defaultLabel = edge.getDefaultLabel() - - for (let i = 0, n = labels.length; i < n; i += 1) { - const elem = this.labelCache[i] - const selectors = this.labelSelectors[i] - - elem.setAttribute('cursor', canLabelMove ? 'move' : 'default') - - const label = labels[i] - const attrs = ObjectExt.merge({}, defaultLabel.attrs, label.attrs) - this.updateAttrs(elem, attrs, { - selectors, - rootBBox: label.size ? Rectangle.fromSize(label.size) : undefined, - }) - } - } - } - - protected renderTools() { - const tools = this.cell.getTools() - this.addTools(tools as ToolsView.Options) - return this - } - - // #endregion - - // #region updating - - update(options: any = {}) { - this.cleanCache() - this.updateConnection(options) - - const attrs = this.cell.getAttrs() - if (attrs != null) { - this.updateAttrs(this.container, attrs, { - selectors: this.selectors, - }) - } - - this.updateLabelPositions() - this.updateTools(options) - - return this - } - - removeRedundantLinearVertices(options: Edge.SetOptions = {}) { - const edge = this.cell - const vertices = edge.getVertices() - const routePoints = [this.sourceAnchor, ...vertices, this.targetAnchor] - const rawCount = routePoints.length - - // Puts the route points into a polyline and try to simplify. - const polyline = new Polyline(routePoints) - polyline.simplify({ threshold: 0.01 }) - const simplifiedPoints = polyline.points.map((point) => point.toJSON()) - const simplifiedCount = simplifiedPoints.length - - // If simplification did not remove any redundant vertices. - if (rawCount === simplifiedCount) { - return 0 - } - - // Sets simplified polyline points as edge vertices. - // Removes first and last polyline points again (source/target anchors). - edge.setVertices(simplifiedPoints.slice(1, simplifiedCount - 1), options) - return rawCount - simplifiedCount - } - - getTerminalView(type: Edge.TerminalType) { - switch (type) { - case 'source': - return this.sourceView || null - case 'target': - return this.targetView || null - default: - throw new Error(`Unknown terminal type '${type}'`) - } - } - - getTerminalAnchor(type: Edge.TerminalType) { - switch (type) { - case 'source': - return Point.create(this.sourceAnchor) - case 'target': - return Point.create(this.targetAnchor) - default: - throw new Error(`Unknown terminal type '${type}'`) - } - } - - getTerminalConnectionPoint(type: Edge.TerminalType) { - switch (type) { - case 'source': - return Point.create(this.sourcePoint) - case 'target': - return Point.create(this.targetPoint) - default: - throw new Error(`Unknown terminal type '${type}'`) - } - } - - getTerminalMagnet(type: Edge.TerminalType, options: { raw?: boolean } = {}) { - switch (type) { - case 'source': { - if (options.raw) { - return this.sourceMagnet - } - const sourceView = this.sourceView - if (!sourceView) { - return null - } - return this.sourceMagnet || sourceView.container - } - case 'target': { - if (options.raw) { - return this.targetMagnet - } - const targetView = this.targetView - if (!targetView) { - return null - } - return this.targetMagnet || targetView.container - } - default: { - throw new Error(`Unknown terminal type '${type}'`) - } - } - } - - updateConnection(options: any = {}) { - const edge = this.cell - - // The edge is being translated by an ancestor that will shift - // source, target and vertices by an equal distance. - // todo isFragmentDescendantOf is invalid - if ( - options.translateBy && - edge.isFragmentDescendantOf(options.translateBy) - ) { - const tx = options.tx || 0 - const ty = options.ty || 0 - this.routePoints = new Polyline(this.routePoints).translate(tx, ty).points - this.translateConnectionPoints(tx, ty) - this.path.translate(tx, ty) - } else { - const vertices = edge.getVertices() - - // 1. Find anchor points - const anchors = this.findAnchors(vertices) - this.sourceAnchor = anchors.source - this.targetAnchor = anchors.target - - // 2. Find route points - this.routePoints = this.findRoutePoints(vertices) - - // 3. Find connection points - const connectionPoints = this.findConnectionPoints( - this.routePoints, - this.sourceAnchor, - this.targetAnchor, - ) - this.sourcePoint = connectionPoints.source - this.targetPoint = connectionPoints.target - - // 4. Find Marker Connection Point - const markerPoints = this.findMarkerPoints( - this.routePoints, - this.sourcePoint, - this.targetPoint, - ) - - // 5. Make path - this.path = this.findPath( - this.routePoints, - markerPoints.source || this.sourcePoint, - markerPoints.target || this.targetPoint, - ) - } - - this.cleanCache() - } - - protected findAnchors(vertices: Point.PointLike[]) { - const firstVertex = vertices[0] - const lastVertex = vertices[vertices.length - 1] - - return this.findAnchorsOrdered('source', firstVertex, 'target', lastVertex) - } - - protected findAnchorsOrdered( - firstType: Edge.TerminalType, - firstPoint: Point.PointLike, - secondType: Edge.TerminalType, - secondPoint: Point.PointLike, - ) { - let firstAnchor: Point - let secondAnchor: Point - - const edge = this.cell - const firstTerminal = edge[firstType] - const secondTerminal = edge[secondType] - const firstView = this.getTerminalView(firstType) - const secondView = this.getTerminalView(secondType) - const firstMagnet = this.getTerminalMagnet(firstType) - const secondMagnet = this.getTerminalMagnet(secondType) - - if (firstView) { - let firstRef - if (firstPoint) { - firstRef = Point.create(firstPoint) - } else if (secondView) { - firstRef = secondMagnet - } else { - firstRef = Point.create(secondTerminal as Edge.TerminalPointData) - } - - firstAnchor = this.getAnchor( - (firstTerminal as Edge.SetCellTerminalArgs).anchor, - firstView, - firstMagnet, - firstRef, - firstType, - ) - } else { - firstAnchor = Point.create(firstTerminal as Edge.TerminalPointData) - } - - if (secondView) { - const secondRef = Point.create(secondPoint || firstAnchor) - secondAnchor = this.getAnchor( - (secondTerminal as Edge.SetCellTerminalArgs).anchor, - secondView, - secondMagnet, - secondRef, - secondType, - ) - } else { - secondAnchor = Point.isPointLike(secondTerminal) - ? Point.create(secondTerminal) - : new Point() - } - - return { - [firstType]: firstAnchor, - [secondType]: secondAnchor, - } - } - - protected getAnchor( - def: NodeAnchor.ManaualItem | string | undefined, - cellView: CellView, - magnet: Element | null, - ref: Point | Element | null, - terminalType: Edge.TerminalType, - ): Point { - const isEdge = cellView.isEdgeElement(magnet) - const connecting = this.renderer.options.connecting - let config = typeof def === 'string' ? { name: def } : def - if (!config) { - const defaults = isEdge - ? (terminalType === 'source' - ? connecting.sourceEdgeAnchor - : connecting.targetEdgeAnchor) || connecting.edgeAnchor - : (terminalType === 'source' - ? connecting.sourceAnchor - : connecting.targetAnchor) || connecting.anchor - - config = typeof defaults === 'string' ? { name: defaults } : defaults - } - - if (!config) { - throw new Error(`Anchor should be specified.`) - } - - let anchor - - const name = config.name - if (isEdge) { - const fn = EdgeAnchor.registry.get(name) - if (typeof fn !== 'function') { - return EdgeAnchor.registry.onNotFound(name) - } - anchor = FunctionExt.call( - fn, - this, - cellView as EdgeView, - magnet as SVGElement, - ref as Point.PointLike, - config.args || {}, - terminalType, - ) - } else { - const fn = NodeAnchor.registry.get(name) - if (typeof fn !== 'function') { - return NodeAnchor.registry.onNotFound(name) - } - - anchor = FunctionExt.call( - fn, - this, - cellView as NodeView, - magnet as SVGElement, - ref as Point.PointLike, - config.args || {}, - terminalType, - ) - } - - return anchor ? anchor.round(this.POINT_ROUNDING) : new Point() - } - - protected findRoutePoints(vertices: Point.PointLike[] = []): Point[] { - const defaultRouter = - this.renderer.options.connecting.router || Router.presets.normal - const router = this.cell.getRouter() || defaultRouter - let routePoints - - if (typeof router === 'function') { - routePoints = FunctionExt.call( - router as Router.Definition, - this, - vertices, - {}, - this, - ) - } else { - const name = typeof router === 'string' ? router : router.name - const args = typeof router === 'string' ? {} : router.args || {} - const fn = name ? Router.registry.get(name) : Router.presets.normal - if (typeof fn !== 'function') { - return Router.registry.onNotFound(name!) - } - - routePoints = FunctionExt.call(fn, this, vertices, args, this) - } - - return routePoints == null - ? vertices.map((p) => Point.create(p)) - : routePoints.map((p) => Point.create(p)) - } - - protected findConnectionPoints( - routePoints: Point[], - sourceAnchor: Point, - targetAnchor: Point, - ) { - const edge = this.cell - const connecting = this.renderer.options.connecting - const sourceTerminal = edge.getSource() - const targetTerminal = edge.getTarget() - const sourceView = this.sourceView - const targetView = this.targetView - const firstRoutePoint = routePoints[0] - const lastRoutePoint = routePoints[routePoints.length - 1] - - // source - let sourcePoint - if (sourceView && !sourceView.isEdgeElement(this.sourceMagnet)) { - const sourceMagnet = this.sourceMagnet || sourceView.container - const sourcePointRef = firstRoutePoint || targetAnchor - const sourceLine = new Line(sourcePointRef, sourceAnchor) - const connectionPointDef = - sourceTerminal.connectionPoint || - connecting.sourceConnectionPoint || - connecting.connectionPoint - sourcePoint = this.getConnectionPoint( - connectionPointDef, - sourceView, - sourceMagnet, - sourceLine, - 'source', - ) - } else { - sourcePoint = sourceAnchor - } - - // target - let targetPoint - if (targetView && !targetView.isEdgeElement(this.targetMagnet)) { - const targetMagnet = this.targetMagnet || targetView.container - const targetConnectionPointDef = - targetTerminal.connectionPoint || - connecting.targetConnectionPoint || - connecting.connectionPoint - const targetPointRef = lastRoutePoint || sourceAnchor - const targetLine = new Line(targetPointRef, targetAnchor) - targetPoint = this.getConnectionPoint( - targetConnectionPointDef, - targetView, - targetMagnet, - targetLine, - 'target', - ) - } else { - targetPoint = targetAnchor - } - - return { - source: sourcePoint, - target: targetPoint, - } - } - - protected getConnectionPoint( - def: string | ConnectionPoint.ManaualItem | undefined, - view: CellView, - magnet: Element, - line: Line, - endType: Edge.TerminalType, - ) { - const anchor = line.end - if (def == null) { - return anchor - } - - const name = typeof def === 'string' ? def : def.name - const args = typeof def === 'string' ? {} : def.args - const fn = ConnectionPoint.registry.get(name) - if (typeof fn !== 'function') { - return ConnectionPoint.registry.onNotFound(name) - } - - const connectionPoint = FunctionExt.call( - fn, - this, - line, - view, - magnet as SVGElement, - args || {}, - endType, - ) - - return connectionPoint ? connectionPoint.round(this.POINT_ROUNDING) : anchor - } - - protected findMarkerPoints( - routePoints: Point[], - sourcePoint: Point, - targetPoint: Point, - ) { - const getLineWidth = (type: Edge.TerminalType) => { - const attrs = this.cell.getAttrs() - const keys = Object.keys(attrs) - for (let i = 0, l = keys.length; i < l; i += 1) { - const attr = attrs[keys[i]] - if (attr[`${type}Marker`] || attr[`${type}-marker`]) { - const strokeWidth = - (attr.strokeWidth as string) || (attr['stroke-width'] as string) - if (strokeWidth) { - return parseFloat(strokeWidth) - } - break - } - } - return null - } - - const firstRoutePoint = routePoints[0] - const lastRoutePoint = routePoints[routePoints.length - 1] - let sourceMarkerPoint - let targetMarkerPoint - - const sourceStrokeWidth = getLineWidth('source') - if (sourceStrokeWidth) { - sourceMarkerPoint = sourcePoint - .clone() - .move(firstRoutePoint || targetPoint, -sourceStrokeWidth) - } - - const targetStrokeWidth = getLineWidth('target') - if (targetStrokeWidth) { - targetMarkerPoint = targetPoint - .clone() - .move(lastRoutePoint || sourcePoint, -targetStrokeWidth) - } - - this.sourceMarkerPoint = sourceMarkerPoint || sourcePoint.clone() - this.targetMarkerPoint = targetMarkerPoint || targetPoint.clone() - - return { - source: sourceMarkerPoint, - target: targetMarkerPoint, - } - } - - protected findPath( - routePoints: Point[], - sourcePoint: Point, - targetPoint: Point, - ): Path { - const def = - this.cell.getConnector() || this.renderer.options.connecting.connector - - let name: string | undefined - let args: Connector.BaseOptions | undefined - let fn: Connector.Definition - - if (typeof def === 'string') { - name = def - } else { - name = def.name - args = def.args - } - - if (name) { - const method = Connector.registry.get(name) - if (typeof method !== 'function') { - return Connector.registry.onNotFound(name) - } - fn = method - } else { - fn = Connector.presets.normal - } - - const path = FunctionExt.call( - fn, - this, - sourcePoint, - targetPoint, - routePoints, - { ...args, raw: true }, - this, - ) - - return typeof path === 'string' ? Path.parse(path) : path - } - - protected translateConnectionPoints(tx: number, ty: number) { - this.sourcePoint.translate(tx, ty) - this.targetPoint.translate(tx, ty) - this.sourceAnchor.translate(tx, ty) - this.targetAnchor.translate(tx, ty) - this.sourceMarkerPoint.translate(tx, ty) - this.targetMarkerPoint.translate(tx, ty) - } - - updateLabelPositions() { - if (this.labelContainer == null) { - return this - } - - const path = this.path - if (!path) { - return this - } - - const edge = this.cell - const labels = edge.getLabels() - if (labels.length === 0) { - return this - } - - const defaultLabel = edge.getDefaultLabel() - const defaultPosition = this.normalizeLabelPosition( - defaultLabel.position as Edge.LabelPosition, - ) - - for (let i = 0, ii = labels.length; i < ii; i += 1) { - const label = labels[i] - const labelPosition = this.normalizeLabelPosition( - label.position as Edge.LabelPosition, - ) - const pos = ObjectExt.merge({}, defaultPosition, labelPosition) - const matrix = this.getLabelTransformationMatrix(pos) - this.labelCache[i].setAttribute( - 'transform', - Dom.matrixToTransformString(matrix), - ) - } - - return this - } - - updateTerminalProperties(type: Edge.TerminalType) { - const edge = this.cell - const renderer = this.renderer - const terminal = edge[type] - const nodeId = terminal && (terminal as Edge.TerminalCellData).cell - const viewKey = `${type}View` as 'sourceView' | 'targetView' - - // terminal is a point - if (!nodeId) { - this[viewKey] = null - this.updateTerminalMagnet(type) - return true - } - - const terminalCell = renderer.getCellById(nodeId) - if (!terminalCell) { - throw new Error(`Edge's ${type} node with id "${nodeId}" not exists`) - } - - const endView = terminalCell.findView(renderer) - if (!endView) { - return false - } - - this[viewKey] = endView - this.updateTerminalMagnet(type) - return true - } - - updateTerminalMagnet(type: Edge.TerminalType) { - const propName = `${type}Magnet` as 'sourceMagnet' | 'targetMagnet' - const terminalView = this.getTerminalView(type) - if (terminalView) { - let magnet = terminalView.getMagnetFromEdgeTerminal(this.cell[type]) - if (magnet === terminalView.container) { - magnet = null - } - - this[propName] = magnet - } else { - this[propName] = null - } - } - - protected getLabelPositionAngle(idx: number) { - const label = this.cell.getLabelAt(idx) - if (label && label.position && typeof label.position === 'object') { - return label.position.angle || 0 - } - return 0 - } - - protected getLabelPositionArgs(idx: number) { - const label = this.cell.getLabelAt(idx) - if (label && label.position && typeof label.position === 'object') { - return label.position.options - } - } - - protected getDefaultLabelPositionArgs() { - const defaultLabel = this.cell.getDefaultLabel() - if ( - defaultLabel && - defaultLabel.position && - typeof defaultLabel.position === 'object' - ) { - return defaultLabel.position.options - } - } - - protected mergeLabelPositionArgs( - labelPositionArgs?: Edge.LabelPositionOptions, - defaultLabelPositionArgs?: Edge.LabelPositionOptions, - ) { - if (labelPositionArgs === null) { - return null - } - if (labelPositionArgs === undefined) { - if (defaultLabelPositionArgs === null) { - return null - } - return defaultLabelPositionArgs - } - - return ObjectExt.merge({}, defaultLabelPositionArgs, labelPositionArgs) - } - - // #endregion - - getConnection() { - return this.path != null ? this.path.clone() : null - } - - getConnectionPathData() { - if (this.path == null) { - return '' - } - - const cache = this.cache.pathCache - if (!ObjectExt.has(cache, 'data')) { - cache.data = this.path.serialize() - } - return cache.data || '' - } - - getConnectionSubdivisions() { - if (this.path == null) { - return null - } - - const cache = this.cache.pathCache - if (!ObjectExt.has(cache, 'segmentSubdivisions')) { - cache.segmentSubdivisions = this.path.getSegmentSubdivisions() - } - return cache.segmentSubdivisions - } - - getConnectionLength() { - if (this.path == null) { - return 0 - } - - const cache = this.cache.pathCache - if (!ObjectExt.has(cache, 'length')) { - cache.length = this.path.length({ - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - return cache.length - } - - getPointAtLength(length: number) { - if (this.path == null) { - return null - } - - return this.path.pointAtLength(length, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getPointAtRatio(ratio: number) { - if (this.path == null) { - return null - } - - if (NumberExt.isPercentage(ratio)) { - // eslint-disable-next-line - ratio = parseFloat(ratio) / 100 - } - - return this.path.pointAt(ratio, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getTangentAtLength(length: number) { - if (this.path == null) { - return null - } - - return this.path.tangentAtLength(length, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getTangentAtRatio(ratio: number) { - if (this.path == null) { - return null - } - - return this.path.tangentAt(ratio, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getClosestPoint(point: Point.PointLike) { - if (this.path == null) { - return null - } - - return this.path.closestPoint(point, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getClosestPointLength(point: Point.PointLike) { - if (this.path == null) { - return null - } - - return this.path.closestPointLength(point, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getClosestPointRatio(point: Point.PointLike) { - if (this.path == null) { - return null - } - - return this.path.closestPointNormalizedLength(point, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getLabelPosition( - x: number, - y: number, - options?: Edge.LabelPositionOptions | null, - ): Edge.LabelPositionObject - getLabelPosition( - x: number, - y: number, - angle: number, - options?: Edge.LabelPositionOptions | null, - ): Edge.LabelPositionObject - getLabelPosition( - x: number, - y: number, - p3?: number | Edge.LabelPositionOptions | null, - p4?: Edge.LabelPositionOptions | null, - ): Edge.LabelPositionObject { - const pos: Edge.LabelPositionObject = { distance: 0 } - - // normalize data from the two possible signatures - let angle = 0 - let options - if (typeof p3 === 'number') { - angle = p3 - options = p4 - } else { - options = p3 - } - - if (options != null) { - pos.options = options - } - - // identify distance/offset settings - const isOffsetAbsolute = options && options.absoluteOffset - const isDistanceRelative = !(options && options.absoluteDistance) - const isDistanceAbsoluteReverse = - options && options.absoluteDistance && options.reverseDistance - - // find closest point t - const path = this.path - const pathOptions = { - segmentSubdivisions: this.getConnectionSubdivisions(), - } - - const labelPoint = new Point(x, y) - const t = path.closestPointT(labelPoint, pathOptions)! - - // distance - const totalLength = this.getConnectionLength() || 0 - let labelDistance = path.lengthAtT(t, pathOptions) - if (isDistanceRelative) { - labelDistance = totalLength > 0 ? labelDistance / totalLength : 0 - } - - if (isDistanceAbsoluteReverse) { - // fix for end point (-0 => 1) - labelDistance = -1 * (totalLength - labelDistance) || 1 - } - pos.distance = labelDistance - - // offset - // use absolute offset if: - // - options.absoluteOffset is true, - // - options.absoluteOffset is not true but there is no tangent - let tangent - if (!isOffsetAbsolute) tangent = path.tangentAtT(t) - let labelOffset - if (tangent) { - labelOffset = tangent.pointOffset(labelPoint) - } else { - const closestPoint = path.pointAtT(t)! - const labelOffsetDiff = labelPoint.diff(closestPoint) - labelOffset = { x: labelOffsetDiff.x, y: labelOffsetDiff.y } - } - - pos.offset = labelOffset - pos.angle = angle - - return pos - } - - protected normalizeLabelPosition(): undefined - protected normalizeLabelPosition( - pos: Edge.LabelPosition, - ): Edge.LabelPositionObject - protected normalizeLabelPosition( - pos?: Edge.LabelPosition, - ): Edge.LabelPositionObject | undefined { - if (typeof pos === 'number') { - return { distance: pos } - } - - return pos - } - - protected getLabelTransformationMatrix(labelPosition: Edge.LabelPosition) { - const pos = this.normalizeLabelPosition(labelPosition) - const options = pos.options || {} - const labelAngle = pos.angle || 0 - const labelDistance = pos.distance - const isDistanceRelative = labelDistance > 0 && labelDistance <= 1 - - let labelOffset = 0 - const offsetCoord = { x: 0, y: 0 } - const offset = pos.offset - if (offset) { - if (typeof offset === 'number') { - labelOffset = offset - } else { - if (offset.x != null) { - offsetCoord.x = offset.x - } - if (offset.y != null) { - offsetCoord.y = offset.y - } - } - } - - const isOffsetAbsolute = - offsetCoord.x !== 0 || offsetCoord.y !== 0 || labelOffset === 0 - - const isKeepGradient = options.keepGradient - const isEnsureLegibility = options.ensureLegibility - - const path = this.path - const pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() } - - const distance = isDistanceRelative - ? labelDistance * this.getConnectionLength()! - : labelDistance - const tangent = path.tangentAtLength(distance, pathOpt) - - let translation - let angle = labelAngle - if (tangent) { - if (isOffsetAbsolute) { - translation = tangent.start - translation.translate(offsetCoord) - } else { - const normal = tangent.clone() - normal.rotate(-90, tangent.start) - normal.setLength(labelOffset) - translation = normal.end - } - if (isKeepGradient) { - angle = tangent.angle() + labelAngle - if (isEnsureLegibility) { - angle = Angle.normalize(((angle + 90) % 180) - 90) - } - } - } else { - // fallback - the connection has zero length - translation = path.start! - if (isOffsetAbsolute) { - translation.translate(offsetCoord) - } - } - - return Dom.createSVGMatrix() - .translate(translation.x, translation.y) - .rotate(angle) - } - - getVertexIndex(x: number, y: number) { - const edge = this.cell - const vertices = edge.getVertices() - const vertexLength = this.getClosestPointLength(new Point(x, y)) - - let index = 0 - - if (vertexLength != null) { - for (const ii = vertices.length; index < ii; index += 1) { - const currentVertex = vertices[index] - const currentLength = this.getClosestPointLength(currentVertex) - if (currentLength != null && vertexLength < currentLength) { - break - } - } - } - - return index - } - - // #region events - - protected getEventArgs(e: E): EdgeView.MouseEventArgs - protected getEventArgs( - e: E, - x: number, - y: number, - ): EdgeView.PositionEventArgs - protected getEventArgs(e: E, x?: number, y?: number) { - const view = this // eslint-disable-line - const edge = view.cell - const cell = edge - if (x == null || y == null) { - return { e, view, edge, cell } as EdgeView.MouseEventArgs - } - return { e, x, y, view, edge, cell } as EdgeView.PositionEventArgs - } - - protected notifyUnhandledMouseDown( - e: Dom.MouseDownEvent, - x: number, - y: number, - ) { - this.notify('edge:unhandled:mousedown', { - e, - x, - y, - view: this, - cell: this.cell, - edge: this.cell, - }) - } - - notifyMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - super.onMouseDown(e, x, y) - this.notify('edge:mousedown', this.getEventArgs(e, x, y)) - } - - notifyMouseMove(e: Dom.MouseMoveEvent, x: number, y: number) { - super.onMouseMove(e, x, y) - this.notify('edge:mousemove', this.getEventArgs(e, x, y)) - } - - notifyMouseUp(e: Dom.MouseUpEvent, x: number, y: number) { - super.onMouseUp(e, x, y) - this.notify('edge:mouseup', this.getEventArgs(e, x, y)) - } - - onClick(e: Dom.ClickEvent, x: number, y: number) { - super.onClick(e, x, y) - this.notify('edge:click', this.getEventArgs(e, x, y)) - } - - onDblClick(e: Dom.DoubleClickEvent, x: number, y: number) { - super.onDblClick(e, x, y) - this.notify('edge:dblclick', this.getEventArgs(e, x, y)) - } - - onContextMenu(e: Dom.ContextMenuEvent, x: number, y: number) { - super.onContextMenu(e, x, y) - this.notify('edge:contextmenu', this.getEventArgs(e, x, y)) - } - - onMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - this.startEdgeDragging(e, x, y) - } - - onMouseMove(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - switch (data.action) { - case 'drag-label': { - this.dragLabel(e, x, y) - break - } - - case 'drag-arrowhead': { - this.dragArrowhead(e, x, y) - break - } - - case 'drag-edge': { - this.dragEdge(e, x, y) - break - } - - default: - break - } - - this.notifyMouseMove(e, x, y) - return data - } - - onMouseUp(e: Dom.MouseUpEvent, x: number, y: number) { - const data = this.getEventData(e) - switch (data.action) { - case 'drag-label': { - this.stopLabelDragging(e, x, y) - break - } - - case 'drag-arrowhead': { - this.stopArrowheadDragging(e, x, y) - break - } - - case 'drag-edge': { - this.stopEdgeDragging(e, x, y) - break - } - - default: - break - } - - this.notifyMouseUp(e, x, y) - this.checkMouseleave(e) - return data - } - - onMouseOver(e: Dom.MouseOverEvent) { - super.onMouseOver(e) - this.notify('edge:mouseover', this.getEventArgs(e)) - } - - onMouseOut(e: Dom.MouseOutEvent) { - super.onMouseOut(e) - this.notify('edge:mouseout', this.getEventArgs(e)) - } - - onMouseEnter(e: Dom.MouseEnterEvent) { - super.onMouseEnter(e) - this.notify('edge:mouseenter', this.getEventArgs(e)) - } - - onMouseLeave(e: Dom.MouseLeaveEvent) { - super.onMouseLeave(e) - this.notify('edge:mouseleave', this.getEventArgs(e)) - } - - onMouseWheel(e: Dom.EventObject, x: number, y: number, delta: number) { - super.onMouseWheel(e, x, y, delta) - this.notify('edge:mousewheel', { - delta, - ...this.getEventArgs(e, x, y), - }) - } - - onCustomEvent(e: Dom.MouseDownEvent, name: string, x: number, y: number) { - // For default edge tool - const tool = Dom.findParentByClass(e.target, 'edge-tool', this.container) - if (tool) { - e.stopPropagation() // no further action to be executed - if (this.can('useEdgeTools')) { - if (name === 'edge:remove') { - this.cell.remove({ ui: true }) - return - } - this.notify('edge:customevent', { name, ...this.getEventArgs(e, x, y) }) - } - - this.notifyMouseDown(e as Dom.MouseDownEvent, x, y) - } else { - this.notify('edge:customevent', { name, ...this.getEventArgs(e, x, y) }) - super.onCustomEvent(e, name, x, y) - } - } - - onLabelMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - this.notifyMouseDown(e, x, y) - this.startLabelDragging(e, x, y) - - const stopPropagation = this.getEventData(e).stopPropagation - if (stopPropagation) { - e.stopPropagation() - } - } - - // #region drag edge - - protected startEdgeDragging(e: Dom.MouseDownEvent, x: number, y: number) { - if (!this.can('edgeMovable')) { - this.notifyUnhandledMouseDown(e, x, y) - return - } - - this.setEventData(e, { - x, - y, - moving: false, - action: 'drag-edge', - }) - } - - protected dragEdge(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - if (!data.moving) { - data.moving = true - this.addClass('edge-moving') - this.notify('edge:move', { - e, - x, - y, - view: this, - cell: this.cell, - edge: this.cell, - }) - } - - this.cell.translate(x - data.x, y - data.y, { ui: true }) - this.setEventData>(e, { x, y }) - this.notify('edge:moving', { - e, - x, - y, - view: this, - cell: this.cell, - edge: this.cell, - }) - } - - protected stopEdgeDragging(e: Dom.MouseUpEvent, x: number, y: number) { - const data = this.getEventData(e) - if (data.moving) { - this.removeClass('edge-moving') - this.notify('edge:moved', { - e, - x, - y, - view: this, - cell: this.cell, - edge: this.cell, - }) - } - data.moving = false - } - - // #endregion - - // #region drag arrowhead - - prepareArrowheadDragging( - type: Edge.TerminalType, - options: { - x: number - y: number - options?: KeyValue - isNewEdge?: boolean - fallbackAction?: EventData.ArrowheadDragging['fallbackAction'] - }, - ) { - const magnet = this.getTerminalMagnet(type) - const data: EventData.ArrowheadDragging = { - action: 'drag-arrowhead', - x: options.x, - y: options.y, - isNewEdge: options.isNewEdge === true, - terminalType: type, - initialMagnet: magnet, - initialTerminal: ObjectExt.clone(this.cell[type]) as Edge.TerminalData, - fallbackAction: options.fallbackAction || 'revert', - getValidateConnectionArgs: this.createValidateConnectionArgs(type), - options: options.options, - } - - this.beforeArrowheadDragging(data) - - return data - } - - protected createValidateConnectionArgs(type: Edge.TerminalType) { - const args: EventData.ValidateConnectionArgs = [] as any - - args[4] = type - args[5] = this - - let opposite: Edge.TerminalType - let i = 0 - let j = 0 - - if (type === 'source') { - i = 2 - opposite = 'target' - } else { - j = 2 - opposite = 'source' - } - - const terminal = this.cell[opposite] - const cellId = (terminal as Edge.TerminalCellData).cell - if (cellId) { - let magnet - const view = (args[i] = this.renderer.findViewByCell(cellId)) - if (view) { - magnet = view.getMagnetFromEdgeTerminal(terminal) - if (magnet === view.container) { - magnet = undefined - } - } - args[i + 1] = magnet - } - - return (cellView: CellView, magnet: Element) => { - args[j] = cellView - args[j + 1] = cellView.container === magnet ? undefined : magnet - return args - } - } - - protected beforeArrowheadDragging(data: EventData.ArrowheadDragging) { - data.zIndex = this.cell.zIndex - this.cell.toFront() - - const style = (this.container as HTMLElement).style - data.pointerEvents = style.pointerEvents - style.pointerEvents = 'none' - - if (this.renderer.options.connecting.highlight) { - this.highlightAvailableMagnets(data) - } - } - - protected afterArrowheadDragging(data: EventData.ArrowheadDragging) { - if (data.zIndex != null) { - this.cell.setZIndex(data.zIndex, { ui: true }) - data.zIndex = null - } - - const container = this.container as HTMLElement - container.style.pointerEvents = data.pointerEvents || '' - - if (this.renderer.options.connecting.highlight) { - this.unhighlightAvailableMagnets(data) - } - } - - protected validateConnection( - sourceView: CellView | null | undefined, - sourceMagnet: Element | null | undefined, - targetView: CellView | null | undefined, - targetMagnet: Element | null | undefined, - terminalType: Edge.TerminalType, - edgeView?: EdgeView | null | undefined, - candidateTerminal?: Edge.TerminalCellData | null | undefined, - ) { - const options = this.renderer.options.connecting - const allowLoop = options.allowLoop - const allowNode = options.allowNode - const allowEdge = options.allowEdge - const allowPort = options.allowPort - const allowMulti = options.allowMulti - const validate = options.validateConnection - - const edge = edgeView ? edgeView.cell : null - const terminalView = terminalType === 'target' ? targetView : sourceView - const terminalMagnet = - terminalType === 'target' ? targetMagnet : sourceMagnet - - let valid = true - const doValidate = ( - validate: ( - this: Renderer, - args: RendererOptions.ValidateConnectionArgs, - ) => boolean, - ) => { - const sourcePort = - terminalType === 'source' - ? candidateTerminal - ? candidateTerminal.port - : null - : edge - ? edge.getSourcePortId() - : null - const targetPort = - terminalType === 'target' - ? candidateTerminal - ? candidateTerminal.port - : null - : edge - ? edge.getTargetPortId() - : null - return FunctionExt.call(validate, this.renderer, { - edge, - edgeView, - sourceView, - targetView, - sourcePort, - targetPort, - sourceMagnet, - targetMagnet, - sourceCell: sourceView ? sourceView.cell : null, - targetCell: targetView ? targetView.cell : null, - type: terminalType, - }) - } - - if (allowLoop != null) { - if (typeof allowLoop === 'boolean') { - if (!allowLoop && sourceView === targetView) { - valid = false - } - } else { - valid = doValidate(allowLoop) - } - } - - if (valid && allowPort != null) { - if (typeof allowPort === 'boolean') { - if (!allowPort && terminalMagnet) { - valid = false - } - } else { - valid = doValidate(allowPort) - } - } - - if (valid && allowEdge != null) { - if (typeof allowEdge === 'boolean') { - if (!allowEdge && EdgeView.isEdgeView(terminalView)) { - valid = false - } - } else { - valid = doValidate(allowEdge) - } - } - - // When judging nodes, the influence of the ports should be excluded, - // because the ports and nodes have the same terminalView - if (valid && allowNode != null && terminalMagnet == null) { - if (typeof allowNode === 'boolean') { - if (!allowNode && NodeView.isNodeView(terminalView)) { - valid = false - } - } else { - valid = doValidate(allowNode) - } - } - - if (valid && allowMulti != null && edgeView) { - const edge = edgeView.cell - const source = - terminalType === 'source' - ? candidateTerminal - : (edge.getSource() as Edge.TerminalCellData) - const target = - terminalType === 'target' - ? candidateTerminal - : (edge.getTarget() as Edge.TerminalCellData) - const terminalCell = candidateTerminal - ? this.renderer.getCellById(candidateTerminal.cell) - : null - - if (source && target && source.cell && target.cell && terminalCell) { - if (typeof allowMulti === 'function') { - valid = doValidate(allowMulti) - } else { - const connectedEdges = this.renderer.model.getConnectedEdges( - terminalCell, - { - outgoing: terminalType === 'source', - incoming: terminalType === 'target', - }, - ) - if (connectedEdges.length) { - if (allowMulti === 'withPort') { - const exist = connectedEdges.some((link) => { - const s = link.getSource() as Edge.TerminalCellData - const t = link.getTarget() as Edge.TerminalCellData - return ( - s && - t && - s.cell === source.cell && - t.cell === target.cell && - s.port != null && - s.port === source.port && - t.port != null && - t.port === target.port - ) - }) - if (exist) { - valid = false - } - } else if (!allowMulti) { - const exist = connectedEdges.some((link) => { - const s = link.getSource() as Edge.TerminalCellData - const t = link.getTarget() as Edge.TerminalCellData - return ( - s && t && s.cell === source.cell && t.cell === target.cell - ) - }) - if (exist) { - valid = false - } - } - } - } - } - } - - if (valid && validate != null) { - valid = doValidate(validate) - } - - return valid - } - - protected allowConnectToBlank(edge: Edge) { - const renderer = this.renderer - const options = renderer.options.connecting - const allowBlank = options.allowBlank - - if (typeof allowBlank !== 'function') { - return !!allowBlank - } - - const edgeView = renderer.findViewByCell(edge) as EdgeView - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - const sourceView = renderer.findViewByCell(sourceCell) - const targetView = renderer.findViewByCell(targetCell) - return FunctionExt.call(allowBlank, renderer, { - edge, - edgeView, - sourceCell, - targetCell, - sourceView, - targetView, - sourcePort: edge.getSourcePortId(), - targetPort: edge.getTargetPortId(), - sourceMagnet: edgeView.sourceMagnet, - targetMagnet: edgeView.targetMagnet, - }) - } - - protected validateEdge( - edge: Edge, - type: Edge.TerminalType, - initialTerminal: Edge.TerminalData, - ) { - const renderer = this.renderer - if (!this.allowConnectToBlank(edge)) { - const sourceId = edge.getSourceCellId() - const targetId = edge.getTargetCellId() - if (!(sourceId && targetId)) { - return false - } - } - - const validate = renderer.options.connecting.validateEdge - if (validate) { - return FunctionExt.call(validate, renderer, { - edge, - type, - previous: initialTerminal, - }) - } - - return true - } - - protected arrowheadDragging( - target: Element, - x: number, - y: number, - data: EventData.ArrowheadDragging, - ) { - data.x = x - data.y = y - - // Checking views right under the pointer - if (data.currentTarget !== target) { - // Unhighlight the previous view under pointer if there was one. - if (data.currentMagnet && data.currentView) { - data.currentView.unhighlight(data.currentMagnet, { - type: 'magnetAdsorbed', - }) - } - - data.currentView = this.renderer.findViewByElem(target) - if (data.currentView) { - // If we found a view that is under the pointer, we need to find - // the closest magnet based on the real target element of the event. - data.currentMagnet = data.currentView.findMagnet(target) - - if ( - data.currentMagnet && - this.validateConnection( - ...data.getValidateConnectionArgs( - data.currentView, - data.currentMagnet, - ), - data.currentView.getEdgeTerminal( - data.currentMagnet, - x, - y, - this.cell, - data.terminalType, - ), - ) - ) { - data.currentView.highlight(data.currentMagnet, { - type: 'magnetAdsorbed', - }) - } else { - // This type of connection is not valid. Disregard this magnet. - data.currentMagnet = null - } - } else { - // Make sure we'll unset previous magnet. - data.currentMagnet = null - } - } - - data.currentTarget = target - this.cell.prop(data.terminalType, { x, y }, { ...data.options, ui: true }) - } - - protected arrowheadDragged( - data: EventData.ArrowheadDragging, - x: number, - y: number, - ) { - const view = data.currentView - const magnet = data.currentMagnet - if (!magnet || !view) { - return - } - - view.unhighlight(magnet, { type: 'magnetAdsorbed' }) - - const type = data.terminalType - const terminal = view.getEdgeTerminal(magnet, x, y, this.cell, type) - this.cell.setTerminal(type, terminal, { ui: true }) - } - - protected snapArrowhead( - x: number, - y: number, - data: EventData.ArrowheadDragging, - ) { - const renderer = this.renderer - const snap = renderer.options.connecting.snap - const radius = (typeof snap === 'object' && snap.radius) || 50 - const views = renderer.findViewsInArea({ - x: x - radius, - y: y - radius, - width: 2 * radius, - height: 2 * radius, - }) - - const prevView = data.closestView || null - const prevMagnet = data.closestMagnet || null - - data.closestView = null - data.closestMagnet = null - - let distance - let minDistance = Number.MAX_SAFE_INTEGER - const pos = new Point(x, y) - - views.forEach((view: any) => { - // todo - if (view.container.getAttribute('magnet') !== 'false') { - // Find distance from the center of the cell to pointer coordinates - distance = view.cell.getBBox().getCenter().distance(pos) - // the connection is looked up in a circle area by `distance < r` - if (distance < radius && distance < minDistance) { - if ( - prevMagnet === view.container || - this.validateConnection( - ...data.getValidateConnectionArgs(view, null), - view.getEdgeTerminal( - view.container, - x, - y, - this.cell, - data.terminalType, - ), - ) - ) { - minDistance = distance - data.closestView = view - data.closestMagnet = view.container - } - } - } - - view.container.querySelectorAll('[magnet]').forEach((magnet: any) => { - // todo - if (magnet.getAttribute('magnet') !== 'false') { - const bbox = view.getBBoxOfElement(magnet) - distance = pos.distance(bbox.getCenter()) - if (distance < radius && distance < minDistance) { - if ( - prevMagnet === magnet || - this.validateConnection( - ...data.getValidateConnectionArgs(view, magnet), - view.getEdgeTerminal( - magnet, - x, - y, - this.cell, - data.terminalType, - ), - ) - ) { - minDistance = distance - data.closestView = view - data.closestMagnet = magnet - } - } - } - }) - }) - - let terminal - const type = data.terminalType - const closestView = data.closestView as any as CellView - const closestMagnet = data.closestMagnet as any as Element - const changed = prevMagnet !== closestMagnet - - if (prevView && changed) { - prevView.unhighlight(prevMagnet, { - type: 'magnetAdsorbed', - }) - } - - if (closestView) { - if (!changed) { - return - } - closestView.highlight(closestMagnet, { - type: 'magnetAdsorbed', - }) - terminal = closestView.getEdgeTerminal( - closestMagnet, - x, - y, - this.cell, - type, - ) - } else { - terminal = { x, y } - } - - this.cell.setTerminal(type, terminal, {}, { ...data.options, ui: true }) - } - - protected snapArrowheadEnd(data: EventData.ArrowheadDragging) { - // Finish off link snapping. - // Everything except view unhighlighting was already done on pointermove. - const closestView = data.closestView - const closestMagnet = data.closestMagnet - if (closestView && closestMagnet) { - closestView.unhighlight(closestMagnet, { - type: 'magnetAdsorbed', - }) - data.currentMagnet = closestView.findMagnet(closestMagnet) - } - - data.closestView = null - data.closestMagnet = null - } - - protected finishEmbedding(data: EventData.ArrowheadDragging) { - // Resets parent of the edge if embedding is enabled - if (this.renderer.options.embedding.enabled && this.cell.updateParent()) { - // Make sure we don't reverse to the original 'z' index - data.zIndex = null - } - } - - protected fallbackConnection(data: EventData.ArrowheadDragging) { - switch (data.fallbackAction) { - case 'remove': - this.cell.remove({ ui: true }) - break - case 'revert': - default: - this.cell.prop(data.terminalType, data.initialTerminal, { - ui: true, - }) - break - } - } - - protected notifyConnectionEvent( - data: EventData.ArrowheadDragging, - e: Dom.MouseUpEvent, - ) { - const terminalType = data.terminalType - const initialTerminal = data.initialTerminal - const currentTerminal = this.cell[terminalType] - const changed = - currentTerminal && !Edge.equalTerminals(initialTerminal, currentTerminal) - - if (changed) { - const renderer = this.renderer - const previous = initialTerminal as Edge.TerminalCellData - const previousCell = previous.cell - ? renderer.getCellById(previous.cell) - : null - const previousPort = previous.port - const previousView = previousCell - ? renderer.findViewByCell(previousCell) - : null - const previousPoint = - previousCell || data.isNewEdge - ? null - : Point.create(initialTerminal as Edge.TerminalPointData).toJSON() - - const current = currentTerminal as Edge.TerminalCellData - const currentCell = current.cell - ? renderer.getCellById(current.cell) - : null - const currentPort = current.port - const currentView = currentCell - ? renderer.findViewByCell(currentCell) - : null - const currentPoint = currentCell - ? null - : Point.create(currentTerminal as Edge.TerminalPointData).toJSON() - - this.notify('edge:connected', { - e, - previousCell, - previousPort, - previousView, - previousPoint, - currentCell, - currentView, - currentPort, - currentPoint, - previousMagnet: data.initialMagnet, - currentMagnet: data.currentMagnet, - edge: this.cell, - view: this, - type: terminalType, - isNew: data.isNewEdge, - }) - } - } - - protected highlightAvailableMagnets(data: EventData.ArrowheadDragging) { - const renderer = this.renderer - const cells = renderer.model.getCells() - data.marked = {} - - for (let i = 0, ii = cells.length; i < ii; i += 1) { - const view = renderer.findViewByCell(cells[i]) - - if (!view) { - continue - } - - const magnets: Element[] = Array.prototype.slice.call( - view.container.querySelectorAll('[magnet]'), - ) - - if (view.container.getAttribute('magnet') !== 'false') { - magnets.push(view.container) - } - - const availableMagnets = magnets.filter((magnet) => - this.validateConnection( - ...data.getValidateConnectionArgs(view, magnet), - view.getEdgeTerminal( - magnet, - data.x, - data.y, - this.cell, - data.terminalType, - ), - ), - ) - - if (availableMagnets.length > 0) { - // highlight all available magnets - for (let j = 0, jj = availableMagnets.length; j < jj; j += 1) { - view.highlight(availableMagnets[j], { type: 'magnetAvailable' }) - } - - // highlight the entire view - view.highlight(null, { type: 'nodeAvailable' }) - data.marked[view.cell.id] = availableMagnets - } - } - } - - protected unhighlightAvailableMagnets(data: EventData.ArrowheadDragging) { - const marked = data.marked || {} - Object.keys(marked).forEach((id) => { - const view = this.renderer.findViewByCell(id) - - if (view) { - const magnets = marked[id] - magnets.forEach((magnet) => { - view.unhighlight(magnet, { type: 'magnetAvailable' }) - }) - - view.unhighlight(null, { type: 'nodeAvailable' }) - } - }) - data.marked = null - } - - protected startArrowheadDragging( - e: Dom.MouseDownEvent, - x: number, - y: number, - ) { - if (!this.can('arrowheadMovable')) { - this.notifyUnhandledMouseDown(e, x, y) - return - } - - const elem = e.target - const type = elem.getAttribute('data-terminal') as Edge.TerminalType - const data = this.prepareArrowheadDragging(type, { x, y }) - this.setEventData(e, data) - } - - protected dragArrowhead(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - if (this.renderer.options.connecting.snap) { - this.snapArrowhead(x, y, data) - } else { - this.arrowheadDragging(this.getEventTarget(e), x, y, data) - } - } - - protected stopArrowheadDragging(e: Dom.MouseUpEvent, x: number, y: number) { - const renderer = this.renderer - const data = this.getEventData(e) - if (renderer.options.connecting.snap) { - this.snapArrowheadEnd(data) - } else { - this.arrowheadDragged(data, x, y) - } - - const valid = this.validateEdge( - this.cell, - data.terminalType, - data.initialTerminal, - ) - - if (valid) { - this.finishEmbedding(data) - this.notifyConnectionEvent(data, e) - } else { - // If the changed edge is not allowed, revert to its previous state. - this.fallbackConnection(data) - } - this.afterArrowheadDragging(data) - } - - // #endregion - - // #region drag lable - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - startLabelDragging(e: Dom.MouseDownEvent, x: number, y: number) { - if (this.can('edgeLabelMovable')) { - const target = e.currentTarget - const index = parseInt(target.getAttribute('data-index'), 10) - const positionAngle = this.getLabelPositionAngle(index) - const labelPositionArgs = this.getLabelPositionArgs(index) - const defaultLabelPositionArgs = this.getDefaultLabelPositionArgs() - const positionArgs = this.mergeLabelPositionArgs( - labelPositionArgs, - defaultLabelPositionArgs, - ) - - this.setEventData(e, { - index, - positionAngle, - positionArgs, - stopPropagation: true, - action: 'drag-label', - }) - } else { - // If labels can't be dragged no default action is triggered. - this.setEventData(e, { stopPropagation: true }) - } - - this.renderer.graphView.delegateDragEvents(e, this) - } - - dragLabel(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - const originLabel = this.cell.getLabelAt(data.index) - const label = ObjectExt.merge({}, originLabel, { - position: this.getLabelPosition( - x, - y, - data.positionAngle, - data.positionArgs, - ), - }) - this.cell.setLabelAt(data.index, label) - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - stopLabelDragging(e: Dom.MouseUpEvent, x: number, y: number) {} - - // #endregion - // #endregion -} - -export namespace EdgeView { - export interface Options extends CellView.Options {} -} - -export namespace EdgeView { - export interface MouseEventArgs { - e: E - edge: Edge - cell: Edge - view: EdgeView - } - - export interface PositionEventArgs - extends MouseEventArgs, - CellView.PositionEventArgs {} - - export interface EventArgs { - 'edge:click': PositionEventArgs - 'edge:dblclick': PositionEventArgs - 'edge:contextmenu': PositionEventArgs - 'edge:mousedown': PositionEventArgs - 'edge:mousemove': PositionEventArgs - 'edge:mouseup': PositionEventArgs - 'edge:mouseover': MouseEventArgs - 'edge:mouseout': MouseEventArgs - 'edge:mouseenter': MouseEventArgs - 'edge:mouseleave': MouseEventArgs - 'edge:mousewheel': PositionEventArgs & - CellView.MouseDeltaEventArgs - - 'edge:customevent': EdgeView.PositionEventArgs & { - name: string - } - - 'edge:unhandled:mousedown': PositionEventArgs - - 'edge:connected': { - e: Dom.MouseUpEvent - edge: Edge - view: EdgeView - isNew: boolean - type: Edge.TerminalType - previousCell?: Cell | null - previousView?: CellView | null - previousPort?: string | null - previousPoint?: Point.PointLike | null - previousMagnet?: Element | null - currentCell?: Cell | null - currentView?: CellView | null - currentPort?: string | null - currentPoint?: Point.PointLike | null - currentMagnet?: Element | null - } - - 'edge:highlight': { - magnet: Element - view: EdgeView - edge: Edge - cell: Edge - options: CellView.HighlightOptions - } - 'edge:unhighlight': EventArgs['edge:highlight'] - - 'edge:move': PositionEventArgs - 'edge:moving': PositionEventArgs - 'edge:moved': PositionEventArgs - } -} - -export namespace EdgeView { - export const toStringTag = `X6.${EdgeView.name}` - - export function isEdgeView(instance: any): instance is EdgeView { - if (instance == null) { - return false - } - - if (instance instanceof EdgeView) { - return true - } - - const tag = instance[Symbol.toStringTag] - const view = instance as EdgeView - - if ( - (tag == null || tag === toStringTag) && - typeof view.isNodeView === 'function' && - typeof view.isEdgeView === 'function' && - typeof view.confirmUpdate === 'function' && - typeof view.update === 'function' && - typeof view.getConnection === 'function' - ) { - return true - } - - return false - } -} - -namespace EventData { - export interface MousemoveEventData {} - - export interface EdgeDragging { - action: 'drag-edge' - moving: boolean - x: number - y: number - } - - export type ValidateConnectionArgs = [ - CellView | null | undefined, // source view - Element | null | undefined, // source magnet - CellView | null | undefined, // target view - Element | null | undefined, // target magnet - Edge.TerminalType, - EdgeView, - ] - - export interface ArrowheadDragging { - action: 'drag-arrowhead' - x: number - y: number - isNewEdge: boolean - terminalType: Edge.TerminalType - fallbackAction: 'remove' | 'revert' - initialMagnet: Element | null - initialTerminal: Edge.TerminalData - getValidateConnectionArgs: ( - cellView: CellView, - magnet: Element | null, - ) => ValidateConnectionArgs - zIndex?: number | null - pointerEvents?: string | null - /** - * Current event target. - */ - currentTarget?: Element - /** - * Current view under pointer. - */ - currentView?: CellView | null - /** - * Current magnet under pointer. - */ - currentMagnet?: Element | null - closestView?: CellView | null - closestMagnet?: Element | null - marked?: KeyValue | null - options?: KeyValue - } - - export interface LabelDragging { - action: 'drag-label' - index: number - positionAngle: number - positionArgs?: Edge.LabelPositionOptions | null - stopPropagation: true - } -} - -EdgeView.config({ - isSvgElement: true, - priority: 1, - bootstrap: ['render', 'source', 'target'], - actions: { - view: ['render'], - markup: ['render'], - attrs: ['update'], - source: ['source', 'update'], - target: ['target', 'update'], - router: ['update'], - connector: ['update'], - labels: ['labels'], - defaultLabel: ['labels'], - tools: ['tools'], - }, -}) - -EdgeView.registry.register('edge', EdgeView, true) diff --git a/packages/x6-core/src/view/flag.ts b/packages/x6-core/src/view/flag.ts deleted file mode 100644 index 2233bc369c2..00000000000 --- a/packages/x6-core/src/view/flag.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* eslint-disable no-bitwise */ - -import { KeyValue } from '@antv/x6-common' -import { CellView } from './cell' - -export class FlagManager { - protected attrs: { [attr: string]: number } - protected flags: { [name: string]: number } - protected bootstrap: FlagManager.Actions - - protected get cell() { - return this.view.cell - } - - constructor( - protected view: CellView, - actions: KeyValue, - bootstrap: FlagManager.Actions = [], - ) { - const flags: { [name: string]: number } = {} - const attrs: { [attr: string]: number } = {} - - let shift = 0 - Object.keys(actions).forEach((attr) => { - let labels = actions[attr] - if (!Array.isArray(labels)) { - labels = [labels] - } - - labels.forEach((label) => { - let flag = flags[label] - if (!flag) { - shift += 1 - flag = flags[label] = 1 << shift - } - attrs[attr] |= flag - }) - }) - - let labels = bootstrap - if (!Array.isArray(labels)) { - labels = [labels] - } - - labels.forEach((label) => { - if (!flags[label]) { - shift += 1 - flags[label] = 1 << shift - } - }) - - // 26 - 30 are reserved for paper flags - // 31+ overflows maximal number - if (shift > 25) { - throw new Error('Maximum number of flags exceeded.') - } - - this.flags = flags - this.attrs = attrs - this.bootstrap = bootstrap - } - - getFlag(label: FlagManager.Actions) { - const flags = this.flags - if (flags == null) { - return 0 - } - - if (Array.isArray(label)) { - return label.reduce((memo, key) => memo | flags[key], 0) - } - - return flags[label] | 0 - } - - hasAction(flag: number, label: FlagManager.Actions) { - return flag & this.getFlag(label) - } - - removeAction(flag: number, label: FlagManager.Actions) { - return flag ^ (flag & this.getFlag(label)) - } - - getBootstrapFlag() { - return this.getFlag(this.bootstrap) - } - - getChangedFlag() { - let flag = 0 - - if (!this.attrs) { - return flag - } - - Object.keys(this.attrs).forEach((attr) => { - if (this.cell.hasChanged(attr)) { - flag |= this.attrs[attr] - } - }) - - return flag - } -} - -export namespace FlagManager { - export type Action = - | 'render' - | 'update' - | 'resize' - | 'rotate' - | 'translate' - | 'ports' - | 'tools' - | 'source' - | 'target' - | 'labels' - - export type Actions = Action | Action[] -} diff --git a/packages/x6-core/src/view/graph-view.ts b/packages/x6-core/src/view/graph-view.ts deleted file mode 100644 index 8400c560b4a..00000000000 --- a/packages/x6-core/src/view/graph-view.ts +++ /dev/null @@ -1,644 +0,0 @@ -import { Dom, FunctionExt } from '@antv/x6-common' -import { Cell } from '../model' -import { Config } from '../common' -import { View, Markup, CellView } from '.' -import { Renderer } from '../renderer' - -export class GraphView extends View { - public readonly container: HTMLElement - public readonly background: HTMLDivElement - public readonly grid: HTMLDivElement - public readonly svg: SVGSVGElement - public readonly defs: SVGDefsElement - public readonly viewport: SVGGElement - public readonly primer: SVGGElement - public readonly stage: SVGGElement - public readonly decorator: SVGGElement - public readonly overlay: SVGGElement - - private restore: () => void - - protected get options() { - return this.renderer.options - } - - constructor(protected readonly renderer: Renderer) { - super() - - const { selectors, fragment } = Markup.parseJSONMarkup(GraphView.markup) - this.background = selectors.background as HTMLDivElement - this.grid = selectors.grid as HTMLDivElement - this.svg = selectors.svg as SVGSVGElement - this.defs = selectors.defs as SVGDefsElement - this.viewport = selectors.viewport as SVGGElement - this.primer = selectors.primer as SVGGElement - this.stage = selectors.stage as SVGGElement - this.decorator = selectors.decorator as SVGGElement - this.overlay = selectors.overlay as SVGGElement - this.container = this.options.container - this.restore = GraphView.snapshoot(this.container) - - Dom.addClass(this.container, this.prefixClassName('graph')) - Dom.append(this.container, fragment) - - this.delegateEvents() - } - - delegateEvents() { - const ctor = this.constructor as typeof GraphView - super.delegateEvents(ctor.events) - return this - } - - /** - * Guard the specified event. If the event is not interesting, it - * returns `true`, otherwise returns `false`. - */ - guard(e: Dom.EventObject, view?: CellView | null) { - // handled as `contextmenu` type - if (e.type === 'mousedown' && e.button === 2) { - return true - } - - if (this.options.guard && this.options.guard(e, view)) { - return true - } - - if (e.data && e.data.guarded !== undefined) { - return e.data.guarded - } - - if (view && view.cell && Cell.isCell(view.cell)) { - return false - } - - if ( - this.svg === e.target || - this.container === e.target || - this.svg.contains(e.target) - ) { - return false - } - - return true - } - - protected findView(elem: Element) { - return this.renderer.findViewByElem(elem) - } - - protected onDblClick(evt: Dom.DoubleClickEvent) { - if (this.options.preventDefaultDblClick) { - evt.preventDefault() - } - - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - - if (this.guard(e, view)) { - return - } - - const localPoint = this.renderer.snapToGrid(e.clientX, e.clientY) - - if (view) { - view.onDblClick(e, localPoint.x, localPoint.y) - } else { - this.renderer.trigger('blank:dblclick', { - e, - x: localPoint.x, - y: localPoint.y, - }) - } - } - - protected onClick(evt: Dom.ClickEvent) { - if (this.getMouseMovedCount(evt) <= this.options.clickThreshold) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - const localPoint = this.renderer.snapToGrid(e.clientX, e.clientY) - if (view) { - view.onClick(e, localPoint.x, localPoint.y) - } else { - this.renderer.trigger('blank:click', { - e, - x: localPoint.x, - y: localPoint.y, - }) - } - } - } - - protected onContextMenu(evt: Dom.ContextMenuEvent) { - if (this.options.preventDefaultContextMenu) { - evt.preventDefault() - } - - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - const localPoint = this.renderer.snapToGrid(e.clientX, e.clientY) - - if (view) { - view.onContextMenu(e, localPoint.x, localPoint.y) - } else { - this.renderer.trigger('blank:contextmenu', { - e, - x: localPoint.x, - y: localPoint.y, - }) - } - } - - delegateDragEvents(e: Dom.MouseDownEvent, view: CellView | null) { - if (e.data == null) { - e.data = {} - } - this.setEventData(e, { - currentView: view || null, - mouseMovedCount: 0, - startPosition: { - x: e.clientX, - y: e.clientY, - }, - }) - const ctor = this.constructor as typeof GraphView - this.delegateDocumentEvents(ctor.documentEvents, e.data) - this.undelegateEvents() - } - - getMouseMovedCount(e: Dom.EventObject) { - const data = this.getEventData(e) - return data.mouseMovedCount || 0 - } - - protected onMouseDown(evt: Dom.MouseDownEvent) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - if (this.options.preventDefaultMouseDown) { - e.preventDefault() - } - - const localPoint = this.renderer.snapToGrid(e.clientX, e.clientY) - - if (view) { - view.onMouseDown(e, localPoint.x, localPoint.y) - } else { - if (this.options.preventDefaultBlankAction) { - e.preventDefault() - } - - this.renderer.trigger('blank:mousedown', { - e, - x: localPoint.x, - y: localPoint.y, - }) - } - - this.delegateDragEvents(e, view) - } - - protected onMouseMove(evt: Dom.MouseMoveEvent) { - const data = this.getEventData(evt) - - const startPosition = data.startPosition - if ( - startPosition && - startPosition.x === evt.clientX && - startPosition.y === evt.clientY - ) { - return - } - - if (data.mouseMovedCount == null) { - data.mouseMovedCount = 0 - } - data.mouseMovedCount += 1 - const mouseMovedCount = data.mouseMovedCount - if (mouseMovedCount <= this.options.moveThreshold) { - return - } - - const e = this.normalizeEvent(evt) - const localPoint = this.renderer.snapToGrid(e.clientX, e.clientY) - - const view = data.currentView - if (view) { - view.onMouseMove(e, localPoint.x, localPoint.y) - } else { - this.renderer.trigger('blank:mousemove', { - e, - x: localPoint.x, - y: localPoint.y, - }) - } - - this.setEventData(e, data) - } - - protected onMouseUp(e: Dom.MouseUpEvent) { - this.undelegateDocumentEvents() - - const normalized = this.normalizeEvent(e) - const localPoint = this.renderer.snapToGrid( - normalized.clientX, - normalized.clientY, - ) - const data = this.getEventData(e) - const view = data.currentView - if (view) { - view.onMouseUp(normalized, localPoint.x, localPoint.y) - } else { - this.renderer.trigger('blank:mouseup', { - e: normalized, - x: localPoint.x, - y: localPoint.y, - }) - } - - if (!e.isPropagationStopped()) { - const ev = new Dom.EventObject(e as any, { - type: 'click', - data: e.data, - }) as Dom.ClickEvent - this.onClick(ev) - } - - e.stopImmediatePropagation() - - this.delegateEvents() - } - - protected onMouseOver(evt: Dom.MouseOverEvent) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - if (view) { - view.onMouseOver(e) - } else { - // prevent border of paper from triggering this - if (this.container === e.target) { - return - } - this.renderer.trigger('blank:mouseover', { e }) - } - } - - protected onMouseOut(evt: Dom.MouseOutEvent) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - - if (this.guard(e, view)) { - return - } - - if (view) { - view.onMouseOut(e) - } else { - if (this.container === e.target) { - return - } - this.renderer.trigger('blank:mouseout', { e }) - } - } - - protected onMouseEnter(evt: Dom.MouseEnterEvent) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - const relatedView = this.renderer.findViewByElem(e.relatedTarget as Element) - if (view) { - if (relatedView === view) { - // mouse moved from tool to view - return - } - view.onMouseEnter(e) - } else { - if (relatedView) { - return - } - this.renderer.trigger('graph:mouseenter', { e }) - } - } - - protected onMouseLeave(evt: Dom.MouseLeaveEvent) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - const relatedView = this.renderer.findViewByElem(e.relatedTarget as Element) - - if (view) { - if (relatedView === view) { - // mouse moved from view to tool - return - } - view.onMouseLeave(e) - } else { - if (relatedView) { - return - } - this.renderer.trigger('graph:mouseleave', { e }) - } - } - - protected onMouseWheel(evt: Dom.EventObject) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - const originalEvent = e.originalEvent as WheelEvent - const localPoint = this.renderer.snapToGrid( - originalEvent.clientX, - originalEvent.clientY, - ) - const delta = Math.max( - -1, - Math.min(1, (originalEvent as any).wheelDelta || -originalEvent.detail), - ) - - if (view) { - view.onMouseWheel(e, localPoint.x, localPoint.y, delta) - } else { - this.renderer.trigger('blank:mousewheel', { - e, - delta, - x: localPoint.x, - y: localPoint.y, - }) - } - } - - protected onCustomEvent(evt: Dom.MouseDownEvent) { - const elem = evt.currentTarget - const event = elem.getAttribute('event') || elem.getAttribute('data-event') - if (event) { - const view = this.findView(elem) - if (view) { - const e = this.normalizeEvent(evt) - if (this.guard(e, view)) { - return - } - - const localPoint = this.renderer.snapToGrid( - e.clientX as number, - e.clientY as number, - ) - view.onCustomEvent(e, event, localPoint.x, localPoint.y) - } - } - } - - protected handleMagnetEvent( - evt: T, - handler: ( - this: Renderer, - view: CellView, - e: T, - magnet: Element, - x: number, - y: number, - ) => void, - ) { - const magnetElem = evt.currentTarget - const magnetValue = magnetElem.getAttribute('magnet') as string - if (magnetValue && magnetValue.toLowerCase() !== 'false') { - const view = this.findView(magnetElem) - if (view) { - const e = this.normalizeEvent(evt) - if (this.guard(e, view)) { - return - } - const localPoint = this.renderer.snapToGrid( - e.clientX as number, - e.clientY as number, - ) - FunctionExt.call( - handler, - this.renderer, - view, - e, - magnetElem, - localPoint.x, - localPoint.y, - ) - } - } - } - - protected onMagnetMouseDown(e: Dom.MouseDownEvent) { - this.handleMagnetEvent(e, (view, e, magnet, x, y) => { - view.onMagnetMouseDown(e, magnet, x, y) - }) - } - - protected onMagnetDblClick(e: Dom.DoubleClickEvent) { - this.handleMagnetEvent(e, (view, e, magnet, x, y) => { - view.onMagnetDblClick(e, magnet, x, y) - }) - } - - protected onMagnetContextMenu(e: Dom.ContextMenuEvent) { - if (this.options.preventDefaultContextMenu) { - e.preventDefault() - } - this.handleMagnetEvent(e, (view, e, magnet, x, y) => { - view.onMagnetContextMenu(e, magnet, x, y) - }) - } - - protected onLabelMouseDown(evt: Dom.MouseDownEvent) { - const labelNode = evt.currentTarget - const view = this.findView(labelNode) - if (view) { - const e = this.normalizeEvent(evt) - if (this.guard(e, view)) { - return - } - - const localPoint = this.renderer.snapToGrid(e.clientX, e.clientY) - view.onLabelMouseDown(e, localPoint.x, localPoint.y) - } - } - - protected onImageDragStart() { - // This is the only way to prevent image dragging in Firefox that works. - // Setting -moz-user-select: none, draggable="false" attribute or - // user-drag: none didn't help. - return false - } - - @View.dispose() - dispose() { - this.undelegateEvents() - this.undelegateDocumentEvents() - this.restore() - this.restore = () => {} - } -} - -export namespace GraphView { - export type SortType = 'none' | 'approx' | 'exact' -} - -export namespace GraphView { - const prefixCls = `${Config.prefixCls}-graph` - - export const markup: Markup.JSONMarkup[] = [ - { - ns: Dom.ns.xhtml, - tagName: 'div', - selector: 'background', - className: `${prefixCls}-background`, - }, - { - ns: Dom.ns.xhtml, - tagName: 'div', - selector: 'grid', - className: `${prefixCls}-grid`, - }, - { - ns: Dom.ns.svg, - tagName: 'svg', - selector: 'svg', - className: `${prefixCls}-svg`, - attrs: { - width: '100%', - height: '100%', - 'xmlns:xlink': Dom.ns.xlink, - }, - children: [ - { - tagName: 'defs', - selector: 'defs', - }, - { - tagName: 'g', - selector: 'viewport', - className: `${prefixCls}-svg-viewport`, - children: [ - { - tagName: 'g', - selector: 'primer', - className: `${prefixCls}-svg-primer`, - }, - { - tagName: 'g', - selector: 'stage', - className: `${prefixCls}-svg-stage`, - }, - { - tagName: 'g', - selector: 'decorator', - className: `${prefixCls}-svg-decorator`, - }, - { - tagName: 'g', - selector: 'overlay', - className: `${prefixCls}-svg-overlay`, - }, - ], - }, - ], - }, - ] - - export function snapshoot(elem: Element) { - const cloned = elem.cloneNode() as Element - elem.childNodes.forEach((child) => cloned.appendChild(child)) - - return () => { - // remove all children - Dom.empty(elem) - - // remove all attributes - while (elem.attributes.length > 0) { - elem.removeAttribute(elem.attributes[0].name) - } - - // restore attributes - for (let i = 0, l = cloned.attributes.length; i < l; i += 1) { - const attr = cloned.attributes[i] - elem.setAttribute(attr.name, attr.value) - } - - // restore children - cloned.childNodes.forEach((child) => elem.appendChild(child)) - } - } -} - -export namespace GraphView { - const prefixCls = Config.prefixCls - - export const events = { - dblclick: 'onDblClick', - contextmenu: 'onContextMenu', - touchstart: 'onMouseDown', - mousedown: 'onMouseDown', - mouseover: 'onMouseOver', - mouseout: 'onMouseOut', - mouseenter: 'onMouseEnter', - mouseleave: 'onMouseLeave', - mousewheel: 'onMouseWheel', - DOMMouseScroll: 'onMouseWheel', - [`mouseenter .${prefixCls}-cell`]: 'onMouseEnter', - [`mouseleave .${prefixCls}-cell`]: 'onMouseLeave', - [`mouseenter .${prefixCls}-cell-tools`]: 'onMouseEnter', - [`mouseleave .${prefixCls}-cell-tools`]: 'onMouseLeave', - [`mousedown .${prefixCls}-cell [event]`]: 'onCustomEvent', - [`touchstart .${prefixCls}-cell [event]`]: 'onCustomEvent', - [`mousedown .${prefixCls}-cell [data-event]`]: 'onCustomEvent', - [`touchstart .${prefixCls}-cell [data-event]`]: 'onCustomEvent', - [`dblclick .${prefixCls}-cell [magnet]`]: 'onMagnetDblClick', - [`contextmenu .${prefixCls}-cell [magnet]`]: 'onMagnetContextMenu', - [`mousedown .${prefixCls}-cell [magnet]`]: 'onMagnetMouseDown', - [`touchstart .${prefixCls}-cell [magnet]`]: 'onMagnetMouseDown', - [`dblclick .${prefixCls}-cell [data-magnet]`]: 'onMagnetDblClick', - [`contextmenu .${prefixCls}-cell [data-magnet]`]: 'onMagnetContextMenu', - [`mousedown .${prefixCls}-cell [data-magnet]`]: 'onMagnetMouseDown', - [`touchstart .${prefixCls}-cell [data-magnet]`]: 'onMagnetMouseDown', - [`dragstart .${prefixCls}-cell image`]: 'onImageDragStart', - [`mousedown .${prefixCls}-edge .${prefixCls}-edge-label`]: - 'onLabelMouseDown', - [`touchstart .${prefixCls}-edge .${prefixCls}-edge-label`]: - 'onLabelMouseDown', - } - - export const documentEvents = { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - } -} - -namespace EventData { - export interface Moving { - mouseMovedCount?: number - startPosition?: { x: number; y: number } - currentView?: CellView | null - } -} diff --git a/packages/x6-core/src/view/index.ts b/packages/x6-core/src/view/index.ts deleted file mode 100644 index d202036ceb0..00000000000 --- a/packages/x6-core/src/view/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './markup' -export * from './view' -export * from './cell' -export * from './edge' -export * from './node' -export * from './tool' -export * from './graph-view' diff --git a/packages/x6-core/src/view/markup.ts b/packages/x6-core/src/view/markup.ts deleted file mode 100644 index c0b7ba3bff6..00000000000 --- a/packages/x6-core/src/view/markup.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { ObjectExt, Dom, Vector, KeyValue, Nilable } from '@antv/x6-common' -import { Attr } from '../registry' - -export type Markup = string | Markup.JSONMarkup | Markup.JSONMarkup[] - -// eslint-disable-next-line -export namespace Markup { - export type Selectors = KeyValue - - export interface JSONMarkup { - /** - * The namespace URI of the element. It defaults to SVG namespace - * `"http://www.w3.org/2000/svg"`. - */ - ns?: string | null - - /** - * The type of element to be created. - */ - tagName: string - - /** - * A unique selector for targeting the element within the `attr` - * cell attribute. - */ - selector?: string | null - - /** - * A selector for targeting multiple elements within the `attr` - * cell attribute. The group selector name must not be the same - * as an existing selector name. - */ - groupSelector?: string | string[] | null - - attrs?: Attr.SimpleAttrs - - style?: Record - - className?: string | string[] - - children?: JSONMarkup[] - - textContent?: string - } - - export interface ParseResult { - fragment: DocumentFragment - selectors: Selectors - groups: KeyValue - } -} - -// eslint-disable-next-line -export namespace Markup { - export function isJSONMarkup(markup?: Nilable) { - return markup != null && !isStringMarkup(markup) - } - - export function isStringMarkup(markup?: Nilable): markup is string { - return markup != null && typeof markup === 'string' - } - - export function clone(markup?: Nilable) { - return markup == null || isStringMarkup(markup) - ? markup - : ObjectExt.cloneDeep(markup) - } - - /** - * Removes blank space in markup to prevent create empty text node. - */ - export function sanitize(markup: string) { - return `${markup}` - .trim() - .replace(/[\r|\n]/g, ' ') - .replace(/>\s+<') - } - - export function parseJSONMarkup( - markup: JSONMarkup | JSONMarkup[], - options: { ns?: string } = { ns: Dom.ns.svg }, - ): ParseResult { - const fragment = document.createDocumentFragment() - const groups: KeyValue = {} - const selectors: Selectors = {} - - const queue: { - markup: JSONMarkup[] - parent: Element | DocumentFragment - ns?: string - }[] = [ - { - markup: Array.isArray(markup) ? markup : [markup], - parent: fragment, - ns: options.ns, - }, - ] - - while (queue.length > 0) { - const item = queue.pop()! - let ns = item.ns || Dom.ns.svg - const defines = item.markup - const parentNode = item.parent - - defines.forEach((define) => { - // tagName - const tagName = define.tagName - if (!tagName) { - throw new TypeError('Invalid tagName') - } - - // ns - if (define.ns) { - ns = define.ns - } - - const node = ns - ? Dom.createElementNS(tagName, ns) - : Dom.createElement(tagName) - - // attrs - const attrs = define.attrs - if (attrs) { - Dom.attr(node, Dom.kebablizeAttrs(attrs)) - } - - // style - const style = define.style - if (style) { - Dom.css(node, style) - } - - // classname - const className = define.className - if (className != null) { - node.setAttribute( - 'class', - Array.isArray(className) ? className.join(' ') : className, - ) - } - - // textContent - if (define.textContent) { - node.textContent = define.textContent - } - - // selector - const selector = define.selector - if (selector != null) { - if (selectors[selector]) { - throw new TypeError('Selector must be unique') - } - - selectors[selector] = node - } - - // group - if (define.groupSelector) { - let nodeGroups = define.groupSelector - if (!Array.isArray(nodeGroups)) { - nodeGroups = [nodeGroups] - } - - nodeGroups.forEach((name) => { - if (!groups[name]) { - groups[name] = [] - } - groups[name].push(node) - }) - } - - parentNode.appendChild(node) - - // children - const children = define.children - if (Array.isArray(children)) { - queue.push({ ns, markup: children, parent: node }) - } - }) - } - - Object.keys(groups).forEach((groupName) => { - if (selectors[groupName]) { - throw new Error('Ambiguous group selector') - } - selectors[groupName] = groups[groupName] - }) - - return { fragment, selectors, groups } - } - - function createContainer(firstChild: Element) { - return firstChild instanceof SVGElement - ? Dom.createSvgElement('g') - : Dom.createElement('div') - } - - export function renderMarkup(markup: Markup): { - elem?: Element - selectors?: Selectors - } { - if (isStringMarkup(markup)) { - const nodes = Vector.createVectors(markup) - const count = nodes.length - - if (count === 1) { - return { - elem: nodes[0].node as Element, - } - } - - if (count > 1) { - const elem = createContainer(nodes[0].node) - nodes.forEach((node) => { - elem.appendChild(node.node) - }) - - return { elem } - } - - return {} - } - - const result = parseJSONMarkup(markup) - const fragment = result.fragment - let elem: Element | null = null - if (fragment.childNodes.length > 1) { - elem = createContainer(fragment.firstChild as Element) - elem.appendChild(fragment) - } else { - elem = fragment.firstChild as Element - } - - return { elem, selectors: result.selectors } - } - - export function parseLabelStringMarkup(markup: string) { - const children = Vector.createVectors(markup) - const fragment = document.createDocumentFragment() - for (let i = 0, n = children.length; i < n; i += 1) { - const currentChild = children[i].node - fragment.appendChild(currentChild) - } - - return { fragment, selectors: {} } - } -} - -// eslint-disable-next-line -export namespace Markup { - export function getSelector( - elem: Element, - stop: Element, - prev?: string, - ): string | undefined { - if (elem != null) { - let selector - const tagName = elem.tagName.toLowerCase() - - if (elem === stop) { - if (typeof prev === 'string') { - selector = `> ${tagName} > ${prev}` - } else { - selector = `> ${tagName}` - } - return selector - } - - const parent = elem.parentNode - if (parent && parent.childNodes.length > 1) { - const nth = Dom.index(elem) + 1 - selector = `${tagName}:nth-child(${nth})` - } else { - selector = tagName - } - - if (prev) { - selector += ` > ${prev}` - } - - return getSelector(elem.parentNode as Element, stop, selector) - } - - return prev - } -} - -// eslint-disable-next-line -export namespace Markup { - export function getPortContainerMarkup(): Markup { - return 'g' - } - - export function getPortMarkup(): Markup { - return { - tagName: 'circle', - selector: 'circle', - attrs: { - r: 10, - fill: '#FFFFFF', - stroke: '#000000', - }, - } - } - - export function getPortLabelMarkup(): Markup { - return { - tagName: 'text', - selector: 'text', - attrs: { - fill: '#000000', - }, - } - } -} - -// eslint-disable-next-line -export namespace Markup { - export function getEdgeMarkup(): Markup { - return [ - { - tagName: 'path', - selector: 'wrap', - groupSelector: 'lines', - attrs: { - fill: 'none', - cursor: 'pointer', - stroke: 'transparent', - strokeLinecap: 'round', - }, - }, - { - tagName: 'path', - selector: 'line', - groupSelector: 'lines', - attrs: { - fill: 'none', - pointerEvents: 'none', - }, - }, - ] - } -} - -// eslint-disable-next-line -export namespace Markup { - export function getForeignObjectMarkup(bare = false): Markup.JSONMarkup { - return { - tagName: 'foreignObject', - selector: 'fo', - children: [ - { - ns: Dom.ns.xhtml, - tagName: 'body', - selector: 'foBody', - attrs: { - xmlns: Dom.ns.xhtml, - }, - style: { - width: '100%', - height: '100%', - background: 'transparent', - }, - children: bare - ? [] - : [ - { - tagName: 'div', - selector: 'foContent', - style: { - width: '100%', - height: '100%', - }, - }, - ], - }, - ], - } - } -} diff --git a/packages/x6-core/src/view/node.ts b/packages/x6-core/src/view/node.ts deleted file mode 100644 index 0203a83246b..00000000000 --- a/packages/x6-core/src/view/node.ts +++ /dev/null @@ -1,1241 +0,0 @@ -import { ArrayExt, FunctionExt, Dom, KeyValue } from '@antv/x6-common' -import { Rectangle, Point, Util as GeomUtil } from '@antv/x6-geometry' -import { Config } from '../common' -import { PortLayout } from '../registry' -import { Cell } from '../model/cell' -import { Node } from '../model/node' -import { Edge } from '../model/edge' -import { PortManager } from '../model/port' -import { CellView } from './cell' -import { EdgeView } from './edge' -import { Markup } from './markup' -import { AttrManager } from './attr' -import { Renderer } from '../renderer' - -export class NodeView< - Entity extends Node = Node, - Options extends NodeView.Options = NodeView.Options, -> extends CellView { - protected portsCache: { [id: string]: NodeView.PortCache } = {} - - protected get [Symbol.toStringTag]() { - return NodeView.toStringTag - } - - protected getContainerClassName() { - const classList = [ - super.getContainerClassName(), - this.prefixClassName('node'), - ] - if (!this.can('nodeMovable')) { - classList.push(this.prefixClassName('node-immovable')) - } - return classList.join(' ') - } - - protected updateClassName(e: Dom.MouseEnterEvent) { - const target = e.target - if (target.hasAttribute('magnet')) { - // port - const className = this.prefixClassName('port-unconnectable') - if (this.can('magnetConnectable')) { - Dom.removeClass(target, className) - } else { - Dom.addClass(target, className) - } - } else { - // node - const className = this.prefixClassName('node-immovable') - if (this.can('nodeMovable')) { - this.removeClass(className) - } else { - this.addClass(className) - } - } - } - - isNodeView(): this is NodeView { - return true - } - - confirmUpdate(flag: number) { - let ret = flag - if (this.hasAction(ret, 'ports')) { - this.removePorts() - this.cleanPortsCache() - } - - if (this.hasAction(ret, 'render')) { - this.render() - ret = this.removeAction(ret, [ - 'render', - 'update', - 'resize', - 'translate', - 'rotate', - 'ports', - 'tools', - ]) - } else { - ret = this.handleAction( - ret, - 'resize', - () => this.resize(), - 'update', // Resize method is calling `update()` internally - ) - - ret = this.handleAction( - ret, - 'update', - () => this.update(), - // `update()` will render ports when useCSSSelectors are enabled - Config.useCSSSelector ? 'ports' : null, - ) - - ret = this.handleAction(ret, 'translate', () => this.translate()) - ret = this.handleAction(ret, 'rotate', () => this.rotate()) - ret = this.handleAction(ret, 'ports', () => this.renderPorts()) - ret = this.handleAction(ret, 'tools', () => this.renderTools()) - } - - return ret - } - - update() { - this.cleanCache() - - // When CSS selector strings are used, make sure no rule matches port nodes. - if (Config.useCSSSelector) { - this.removePorts() - } - - const node = this.cell - const size = node.getSize() - const attrs = node.getAttrs() - this.updateAttrs(this.container, attrs, { - rootBBox: new Rectangle(0, 0, size.width, size.height), - selectors: this.selectors, - }) - - if (Config.useCSSSelector) { - this.renderPorts() - } - } - - protected renderMarkup() { - const markup = this.cell.markup - if (markup) { - if (typeof markup === 'string') { - throw new TypeError('Not support string markup.') - } - - return this.renderJSONMarkup(markup) - } - - throw new TypeError('Invalid node markup.') - } - - protected renderJSONMarkup(markup: Markup.JSONMarkup | Markup.JSONMarkup[]) { - const ret = this.parseJSONMarkup(markup, this.container) - this.selectors = ret.selectors - this.container.appendChild(ret.fragment) - } - - render() { - this.empty() - this.renderMarkup() - - this.resize() - this.updateTransform() - - if (!Config.useCSSSelector) { - this.renderPorts() - } - - this.renderTools() - - return this - } - - resize() { - if (this.cell.getAngle()) { - this.rotate() - } - - this.update() - } - - translate() { - this.updateTransform() - } - - rotate() { - this.updateTransform() - } - - protected getTranslationString() { - const position = this.cell.getPosition() - return `translate(${position.x},${position.y})` - } - - protected getRotationString() { - const angle = this.cell.getAngle() - if (angle) { - const size = this.cell.getSize() - return `rotate(${angle},${size.width / 2},${size.height / 2})` - } - } - - protected updateTransform() { - let transform = this.getTranslationString() - const rot = this.getRotationString() - if (rot) { - transform += ` ${rot}` - } - this.container.setAttribute('transform', transform) - } - - // #region ports - - findPortElem(portId?: string, selector?: string) { - const cache = portId ? this.portsCache[portId] : null - if (!cache) { - return null - } - const portRoot = cache.portContentElement - const portSelectors = cache.portContentSelectors || {} - return this.findOne(selector, portRoot, portSelectors) - } - - protected cleanPortsCache() { - this.portsCache = {} - } - - protected removePorts() { - Object.keys(this.portsCache).forEach((portId) => { - const cached = this.portsCache[portId] - Dom.remove(cached.portElement) - }) - } - - protected renderPorts() { - const container = this.container - // References to rendered elements without z-index - const references: Element[] = [] - container.childNodes.forEach((child) => { - references.push(child as Element) - }) - const parsedPorts = this.cell.getParsedPorts() - const portsGropsByZ = ArrayExt.groupBy(parsedPorts, 'zIndex') - const autoZIndexKey = 'auto' - - // render non-z first - if (portsGropsByZ[autoZIndexKey]) { - portsGropsByZ[autoZIndexKey].forEach((port) => { - const portElement = this.getPortElement(port) - container.append(portElement) - references.push(portElement) - }) - } - - Object.keys(portsGropsByZ).forEach((key) => { - if (key !== autoZIndexKey) { - const zIndex = parseInt(key, 10) - this.appendPorts(portsGropsByZ[key], zIndex, references) - } - }) - - this.updatePorts() - } - - protected appendPorts( - ports: PortManager.Port[], - zIndex: number, - refs: Element[], - ) { - const elems = ports.map((p) => this.getPortElement(p)) - if (refs[zIndex] || zIndex < 0) { - Dom.before(refs[Math.max(zIndex, 0)], elems) - } else { - Dom.append(this.container, elems) - } - } - - protected getPortElement(port: PortManager.Port) { - const cached = this.portsCache[port.id] - if (cached) { - return cached.portElement - } - - return this.createPortElement(port) - } - - protected createPortElement(port: PortManager.Port) { - let renderResult = Markup.renderMarkup(this.cell.getPortContainerMarkup()) - const portElement = renderResult.elem - if (portElement == null) { - throw new Error('Invalid port container markup.') - } - - renderResult = Markup.renderMarkup(this.getPortMarkup(port)) - const portContentElement = renderResult.elem - const portContentSelectors = renderResult.selectors - - if (portContentElement == null) { - throw new Error('Invalid port markup.') - } - - this.setAttrs( - { - port: port.id, - 'port-group': port.group, - }, - portContentElement, - ) - - Dom.addClass(portElement, 'x6-port') - Dom.addClass(portContentElement, 'x6-port-body') - portElement.appendChild(portContentElement) - - let portSelectors: Markup.Selectors | undefined = portContentSelectors - let portLabelElement: Element | undefined - let portLabelSelectors: Markup.Selectors | null | undefined - const existLabel = this.existPortLabel(port) - if (existLabel) { - renderResult = Markup.renderMarkup(this.getPortLabelMarkup(port.label)) - portLabelElement = renderResult.elem - portLabelSelectors = renderResult.selectors - if (portLabelElement == null) { - throw new Error('Invalid port label markup.') - } - if (portContentSelectors && portLabelSelectors) { - // eslint-disable-next-line - for (const key in portLabelSelectors) { - if (portContentSelectors[key] && key !== this.rootSelector) { - throw new Error('Selectors within port must be unique.') - } - } - portSelectors = { - ...portContentSelectors, - ...portLabelSelectors, - } - } - Dom.addClass(portLabelElement, 'x6-port-label') - portElement.appendChild(portLabelElement) - } - - this.portsCache[port.id] = { - portElement, - portSelectors, - portLabelElement, - portLabelSelectors, - portContentElement, - portContentSelectors, - } - - // todo - // this.graph.hook.onPortRendered({ - // port, - // node: this.cell, - // container: portElement, - // selectors: portSelectors, - // labelContainer: portLabelElement, - // labelSelectors: portLabelSelectors, - // contentContainer: portContentElement, - // contentSelectors: portContentSelectors, - // }) - - return portElement - } - - protected updatePorts() { - const groups = this.cell.getParsedGroups() - Object.keys(groups).forEach((groupName) => this.updatePortGroup(groupName)) - } - - protected updatePortGroup(groupName?: string) { - const bbox = Rectangle.fromSize(this.cell.getSize()) - const metrics = this.cell.getPortsLayoutByGroup(groupName, bbox) - - for (let i = 0, n = metrics.length; i < n; i += 1) { - const metric = metrics[i] - const portId = metric.portId - const cached = this.portsCache[portId] || {} - const portLayout = metric.portLayout - this.applyPortTransform(cached.portElement, portLayout) - if (metric.portAttrs != null) { - const options: Partial = { - selectors: cached.portSelectors || {}, - } - - if (metric.portSize) { - options.rootBBox = Rectangle.fromSize(metric.portSize) - } - - this.updateAttrs(cached.portElement, metric.portAttrs, options) - } - - const labelLayout = metric.labelLayout - if (labelLayout && cached.portLabelElement) { - this.applyPortTransform( - cached.portLabelElement, - labelLayout, - -(portLayout.angle || 0), - ) - - if (labelLayout.attrs) { - const options: Partial = { - selectors: cached.portLabelSelectors || {}, - } - - if (metric.labelSize) { - options.rootBBox = Rectangle.fromSize(metric.labelSize) - } - - this.updateAttrs(cached.portLabelElement, labelLayout.attrs, options) - } - } - } - } - - protected applyPortTransform( - element: Element, - layout: PortLayout.Result, - initialAngle = 0, - ) { - const angle = layout.angle - const position = layout.position - const matrix = Dom.createSVGMatrix() - .rotate(initialAngle) - .translate(position.x || 0, position.y || 0) - .rotate(angle || 0) - - Dom.transform(element as SVGElement, matrix, { absolute: true }) - } - - protected getPortMarkup(port: PortManager.Port) { - return port.markup || this.cell.portMarkup - } - - protected getPortLabelMarkup(label: PortManager.Label) { - return label.markup || this.cell.portLabelMarkup - } - - protected existPortLabel(port: PortManager.Port) { - const traverse = (attrs: KeyValue | undefined): boolean => { - if (attrs) { - const keys = Object.keys(attrs) - for (let i = 0, len = keys.length; i < len; i += 1) { - const key = keys[i] - if (key === 'text') { - return true - } - const value = attrs[key] - if (typeof value === 'object') { - return traverse(value) - } - } - } - return false - } - return traverse(port.attrs) - } - - // #endregion - - // #region events - - protected getEventArgs(e: E): NodeView.MouseEventArgs - protected getEventArgs( - e: E, - x: number, - y: number, - ): NodeView.PositionEventArgs - protected getEventArgs(e: E, x?: number, y?: number) { - const view = this // eslint-disable-line - const node = view.cell - const cell = node - if (x == null || y == null) { - return { e, view, node, cell } as NodeView.MouseEventArgs - } - return { e, x, y, view, node, cell } as NodeView.PositionEventArgs - } - - notifyMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - super.onMouseDown(e, x, y) - this.notify('node:mousedown', this.getEventArgs(e, x, y)) - } - - notifyMouseMove(e: Dom.MouseMoveEvent, x: number, y: number) { - super.onMouseMove(e, x, y) - this.notify('node:mousemove', this.getEventArgs(e, x, y)) - } - - notifyMouseUp(e: Dom.MouseUpEvent, x: number, y: number) { - super.onMouseUp(e, x, y) - this.notify('node:mouseup', this.getEventArgs(e, x, y)) - } - - onClick(e: Dom.ClickEvent, x: number, y: number) { - super.onClick(e, x, y) - this.notify('node:click', this.getEventArgs(e, x, y)) - } - - onDblClick(e: Dom.DoubleClickEvent, x: number, y: number) { - super.onDblClick(e, x, y) - this.notify('node:dblclick', this.getEventArgs(e, x, y)) - } - - onContextMenu(e: Dom.ContextMenuEvent, x: number, y: number) { - super.onContextMenu(e, x, y) - this.notify('node:contextmenu', this.getEventArgs(e, x, y)) - } - - onMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - if (this.isPropagationStopped(e)) { - return - } - this.notifyMouseDown(e, x, y) - this.startNodeDragging(e, x, y) - } - - onMouseMove(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - const action = data.action - if (action === 'magnet') { - this.dragMagnet(e, x, y) - } else { - if (action === 'move') { - const meta = data as EventData.Moving - const view = meta.targetView || this - view.dragNode(e, x, y) - view.notify('node:moving', { - e, - x, - y, - view, - cell: view.cell, - node: view.cell, - }) - } - this.notifyMouseMove(e, x, y) - } - - this.setEventData(e, data) - } - - onMouseUp(e: Dom.MouseUpEvent, x: number, y: number) { - const data = this.getEventData(e) - const action = data.action - if (action === 'magnet') { - this.stopMagnetDragging(e, x, y) - } else { - this.notifyMouseUp(e, x, y) - if (action === 'move') { - const meta = data as EventData.Moving - const view = meta.targetView || this - view.stopNodeDragging(e, x, y) - } - } - - const magnet = (data as EventData.Magnet).targetMagnet - if (magnet) { - this.onMagnetClick(e, magnet, x, y) - } - - this.checkMouseleave(e) - } - - onMouseOver(e: Dom.MouseOverEvent) { - super.onMouseOver(e) - this.notify('node:mouseover', this.getEventArgs(e)) - } - - onMouseOut(e: Dom.MouseOutEvent) { - super.onMouseOut(e) - this.notify('node:mouseout', this.getEventArgs(e)) - } - - onMouseEnter(e: Dom.MouseEnterEvent) { - this.updateClassName(e) - super.onMouseEnter(e) - this.notify('node:mouseenter', this.getEventArgs(e)) - } - - onMouseLeave(e: Dom.MouseLeaveEvent) { - super.onMouseLeave(e) - this.notify('node:mouseleave', this.getEventArgs(e)) - } - - onMouseWheel(e: Dom.EventObject, x: number, y: number, delta: number) { - super.onMouseWheel(e, x, y, delta) - this.notify('node:mousewheel', { - delta, - ...this.getEventArgs(e, x, y), - }) - } - - onMagnetClick(e: Dom.MouseUpEvent, magnet: Element, x: number, y: number) { - const renderer = this.renderer - const count = renderer.graphView.getMouseMovedCount(e) - if (count > renderer.options.clickThreshold) { - return - } - this.notify('node:magnet:click', { - magnet, - ...this.getEventArgs(e, x, y), - }) - } - - onMagnetDblClick( - e: Dom.DoubleClickEvent, - magnet: Element, - x: number, - y: number, - ) { - this.notify('node:magnet:dblclick', { - magnet, - ...this.getEventArgs(e, x, y), - }) - } - - onMagnetContextMenu( - e: Dom.ContextMenuEvent, - magnet: Element, - x: number, - y: number, - ) { - this.notify('node:magnet:contextmenu', { - magnet, - ...this.getEventArgs(e, x, y), - }) - } - - onMagnetMouseDown( - e: Dom.MouseDownEvent, - magnet: Element, - x: number, - y: number, - ) { - this.startMagnetDragging(e, x, y) - } - - onCustomEvent(e: Dom.MouseDownEvent, name: string, x: number, y: number) { - this.notify('node:customevent', { name, ...this.getEventArgs(e, x, y) }) - super.onCustomEvent(e, name, x, y) - } - - protected prepareEmbedding(e: Dom.MouseMoveEvent) { - const renderer = this.renderer - const data = this.getEventData(e) - const node = data.cell || this.cell - const view = renderer.findViewByCell(node) - const localPoint = renderer.snapToGrid(e.clientX, e.clientY) - - this.notify('node:embed', { - e, - node, - view, - cell: node, - x: localPoint.x, - y: localPoint.y, - currentParent: node.getParent(), - }) - } - - processEmbedding(e: Dom.MouseMoveEvent, data: EventData.MovingTargetNode) { - const cell = data.cell || this.cell - const renderer = data.renderer || this.renderer - const options = renderer.options.embedding - const findParent = options.findParent - - let candidates = - typeof findParent === 'function' - ? ( - FunctionExt.call(findParent, renderer, { - view: this, - node: this.cell, - }) as Cell[] - ).filter((c) => { - return ( - Cell.isCell(c) && - this.cell.id !== c.id && - !c.isDescendantOf(this.cell) - ) - }) - : renderer.model.getNodesUnderNode(cell, { - by: findParent as Rectangle.KeyPoint, - }) - - // Picks the node with the highest `z` index - if (options.frontOnly) { - candidates = candidates.slice(-1) - } - - let newCandidateView = null - const prevCandidateView = data.candidateEmbedView - const validateEmbeding = options.validate - for (let i = candidates.length - 1; i >= 0; i -= 1) { - const candidate = candidates[i] - - if (prevCandidateView && prevCandidateView.cell.id === candidate.id) { - // candidate remains the same - newCandidateView = prevCandidateView - break - } else { - const view = candidate.findView(renderer) as NodeView - if ( - FunctionExt.call(validateEmbeding, renderer, { - child: this.cell, - parent: view.cell, - childView: this, - parentView: view, - }) - ) { - // flip to the new candidate - newCandidateView = view - break - } - } - } - - this.clearEmbedding(data) - if (newCandidateView) { - newCandidateView.highlight(null, { type: 'embedding' }) - } - data.candidateEmbedView = newCandidateView - - const localPoint = renderer.snapToGrid(e.clientX, e.clientY) - this.notify('node:embedding', { - e, - cell, - node: cell, - view: renderer.findViewByCell(cell), - x: localPoint.x, - y: localPoint.y, - currentParent: cell.getParent(), - candidateParent: newCandidateView ? newCandidateView.cell : null, - }) - } - - clearEmbedding(data: EventData.MovingTargetNode) { - const candidateView = data.candidateEmbedView - if (candidateView) { - candidateView.unhighlight(null, { type: 'embedding' }) - data.candidateEmbedView = null - } - } - - finalizeEmbedding(e: Dom.MouseUpEvent, data: EventData.MovingTargetNode) { - const cell = data.cell || this.cell - const renderer = data.renderer || this.renderer - const view = renderer.findViewByCell(cell) - const parent = cell.getParent() - const candidateView = data.candidateEmbedView - if (candidateView) { - // Candidate view is chosen to become the parent of the node. - candidateView.unhighlight(null, { type: 'embedding' }) - data.candidateEmbedView = null - if (parent == null || parent.id !== candidateView.cell.id) { - candidateView.cell.insertChild(cell, undefined, { ui: true }) - } - } else if (parent) { - parent.unembed(cell, { ui: true }) - } - - renderer.model - .getConnectedEdges(cell, { deep: true }) - .forEach((edge: any) => { - // todo - edge.updateParent({ ui: true }) - }) - - const localPoint = renderer.snapToGrid(e.clientX, e.clientY) - - if (view) { - view.notify('node:embedded', { - e, - cell, - x: localPoint.x, - y: localPoint.y, - node: cell, - view: renderer.findViewByCell(cell), - previousParent: parent, - currentParent: cell.getParent(), - }) - } - } - - getDelegatedView() { - let cell = this.cell - let view: NodeView = this // eslint-disable-line - - while (view) { - if (cell.isEdge()) { - break - } - if (!cell.hasParent() || view.can('stopDelegateOnDragging')) { - return view - } - cell = cell.getParent() as Entity - view = this.renderer.findViewByCell(cell) as NodeView - } - - return null - } - - protected validateMagnet( - cellView: CellView, - magnet: Element, - e: Dom.MouseDownEvent | Dom.MouseEnterEvent, - ) { - if (magnet.getAttribute('magnet') !== 'passive') { - const validate = this.renderer.options.connecting.validateMagnet - if (validate) { - return FunctionExt.call(validate, this.renderer, { - e, - magnet, - view: cellView, - cell: cellView.cell, - }) - } - return true - } - return false - } - - protected startMagnetDragging(e: Dom.MouseDownEvent, x: number, y: number) { - if (!this.can('magnetConnectable')) { - return - } - - e.stopPropagation() - - const magnet = e.currentTarget - const renderer = this.renderer - - this.setEventData>(e, { - targetMagnet: magnet, - }) - - if (this.validateMagnet(this, magnet, e)) { - if (renderer.options.magnetThreshold <= 0) { - this.startConnectting(e, magnet, x, y) - } - - this.setEventData>(e, { - action: 'magnet', - }) - this.stopPropagation(e) - } else { - this.onMouseDown(e, x, y) - } - - renderer.graphView.delegateDragEvents(e, this) - } - - protected startConnectting( - e: Dom.MouseDownEvent, - magnet: Element, - x: number, - y: number, - ) { - this.renderer.model.startBatch('add-edge') - const edgeView = this.createEdgeFromMagnet(magnet, x, y) - edgeView.notifyMouseDown(e, x, y) // backwards compatibility events - edgeView.setEventData( - e, - edgeView.prepareArrowheadDragging('target', { - x, - y, - isNewEdge: true, - fallbackAction: 'remove', - }), - ) - this.setEventData>(e, { edgeView }) - } - - protected getDefaultEdge(sourceView: CellView, sourceMagnet: Element) { - let edge: Edge | undefined | null | void - - const create = this.renderer.options.connecting.createEdge - if (create) { - edge = FunctionExt.call(create, this.renderer, { - sourceMagnet, - sourceView, - sourceCell: sourceView.cell, - }) - } - - return edge as Edge - } - - protected createEdgeFromMagnet(magnet: Element, x: number, y: number) { - const renderer = this.renderer - const model = renderer.model - const edge = this.getDefaultEdge(this, magnet) - - edge.setSource({ - ...edge.getSource(), - ...this.getEdgeTerminal(magnet, x, y, edge, 'source'), - }) - edge.setTarget({ ...edge.getTarget(), x, y }) - edge.addTo(model, { async: false, ui: true }) - - return edge.findView(renderer) as EdgeView - } - - protected dragMagnet(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - const edgeView = data.edgeView - if (edgeView) { - edgeView.onMouseMove(e, x, y) - this.autoScrollGraph(e.clientX, e.clientY) - } else { - const renderer = this.renderer - const magnetThreshold = renderer.options.magnetThreshold as any - const currentTarget = this.getEventTarget(e) - const targetMagnet = data.targetMagnet - - // magnetThreshold when the pointer leaves the magnet - if (magnetThreshold === 'onleave') { - if ( - targetMagnet === currentTarget || - targetMagnet.contains(currentTarget) - ) { - return - } - // eslint-disable-next-line no-lonely-if - } else { - // magnetThreshold defined as a number of movements - if (renderer.graphView.getMouseMovedCount(e) <= magnetThreshold) { - return - } - } - this.startConnectting(e as any, targetMagnet, x, y) - } - } - - protected stopMagnetDragging(e: Dom.MouseUpEvent, x: number, y: number) { - const data = this.eventData(e) - const edgeView = data.edgeView - if (edgeView) { - edgeView.onMouseUp(e, x, y) - this.renderer.model.stopBatch('add-edge') - } - } - - protected notifyUnhandledMouseDown( - e: Dom.MouseDownEvent, - x: number, - y: number, - ) { - this.notify('node:unhandled:mousedown', { - e, - x, - y, - view: this, - cell: this.cell, - node: this.cell, - }) - } - - protected notifyNodeMove( - name: Key, - e: Dom.MouseMoveEvent | Dom.MouseUpEvent, - x: number, - y: number, - cell: Cell, - ) { - const cells = [cell] - - // todo - // const selection = this.graph.selection.widget - // if (selection && selection.options.movable) { - // const selectedCells = this.graph.getSelectedCells() - // if (selectedCells.includes(cell)) { - // cells = selectedCells.filter((c: Cell) => c.isNode()) - // } - // } - - cells.forEach((c: Cell) => { - this.notify(name, { - e, - x, - y, - cell: c, - node: c, - view: c.findView(this.renderer), - }) - }) - } - - // todo - protected getRestrictArea(view?: NodeView): Rectangle.RectangleLike | null { - const restrict = this.renderer.options.translating.restrict - const area = - typeof restrict === 'function' - ? FunctionExt.call(restrict, this.renderer, view!) - : restrict - - if (typeof area === 'number') { - return this.renderer.options.getGraphArea().inflate(area) - } - - if (area === true) { - return this.renderer.options.getGraphArea() - } - - return area || null - } - - protected startNodeDragging(e: Dom.MouseDownEvent, x: number, y: number) { - const targetView = this.getDelegatedView() - if (targetView == null || !targetView.can('nodeMovable')) { - return this.notifyUnhandledMouseDown(e, x, y) - } - - this.setEventData(e, { - targetView, - action: 'move', - }) - - const position = Point.create(targetView.cell.getPosition()) - targetView.setEventData(e, { - moving: false, - offset: position.diff(x, y), - restrict: this.getRestrictArea(targetView), - }) - } - - protected dragNode(e: Dom.MouseMoveEvent, x: number, y: number) { - const node = this.cell - const renderer = this.renderer - const gridSize = renderer.options.getGridSize() - const data = this.getEventData(e) - const offset = data.offset - const restrict = data.restrict - - if (!data.moving) { - data.moving = true - this.addClass('node-moving') - this.notifyNodeMove('node:move', e, x, y, this.cell) - } - - this.autoScrollGraph(e.clientX, e.clientY) - - const posX = GeomUtil.snapToGrid(x + offset.x, gridSize) - const posY = GeomUtil.snapToGrid(y + offset.y, gridSize) - node.setPosition(posX, posY, { - restrict, - deep: true, - ui: true, - }) - - if (renderer.options.embedding.enabled) { - if (!data.embedding) { - this.prepareEmbedding(e) - data.embedding = true - } - this.processEmbedding(e, data) - } - } - - protected stopNodeDragging(e: Dom.MouseUpEvent, x: number, y: number) { - const data = this.getEventData(e) - if (data.embedding) { - this.finalizeEmbedding(e, data) - } - - if (data.moving) { - this.removeClass('node-moving') - this.notifyNodeMove('node:moved', e, x, y, this.cell) - } - - data.moving = false - data.embedding = false - } - - // eslint-disable-next-line - protected autoScrollGraph(x: number, y: number) { - // todo - // const scroller = this.graph.scroller.widget - // if (scroller) { - // scroller.autoScroll(x, y) - // } - } - - // #endregion -} - -export namespace NodeView { - export interface Options extends CellView.Options {} - - export interface PortCache { - portElement: Element - portSelectors?: Markup.Selectors | null - portLabelElement?: Element - portLabelSelectors?: Markup.Selectors | null - portContentElement?: Element - portContentSelectors?: Markup.Selectors | null - } -} - -export namespace NodeView { - interface MagnetEventArgs { - magnet: Element - } - - export interface MouseEventArgs { - e: E - node: Node - cell: Node - view: NodeView - } - - export interface PositionEventArgs - extends MouseEventArgs, - CellView.PositionEventArgs {} - - export interface TranslateEventArgs extends PositionEventArgs {} - - export interface ResizeEventArgs extends PositionEventArgs {} - - export interface RotateEventArgs extends PositionEventArgs {} - - export interface EventArgs { - 'node:click': PositionEventArgs - 'node:dblclick': PositionEventArgs - 'node:contextmenu': PositionEventArgs - 'node:mousedown': PositionEventArgs - 'node:mousemove': PositionEventArgs - 'node:mouseup': PositionEventArgs - 'node:mouseover': MouseEventArgs - 'node:mouseout': MouseEventArgs - 'node:mouseenter': MouseEventArgs - 'node:mouseleave': MouseEventArgs - 'node:mousewheel': PositionEventArgs & - CellView.MouseDeltaEventArgs - - 'node:customevent': PositionEventArgs & { - name: string - } - - 'node:unhandled:mousedown': PositionEventArgs - - 'node:highlight': { - magnet: Element - view: NodeView - node: Node - cell: Node - options: CellView.HighlightOptions - } - 'node:unhighlight': EventArgs['node:highlight'] - - 'node:magnet:click': PositionEventArgs & MagnetEventArgs - 'node:magnet:dblclick': PositionEventArgs & - MagnetEventArgs - 'node:magnet:contextmenu': PositionEventArgs & - MagnetEventArgs - - 'node:move': TranslateEventArgs - 'node:moving': TranslateEventArgs - 'node:moved': TranslateEventArgs - - 'node:resize': ResizeEventArgs - 'node:resizing': ResizeEventArgs - 'node:resized': ResizeEventArgs - - 'node:rotate': RotateEventArgs - 'node:rotating': RotateEventArgs - 'node:rotated': RotateEventArgs - - 'node:embed': PositionEventArgs & { - currentParent: Node | null - } - 'node:embedding': PositionEventArgs & { - currentParent: Node | null - candidateParent: Node | null - } - 'node:embedded': PositionEventArgs & { - currentParent: Node | null - previousParent: Node | null - } - } -} - -export namespace NodeView { - export const toStringTag = `X6.${NodeView.name}` - - export function isNodeView(instance: any): instance is NodeView { - if (instance == null) { - return false - } - - if (instance instanceof NodeView) { - return true - } - - const tag = instance[Symbol.toStringTag] - const view = instance as NodeView - - if ( - (tag == null || tag === toStringTag) && - typeof view.isNodeView === 'function' && - typeof view.isEdgeView === 'function' && - typeof view.confirmUpdate === 'function' && - typeof view.update === 'function' && - typeof view.findPortElem === 'function' && - typeof view.resize === 'function' && - typeof view.rotate === 'function' && - typeof view.translate === 'function' - ) { - return true - } - - return false - } -} - -namespace EventData { - export type Mousemove = Moving | Magnet - - export interface Magnet { - action: 'magnet' - targetMagnet: Element - edgeView?: EdgeView - } - - export interface Moving { - action: 'move' - targetView: NodeView - } - - export interface MovingTargetNode { - moving: boolean - offset: Point.PointLike - restrict?: Rectangle.RectangleLike | null - embedding?: boolean - candidateEmbedView?: NodeView | null - cell?: Node - renderer?: Renderer - } -} - -NodeView.config({ - isSvgElement: true, - priority: 0, - bootstrap: ['render'], - actions: { - view: ['render'], - markup: ['render'], - attrs: ['update'], - size: ['resize', 'ports', 'tools'], - angle: ['rotate', 'tools'], - position: ['translate', 'tools'], - ports: ['ports'], - tools: ['tools'], - }, -}) - -NodeView.registry.register('node', NodeView, true) diff --git a/packages/x6-core/src/view/tool.ts b/packages/x6-core/src/view/tool.ts deleted file mode 100644 index aeafe38d548..00000000000 --- a/packages/x6-core/src/view/tool.ts +++ /dev/null @@ -1,538 +0,0 @@ -import { Dom, ObjectExt, StringExt, KeyValue } from '@antv/x6-common' -import { NodeTool, EdgeTool } from '../registry/tool' -import { View } from './view' -import { CellView } from './cell' -import { Markup } from './markup' - -export class ToolsView extends View { - public tools: ToolsView.ToolItem[] | null - public options: ToolsView.Options - public cellView: CellView - public svgContainer: SVGGElement - public htmlContainer: HTMLDivElement - - public get name() { - return this.options.name - } - - public get renderer() { - return this.cellView.renderer - } - - public get cell() { - return this.cellView.cell - } - - protected get [Symbol.toStringTag]() { - return ToolsView.toStringTag - } - - constructor(options: ToolsView.Options = {}) { - super() - this.svgContainer = this.createContainer(true, options) as SVGGElement - this.htmlContainer = this.createContainer(false, options) as HTMLDivElement - this.config(options) - } - - protected createContainer(svg: boolean, options: ToolsView.Options) { - const container = svg - ? View.createElement('g', true) - : View.createElement('div', false) - Dom.addClass(container, this.prefixClassName('cell-tools')) - if (options.className) { - Dom.addClass(container, options.className) - } - return container - } - - config(options: ToolsView.ConfigOptions) { - this.options = { - ...this.options, - ...options, - } - - if (!CellView.isCellView(options.view) || options.view === this.cellView) { - return this - } - - this.cellView = options.view - - if (this.cell.isEdge()) { - Dom.addClass(this.svgContainer, this.prefixClassName('edge-tools')) - Dom.addClass(this.htmlContainer, this.prefixClassName('edge-tools')) - } else if (this.cell.isNode()) { - Dom.addClass(this.svgContainer, this.prefixClassName('node-tools')) - Dom.addClass(this.htmlContainer, this.prefixClassName('node-tools')) - } - - this.svgContainer.setAttribute('data-cell-id', this.cell.id) - this.htmlContainer.setAttribute('data-cell-id', this.cell.id) - - if (this.name) { - this.svgContainer.setAttribute('data-tools-name', this.name) - this.htmlContainer.setAttribute('data-tools-name', this.name) - } - - const tools = this.options.items - if (!Array.isArray(tools)) { - return this - } - - this.tools = [] - - const normalizedTools: typeof tools = [] - - tools.forEach((meta) => { - if (ToolsView.ToolItem.isToolItem(meta)) { - if (meta.name === 'vertices') { - normalizedTools.unshift(meta) - } else { - normalizedTools.push(meta) - } - } else { - const name = typeof meta === 'object' ? meta.name : meta - if (name === 'vertices') { - normalizedTools.unshift(meta) - } else { - normalizedTools.push(meta) - } - } - }) - - for (let i = 0; i < normalizedTools.length; i += 1) { - const meta = normalizedTools[i] - let tool: ToolsView.ToolItem | undefined - - if (ToolsView.ToolItem.isToolItem(meta)) { - tool = meta - } else { - const name = typeof meta === 'object' ? meta.name : meta - const args = typeof meta === 'object' ? meta.args || {} : {} - if (name) { - if (this.cell.isNode()) { - const ctor = NodeTool.registry.get(name) - if (ctor) { - tool = new ctor(args) // eslint-disable-line - } else { - return NodeTool.registry.onNotFound(name) - } - } else if (this.cell.isEdge()) { - const ctor = EdgeTool.registry.get(name) - if (ctor) { - tool = new ctor(args) // eslint-disable-line - } else { - return EdgeTool.registry.onNotFound(name) - } - } - } - } - - if (tool) { - tool.config(this.cellView, this) - tool.render() - const container = - tool.options.isSVGElement !== false - ? this.svgContainer - : this.htmlContainer - container.appendChild(tool.container) - this.tools.push(tool) - } - } - - return this - } - - update(options: ToolsView.UpdateOptions = {}) { - const tools = this.tools - if (tools) { - tools.forEach((tool) => { - if (options.toolId !== tool.cid && tool.isVisible()) { - tool.update() - } - }) - } - return this - } - - focus(focusedTool: ToolsView.ToolItem | null) { - const tools = this.tools - if (tools) { - tools.forEach((tool) => { - if (focusedTool === tool) { - tool.show() - } else { - tool.hide() - } - }) - } - - return this - } - - blur(blurredTool: ToolsView.ToolItem | null) { - const tools = this.tools - if (tools) { - tools.forEach((tool) => { - if (tool !== blurredTool && !tool.isVisible()) { - tool.show() - tool.update() - } - }) - } - - return this - } - - hide() { - return this.focus(null) - } - - show() { - return this.blur(null) - } - - remove() { - const tools = this.tools - if (tools) { - tools.forEach((tool) => tool.remove()) - this.tools = null - } - - Dom.remove(this.svgContainer) - Dom.remove(this.htmlContainer) - return super.remove() - } - - mount() { - const tools = this.tools - const cellView = this.cellView - if (cellView && tools) { - const hasSVG = tools.some((tool) => tool.options.isSVGElement !== false) - const hasHTML = tools.some((tool) => tool.options.isSVGElement === false) - if (hasSVG) { - const parent = this.options.local - ? cellView.container - : cellView.renderer.graphView.decorator - parent.appendChild(this.svgContainer) - } - - if (hasHTML) { - this.renderer.container.appendChild(this.htmlContainer) - } - } - return this - } -} - -export namespace ToolsView { - export interface Options extends ConfigOptions { - className?: string - } - - export interface ConfigOptions { - view?: CellView - name?: string - local?: boolean - items?: - | ( - | ToolItem - | string - | NodeTool.NativeNames - | NodeTool.NativeItem - | NodeTool.ManaualItem - | EdgeTool.NativeNames - | EdgeTool.NativeItem - | EdgeTool.ManaualItem - )[] - | null - } - - export interface UpdateOptions { - toolId?: string - } -} - -export namespace ToolsView { - export const toStringTag = `X6.${ToolsView.name}` - - export function isToolsView(instance: any): instance is ToolsView { - if (instance == null) { - return false - } - - if (instance instanceof ToolsView) { - return true - } - - const tag = instance[Symbol.toStringTag] - const view = instance as ToolsView - - if ( - (tag == null || tag === toStringTag) && - view.renderer != null && - view.cell != null && - typeof view.config === 'function' && - typeof view.update === 'function' && - typeof view.focus === 'function' && - typeof view.blur === 'function' && - typeof view.show === 'function' && - typeof view.hide === 'function' - ) { - return true - } - - return false - } -} - -export namespace ToolsView { - export class ToolItem< - TargetView extends CellView = CellView, - Options extends ToolItem.Options = ToolItem.Options, - > extends View { - // #region static - - protected static defaults: ToolItem.Options = { - isSVGElement: true, - tagName: 'g', - } - - public static getDefaults() { - return this.defaults as T - } - - public static config( - options: Partial, - ) { - this.defaults = this.getOptions(options) - } - - public static getOptions( - options: Partial, - ): T { - return ObjectExt.merge( - ObjectExt.cloneDeep(this.getDefaults()), - options, - ) as T - } - - // #endregion - - public readonly options: Options - - public container: HTMLElement | SVGElement - - public parent: ToolsView - - protected cellView: TargetView - - protected visible = true - - protected childNodes: KeyValue - - public get renderer() { - return this.cellView.renderer - } - - public get cell() { - return this.cellView.cell - } - - public get name() { - return this.options.name - } - - protected get [Symbol.toStringTag]() { - return ToolItem.toStringTag - } - - constructor(options: Partial = {}) { - super() - - this.options = this.getOptions(options) - this.container = View.createElement( - this.options.tagName || 'g', - this.options.isSVGElement !== false, - ) - - Dom.addClass(this.container, this.prefixClassName('cell-tool')) - - if (typeof this.options.className === 'string') { - Dom.addClass(this.container, this.options.className) - } - - this.init() - } - - protected init() {} - - protected getOptions(options: Partial): Options { - const ctor = this.constructor as any as ToolItem - return ctor.getOptions(options) as Options - } - - delegateEvents() { - if (this.options.events) { - super.delegateEvents(this.options.events) - } - return this - } - - config(view: CellView, toolsView: ToolsView) { - this.cellView = view as TargetView - this.parent = toolsView - this.stamp(this.container) - - if (this.cell.isEdge()) { - Dom.addClass(this.container, this.prefixClassName('edge-tool')) - } else if (this.cell.isNode()) { - Dom.addClass(this.container, this.prefixClassName('node-tool')) - } - - if (this.name) { - this.container.setAttribute('data-tool-name', this.name) - } - - this.delegateEvents() - - return this - } - - render() { - this.empty() - - const markup = this.options.markup - if (markup) { - const meta = Markup.parseJSONMarkup(markup) - this.container.appendChild(meta.fragment) - this.childNodes = meta.selectors as KeyValue - } - - this.onRender() - return this - } - - protected onRender() {} - - update() { - return this - } - - protected stamp(elem: Element = this.container) { - if (elem) { - elem.setAttribute('data-cell-id', this.cellView.cell.id) - } - } - - show() { - this.container.style.display = '' - this.visible = true - return this - } - - hide() { - this.container.style.display = 'none' - this.visible = false - return this - } - - isVisible() { - return this.visible - } - - focus() { - const opacity = this.options.focusOpacity - if (opacity != null && Number.isFinite(opacity)) { - this.container.style.opacity = `${opacity}` - } - this.parent.focus(this) - return this - } - - blur() { - this.container.style.opacity = '' - this.parent.blur(this) - return this - } - - protected guard(evt: Dom.EventObject) { - if (this.renderer == null || this.cellView == null) { - return true - } - - return this.renderer.graphView.guard(evt, this.cellView) - } - } - - export namespace ToolItem { - export interface Options { - name?: string - tagName?: string - isSVGElement?: boolean - className?: string - markup?: Exclude - events?: View.Events | null - documentEvents?: View.Events | null - focusOpacity?: number - } - } - - export namespace ToolItem { - export type Definition = - | typeof ToolItem - | (new (options: ToolItem.Options) => ToolItem) - - let counter = 0 - function getClassName(name?: string) { - if (name) { - return StringExt.pascalCase(name) - } - counter += 1 - return `CustomTool${counter}` - } - - export function define(options: T) { - const tool = ObjectExt.createClass( - getClassName(options.name), - this as Definition, - ) as typeof ToolItem - - tool.config(options) - return tool - } - } - - export namespace ToolItem { - export const toStringTag = `X6.${ToolItem.name}` - - export function isToolItem(instance: any): instance is ToolItem { - if (instance == null) { - return false - } - - if (instance instanceof ToolItem) { - return true - } - - const tag = instance[Symbol.toStringTag] - const view = instance as ToolItem - - if ( - (tag == null || tag === toStringTag) && - view.renderer != null && - view.cell != null && - typeof view.config === 'function' && - typeof view.update === 'function' && - typeof view.focus === 'function' && - typeof view.blur === 'function' && - typeof view.show === 'function' && - typeof view.hide === 'function' && - typeof view.isVisible === 'function' - ) { - return true - } - - return false - } - } -} diff --git a/packages/x6-core/src/view/view.ts b/packages/x6-core/src/view/view.ts deleted file mode 100644 index 2ea0577531f..00000000000 --- a/packages/x6-core/src/view/view.ts +++ /dev/null @@ -1,452 +0,0 @@ -import { Dom, KeyValue } from '@antv/x6-common' -import { Basecoat, Config } from '../common' -import { Markup } from './markup' -import { Attr } from '../registry' - -export abstract class View extends Basecoat { - public readonly cid: string - public container: Element - protected selectors: Markup.Selectors - - public get priority() { - return 2 - } - - constructor() { - super() - this.cid = Private.uniqueId() - View.views[this.cid] = this - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - confirmUpdate(flag: number, options: any): number { - return 0 - } - - empty(elem: Element = this.container) { - Dom.empty(elem) - return this - } - - unmount(elem: Element = this.container) { - Dom.remove(elem) - return this - } - - remove(elem: Element = this.container) { - if (elem === this.container) { - this.removeEventListeners(document) - this.onRemove() - delete View.views[this.cid] - } - this.unmount(elem) - return this - } - - protected onRemove() {} - - setClass(className: string | string[], elem: Element = this.container) { - elem.classList.value = Array.isArray(className) - ? className.join(' ') - : className - } - - addClass(className: string | string[], elem: Element = this.container) { - Dom.addClass( - elem, - Array.isArray(className) ? className.join(' ') : className, - ) - return this - } - - removeClass(className: string | string[], elem: Element = this.container) { - Dom.removeClass( - elem, - Array.isArray(className) ? className.join(' ') : className, - ) - return this - } - - setStyle( - style: Record, - elem: Element = this.container, - ) { - Dom.css(elem, style) - return this - } - - setAttrs(attrs?: Attr.SimpleAttrs | null, elem: Element = this.container) { - if (attrs != null && elem != null) { - Dom.attr(elem, attrs) - } - return this - } - - /** - * Returns the value of the specified attribute of `node`. - * - * If the node does not set a value for attribute, start recursing up - * the DOM tree from node to lookup for attribute at the ancestors of - * node. If the recursion reaches CellView's root node and attribute - * is not found even there, return `null`. - */ - findAttr(attrName: string, elem: Element = this.container) { - let current = elem - while (current && current.nodeType === 1) { - const value = current.getAttribute(attrName) - if (value != null) { - return value - } - - if (current === this.container) { - return null - } - - current = current.parentNode as Element - } - - return null - } - - find( - selector?: string, - rootElem: Element = this.container, - selectors: Markup.Selectors = this.selectors, - ) { - return View.find(selector, rootElem, selectors).elems - } - - findOne( - selector?: string, - rootElem: Element = this.container, - selectors: Markup.Selectors = this.selectors, - ) { - const nodes = this.find(selector, rootElem, selectors) - return nodes.length > 0 ? nodes[0] : null - } - - findByAttr(attrName: string, elem: Element = this.container) { - let node = elem - while (node && node.getAttribute) { - const val = node.getAttribute(attrName) - if ((val != null || node === this.container) && val !== 'false') { - return node - } - node = node.parentNode as Element - } - - // If the overall cell has set `magnet === false`, then returns - // `null` to announce there is no magnet found for this cell. - // This is especially useful to set on cells that have 'ports'. - // In this case, only the ports have set `magnet === true` and the - // overall element has `magnet === false`. - return null - } - - getSelector(elem: Element, prevSelector?: string): string | undefined { - let selector - - if (elem === this.container) { - if (typeof prevSelector === 'string') { - selector = `> ${prevSelector}` - } - return selector - } - - if (elem) { - const nth = Dom.index(elem) + 1 - selector = `${elem.tagName.toLowerCase()}:nth-child(${nth})` - if (prevSelector) { - selector += ` > ${prevSelector}` - } - - selector = this.getSelector(elem.parentNode as Element, selector) - } - - return selector - } - - prefixClassName(className: string) { - return Config.prefix(className) - } - - delegateEvents(events: View.Events, append?: boolean) { - if (events == null) { - return this - } - - if (!append) { - this.undelegateEvents() - } - - const splitter = /^(\S+)\s*(.*)$/ - Object.keys(events).forEach((key) => { - const match = key.match(splitter) - if (match == null) { - return - } - - const method = this.getEventHandler(events[key]) - if (typeof method === 'function') { - this.delegateEvent(match[1], match[2], method) - } - }) - - return this - } - - undelegateEvents() { - Dom.Event.off(this.container, this.getEventNamespace()) - return this - } - - delegateDocumentEvents(events: View.Events, data?: KeyValue) { - this.addEventListeners(document, events, data) - return this - } - - undelegateDocumentEvents() { - this.removeEventListeners(document) - return this - } - - protected delegateEvent( - eventName: string, - selector: string | Record, - listener: any, - ) { - Dom.Event.on( - this.container, - eventName + this.getEventNamespace(), - selector, - listener, - ) - return this - } - - protected undelegateEvent( - eventName: string, - selector: string, - listener: any, - ): this - protected undelegateEvent(eventName: string): this - protected undelegateEvent(eventName: string, listener: any): this - protected undelegateEvent( - eventName: string, - selector?: string | any, - listener?: any, - ) { - const name = eventName + this.getEventNamespace() - if (selector == null) { - Dom.Event.off(this.container, name) - } else if (typeof selector === 'string') { - Dom.Event.off(this.container, name, selector, listener) - } else { - Dom.Event.off(this.container, name, selector) - } - return this - } - - protected addEventListeners( - elem: Element | Document, - events: View.Events, - data?: KeyValue, - ) { - if (events == null) { - return this - } - - const ns = this.getEventNamespace() - Object.keys(events).forEach((eventName) => { - const method = this.getEventHandler(events[eventName]) - if (typeof method === 'function') { - Dom.Event.on( - elem as Element, - eventName + ns, - data, - method as any, - ) - } - }) - - return this - } - - protected removeEventListeners(elem: Element | Document) { - if (elem != null) { - Dom.Event.off(elem as Element, this.getEventNamespace()) - } - return this - } - - protected getEventNamespace() { - return `.${Config.prefixCls}-event-${this.cid}` - } - - // eslint-disable-next-line - protected getEventHandler(handler: string | Function) { - // eslint-disable-next-line - let method: Function | undefined - if (typeof handler === 'string') { - const fn = (this as any)[handler] - if (typeof fn === 'function') { - method = (...args: any) => fn.call(this, ...args) - } - } else { - method = (...args: any) => handler.call(this, ...args) - } - - return method - } - - getEventTarget(e: Dom.EventObject, options: { fromPoint?: boolean } = {}) { - // Touchmove/Touchend event's target is not reflecting the element - // under the coordinates as mousemove does. - // It holds the element when a touchstart triggered. - const { target, type, clientX = 0, clientY = 0 } = e - if (options.fromPoint || type === 'touchmove' || type === 'touchend') { - return document.elementFromPoint(clientX, clientY) - } - - return target - } - - stopPropagation(e: Dom.EventObject) { - this.setEventData(e, { propagationStopped: true }) - return this - } - - isPropagationStopped(e: Dom.EventObject) { - return this.getEventData(e).propagationStopped === true - } - - getEventData(e: Dom.EventObject): T { - return this.eventData(e) - } - - setEventData(e: Dom.EventObject, data: T): T { - return this.eventData(e, data) - } - - protected eventData(e: Dom.EventObject, data?: T): T { - if (e == null) { - throw new TypeError('Event object required') - } - - let currentData = e.data - const key = `__${this.cid}__` - - // get - if (data == null) { - if (currentData == null) { - return {} as T - } - return currentData[key] || {} - } - - // set - if (currentData == null) { - currentData = e.data = {} - } - - if (currentData[key] == null) { - currentData[key] = { ...data } - } else { - currentData[key] = { ...currentData[key], ...data } - } - - return currentData[key] - } - - normalizeEvent(evt: T) { - return View.normalizeEvent(evt) - } -} - -export namespace View { - export type Events = KeyValue // eslint-disable-line -} - -export namespace View { - export function createElement(tagName?: string, isSvgElement?: boolean) { - return isSvgElement - ? Dom.createSvgElement(tagName || 'g') - : (Dom.createElementNS(tagName || 'div') as HTMLElement) - } - - export function find( - selector: string | null | undefined, - rootElem: Element, - selectors: Markup.Selectors, - ): { isCSSSelector?: boolean; elems: Element[] } { - if (!selector || selector === '.') { - return { elems: [rootElem] } - } - - if (selectors) { - const nodes = selectors[selector] - if (nodes) { - return { elems: Array.isArray(nodes) ? nodes : [nodes] } - } - } - - if (Config.useCSSSelector) { - return { - isCSSSelector: true, - // $(rootElem).find(selector).toArray() as Element[] todo - elems: Array.prototype.slice.call(rootElem.querySelectorAll(selector)), - } - } - - return { elems: [] } - } - - export function normalizeEvent(evt: T) { - let normalizedEvent = evt - const originalEvent = evt.originalEvent as TouchEvent - const touchEvt: any = - originalEvent && - originalEvent.changedTouches && - originalEvent.changedTouches[0] - - if (touchEvt) { - // eslint-disable-next-line no-restricted-syntax - for (const key in evt) { - // copy all the properties from the input event that are not - // defined on the touch event (functions included). - if (touchEvt[key] === undefined) { - touchEvt[key] = (evt as any)[key] - } - } - normalizedEvent = touchEvt - } - - // IE: evt.target could be set to SVGElementInstance for SVGUseElement - const target = normalizedEvent.target - if (target) { - const useElement = target.correspondingUseElement - if (useElement) { - normalizedEvent.target = useElement - } - } - - return normalizedEvent - } -} - -export namespace View { - export const views: { [cid: string]: View } = {} - - export function getView(cid: string) { - return views[cid] || null - } -} - -namespace Private { - let counter = 0 - export function uniqueId() { - const id = `v${counter}` - counter += 1 - return id - } -} diff --git a/packages/x6-core/tsconfig.json b/packages/x6-core/tsconfig.json deleted file mode 100644 index 4082f16a5d9..00000000000 --- a/packages/x6-core/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../tsconfig.json" -} diff --git a/packages/x6-next/LICENSE b/packages/x6-next/LICENSE deleted file mode 100644 index 4dc9501d975..00000000000 --- a/packages/x6-next/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021-2022 Alipay.inc - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/x6-next/README.md b/packages/x6-next/README.md deleted file mode 100644 index 9f58ee05db9..00000000000 --- a/packages/x6-next/README.md +++ /dev/null @@ -1,123 +0,0 @@ -简体中文 | [English](/README.en-us.md) - -

flow

- -

X6 是 AntV 旗下的图编辑引擎

-

提供简单易用的节点定制能力和开箱即用的交互组件,方便我们快速搭建流程图、DAG 图、ER 图等图应用

- -

-build -coverage -Language grade: JavaScript -NPM Package -NPM Downloads -

- -

-MIT License -Language -PRs Welcome -website -

- -## 特性 - -- 🌱 极易定制:支持使用 SVG/HTML/React/Vue/Angular 定制节点样式和交互 -- 🚀 开箱即用:内置 10+ 图编辑配套扩展,如框选、对齐线、小地图等 -- 🧲 数据驱动:基于 MVC 架构,用户更加专注于数据逻辑和业务逻辑 -- 💯 事件驱动:完备的事件系统,可以监听图表内发生的任何事件 - -## 兼容环境 - -- 现代浏览器和 IE11(需要 polyfills) -- 支持服务端渲染。 - -| [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | -| --- | --- | --- | --- | -| IE11, Edge | last 2 versions | last 2 versions | last 2 versions | - -## 安装 - -```shell -# npm -$ npm install @antv/x6 --save - -# yarn -$ yarn add @antv/x6 -``` - -## 示例 - -```html -
-``` - -```ts -import { Graph } from '@antv/x6' - -const graph = new Graph({ - container: document.getElementById('container'), - grid: true -}) - -const source = graph.addNode({ - x: 300, - y: 40, - width: 80, - height: 40, - label: 'Hello', -}) - -const target = graph.addNode({ - x: 420, - y: 180, - width: 80, - height: 40, - label: 'World', -}) - -graph.addEdge({ - source, - target, -}) -``` - -## 链接 - -- [文档](https://x6.antv.vision/zh/docs/tutorial/about) -- [示例](https://x6.antv.vision/zh/examples/gallery) -- [博客](https://www.yuque.com/antv/x6/gcinvi) -- [更新日志](https://www.yuque.com/antv/x6/bbfu6r) -- [常见问题](https://www.yuque.com/antv/x6/be9pfx) -- [CodeSanbox 模板](https://codesandbox.io/s/qosj0?file=/src/app.tsx) - -## 本地开发 - -```shell -# 全局安装 yarn 和 lerna 工具 -$ npm install yarn -g -$ npm install lerna -g - -# 安装项目依赖和初始化构建 -$ yarn bootstrap - -# 进入到指定项目开发和调试 -cd packages/x6 -yarn build:watch - -# 启动 example 查看效果 -cd examples/x6-example-features -yarn start -``` - -## 参与共建 - -如果希望参与到 X6 的开发中,请遵从我们的[贡献指南](/CONTRIBUTING.zh-CN.md)。如果你贡献度足够活跃,你可以申请成为社区协作者。 - - - Contributors - - -## 开源协议 - -该项目的代码和文档基于 [MIT License](/LICENSE) 开源协议。 diff --git a/packages/x6-next/__tests__/util/index.test.ts b/packages/x6-next/__tests__/util/index.test.ts deleted file mode 100644 index 7206b7aefc3..00000000000 --- a/packages/x6-next/__tests__/util/index.test.ts +++ /dev/null @@ -1,208 +0,0 @@ -// import { Rectangle, Ellipse, Polyline, Path } from '../../geometry' -import { Util } from '../../src/util' -import { Vector } from '@antv/x6-common' - -describe('Util', () => { - const fixture = document.createElement('div') - const svgContainer = Vector.create('svg').node - fixture.appendChild(svgContainer) - document.body.appendChild(fixture) - - afterAll(() => { - fixture.parentNode?.removeChild(fixture) - }) - - it('should return correct bbox of rect', () => { - const container = Vector.create(svgContainer) - const group = Vector.create('g') - const rect = Vector.create('rect') - - container.append(group) - group.append(rect) - group.translate(50, 50).rotate(20) - - rect.translate(20, 20).rotate(20) - rect.setAttribute('x', 10) - rect.setAttribute('y', 10) - rect.setAttribute('width', 100) - rect.setAttribute('height', 100) - rect.setAttribute('strokeWidth', 4) - - const bbox1 = Util.getBBox(rect.node) - const bbox2 = Util.getBBoxV2(rect.node) - expect(bbox1.equals(bbox2)) - - rect.remove() - group.remove() - }) - - it('should return correct bbox of rect with dx dy', () => { - const container = Vector.create(svgContainer) - const group = Vector.create('g') - const rect = Vector.create('rect') - - container.append(group) - group.append(rect) - group.translate(50, 50).rotate(20) - - rect.translate(20, 20).rotate(20) - rect.setAttribute('dx', 10) - rect.setAttribute('dy', 10) - rect.setAttribute('width', 100) - rect.setAttribute('height', 100) - - const bbox1 = Util.getBBox(rect.node) - const bbox2 = Util.getBBoxV2(rect.node) - expect(bbox1.equals(bbox2)) - - rect.remove() - group.remove() - }) - - it('should return correct bbox of circle', () => { - const container = Vector.create(svgContainer) - const group = Vector.create('g') - const circle = Vector.create('circle') - - container.append(group) - group.append(circle) - group.translate(50, 50).rotate(20) - - circle.setAttribute('cx', 10) - circle.setAttribute('cy', 10) - circle.setAttribute('r', 20) - - const bbox1 = Util.getBBox(circle.node) - const bbox2 = Util.getBBoxV2(circle.node) - expect(bbox1.equals(bbox2)) - - circle.remove() - group.remove() - }) - - it('should return correct bbox of ellipse', () => { - const container = Vector.create(svgContainer) - const group = Vector.create('g') - const ellipse = Vector.create('ellipse') - - container.append(group) - group.append(ellipse) - group.translate(50, 50).rotate(20) - - ellipse.setAttribute('cx', 10) - ellipse.setAttribute('cy', 10) - ellipse.setAttribute('rx', 40) - ellipse.setAttribute('ry', 20) - - const bbox1 = Util.getBBox(ellipse.node) - const bbox2 = Util.getBBoxV2(ellipse.node) - expect(bbox1.equals(bbox2)) - - ellipse.remove() - group.remove() - }) - - it('should return correct bbox of polyline', () => { - const container = Vector.create(svgContainer) - const group = Vector.create('g') - const polyline = Vector.create('polyline') - - container.append(group) - group.append(polyline) - group.translate(50, 50).rotate(20) - - polyline.setAttribute('points', '0, 0 0, 80 80, 80 80, 0') - - const bbox1 = Util.getBBox(polyline.node) - const bbox2 = Util.getBBoxV2(polyline.node) - expect(bbox1.equals(bbox2)) - - polyline.remove() - group.remove() - }) - - it('should return correct bbox of polygon', () => { - const container = Vector.create(svgContainer) - const group = Vector.create('g') - const polygon = Vector.create('polygon') - - container.append(group) - group.append(polygon) - group.translate(50, 50).rotate(20) - - polygon.setAttribute('points', '0, 40 40, 0 80, 40 40, 80') - - const bbox1 = Util.getBBox(polygon.node) - const bbox2 = Util.getBBoxV2(polygon.node) - expect(bbox1.equals(bbox2)) - - polygon.remove() - group.remove() - }) - - it('should return correct bbox of path', () => { - const container = Vector.create(svgContainer) - const group = Vector.create('g') - const path = Vector.create('path') - - container.append(group) - group.append(path) - group.translate(50, 50).rotate(20) - - path.setAttribute( - 'd', - 'M 0 20 L 57.142857142857146 0 C 114.28571428571429 0 114.28571428571429 80 57.142857142857146 80 L 0 60 Z', - ) - - const bbox1 = Util.getBBox(path.node) - const bbox2 = Util.getBBoxV2(path.node) - expect(bbox1.equals(bbox2)) - - path.remove() - group.remove() - }) - - it('should return correct bbox of line', () => { - const container = Vector.create(svgContainer) - const group = Vector.create('g') - const line = Vector.create('line') - - container.append(group) - group.append(line) - group.translate(50, 50).rotate(20) - - line.setAttribute('x1', 100) - line.setAttribute('x2', 100) - line.setAttribute('y1', 200) - line.setAttribute('y2', 300) - - const bbox1 = Util.getBBox(line.node) - const bbox2 = Util.getBBoxV2(line.node) - expect(bbox1.equals(bbox2)) - - line.remove() - group.remove() - }) - - it('should return correct bbox of text', () => { - const container = Vector.create(svgContainer) - const group = Vector.create('g') - const text = Vector.create('text') - const tspan = Vector.create('tspan') - - container.append(group) - group.append(text) - text.translate(50, 50).rotate(20) - text.append(tspan) - - tspan.setAttribute('dy', '-3em') - - const bbox1 = Util.getBBox(tspan.node) - const bbox2 = Util.getBBoxV2(tspan.node) - expect(bbox1.equals(bbox2)) - - tspan.remove() - text.remove() - group.remove() - }) -}) diff --git a/packages/x6-next/index.ts b/packages/x6-next/index.ts deleted file mode 100644 index 6f39cd49b29..00000000000 --- a/packages/x6-next/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src' diff --git a/packages/x6-next/karma.conf.js b/packages/x6-next/karma.conf.js deleted file mode 100644 index e95908ebb15..00000000000 --- a/packages/x6-next/karma.conf.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = (config) => - require('../../configs/karma-config.js')(config, { - files: [{ pattern: './src/**/*.ts' }, { pattern: './__tests__/**/*.ts' }], - }) diff --git a/packages/x6-next/package.json b/packages/x6-next/package.json deleted file mode 100644 index 32ff080b7d6..00000000000 --- a/packages/x6-next/package.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "name": "@antv/x6-next", - "version": "2.0.6-beta.6", - "description": "JavaScript diagramming library that uses SVG and HTML for rendering.", - "main": "lib/index.js", - "module": "es/index.js", - "unpkg": "dist/x6-next.js", - "jsdelivr": "dist/x6-next.js", - "types": "lib/index.d.ts", - "files": [ - "dist", - "es", - "lib", - "src" - ], - "keywords": [ - "graph", - "diagram", - "flowchart", - "uml", - "x6-editor", - "editor", - "svg", - "x6", - "antv" - ], - "scripts": { - "clean:build": "rimraf dist es lib", - "clean:coverage": "rimraf ./test/coverage", - "clean": "run-p clean:build clean:coverage", - "lint:ts": "eslint 'src/**/*.{js,ts}?(x)' --fix", - "lint:style": "stylelint 'src/**/*.less' --syntax less --fix", - "lint": "run-s lint:ts lint:style", - "build:esm": "tsc --module esnext --target es2015 --outDir ./es", - "build:cjs": "tsc --module commonjs --target es2015 --outDir ./lib", - "build:umd": "rollup -c", - "build:less": "node ./scripts/style", - "build:readme": "node ./scripts/readme.js", - "build:version": "node ../../scripts/version.js", - "build:dev": "run-p build:less build:cjs build:esm", - "build:watch": "yarn build:esm --w", - "build:watch:esm": "yarn build:esm --w", - "build:watch:cjs": "yarn build:cjs --w", - "build": "run-p build:readme build:dev build:umd", - "prebuild": "run-s lint clean", - "test": "karma start", - "coveralls": "cat ./test/coverage/lcov.info | coveralls", - "pretest": "run-p clean:coverage", - "prepare": "run-s test build", - "precommit": "lint-staged" - }, - "lint-staged": { - "src/**/*.less": [ - "stylelint --syntax less --fix" - ], - "src/**/*.ts": [ - "eslint --fix" - ] - }, - "inherits": [ - "@antv/x6-package-json/cli.json", - "@antv/x6-package-json/less.json", - "@antv/x6-package-json/karma.json", - "@antv/x6-package-json/eslint.json", - "@antv/x6-package-json/rollup.json" - ], - "dependencies": { - "@antv/x6-common": "^2.0.6-beta.3", - "@antv/x6-geometry": "^2.0.6-beta.2" - }, - "devDependencies": { - "@rollup/plugin-commonjs": "^20.0.0", - "@rollup/plugin-node-resolve": "^13.0.4", - "@rollup/plugin-replace": "^3.0.0", - "@rollup/plugin-typescript": "^8.2.5", - "@types/jasmine": "^3.9.0", - "@types/node": "^16.9.1", - "@types/resize-observer-browser": "^0.1.5", - "@types/sinon": "^10.0.2", - "@typescript-eslint/eslint-plugin": "^4.31.0", - "@typescript-eslint/parser": "^4.31.0", - "coveralls": "^3.1.1", - "eslint": "^7.32.0", - "eslint-config-airbnb-base": "^14.2.1", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-jest": "^24.4.0", - "eslint-plugin-jsx-a11y": "^6.4.1", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-promise": "^5.1.0", - "eslint-plugin-react": "^7.25.1", - "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-unicorn": "^36.0.0", - "fs-extra": "^10.0.0", - "jasmine-core": "^3.9.0", - "karma": "^6.3.4", - "karma-chrome-launcher": "^3.1.0", - "karma-cli": "^2.0.0", - "karma-jasmine": "^4.0.1", - "karma-spec-reporter": "^0.0.32", - "karma-typescript": "5.3.0", - "karma-typescript-es6-transform": "5.3.0", - "less": "^4.1.1", - "lint-staged": "^11.1.2", - "npm-run-all": "^4.1.5", - "postcss": "^8.3.6", - "prettier": "^2.4.0", - "pretty-quick": "^3.1.1", - "rimraf": "^3.0.2", - "rollup": "^2.56.3", - "rollup-plugin-auto-external": "^2.0.0", - "rollup-plugin-filesize": "^9.1.1", - "rollup-plugin-postcss": "^4.0.1", - "rollup-plugin-progress": "^1.1.2", - "rollup-plugin-terser": "^7.0.2", - "sinon": "^11.1.2", - "stylelint": "^13.13.1", - "stylelint-config-prettier": "^8.0.2", - "stylelint-config-rational-order": "^0.1.2", - "stylelint-config-standard": "^22.0.0", - "stylelint-declaration-block-no-ignored-properties": "^2.4.0", - "stylelint-order": "^4.1.0", - "ts-node": "^10.2.1", - "tslib": "^2.3.1", - "typescript": "^4.4.3", - "utility-types": "^3.10.0" - }, - "author": { - "name": "bubkoo", - "email": "bubkoo.wy@gmail.com" - }, - "contributors": [], - "license": "MIT", - "homepage": "https://github.com/antvis/x6", - "bugs": { - "url": "https://github.com/antvis/x6/issues" - }, - "repository": { - "type": "git", - "url": "ssh://git@github.com/antvis/x6.git", - "directory": "packages/x6-next" - }, - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org" - }, - "gitHead": "576fa342fa65a6867ead29f6801a30dcb31bcdb5" -} diff --git a/packages/x6-next/rollup.config.js b/packages/x6-next/rollup.config.js deleted file mode 100644 index 9b75f4bf400..00000000000 --- a/packages/x6-next/rollup.config.js +++ /dev/null @@ -1,13 +0,0 @@ -import config from '../../configs/rollup-config' - -export default config({ - output: [ - { - name: 'X6Next', - format: 'umd', - file: 'dist/x6-next.js', - sourcemap: true, - }, - ], - context: 'window', -}) diff --git a/packages/x6-next/scripts/readme.js b/packages/x6-next/scripts/readme.js deleted file mode 100644 index cb90c7d294d..00000000000 --- a/packages/x6-next/scripts/readme.js +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs') - -fs.copyFileSync('../../README.md', './README.md') diff --git a/packages/x6-next/scripts/style.js b/packages/x6-next/scripts/style.js deleted file mode 100644 index 068f9bc5d57..00000000000 --- a/packages/x6-next/scripts/style.js +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs') -const os = require('os') -const path = require('path') -const fse = require('fs-extra') -const cp = require('child_process') - -const cwd = process.cwd() -const es = path.join(cwd, 'es') -const lib = path.join(cwd, 'lib') -const src = path.join(cwd, 'src') -const dist = path.join(cwd, 'dist') - -function compile(source, target) { - let cmd = './node_modules/.bin/lessc' - if (os.type() === 'Windows_NT') { - cmd = path.join(cwd, './node_modules/.bin/lessc.cmd') - } - cp.execFileSync(cmd, [source, target]) -} - -compile(path.join(src, 'index.less'), path.join(es, 'index.css')) -compile(path.join(src, 'index.less'), path.join(lib, 'index.css')) -compile(path.join(src, 'index.less'), path.join(dist, 'x6.css')) - -function toCSSPath(source) { - const dir = path.dirname(source) - const file = `${path.basename(source, '.less')}.css` - return path.join(dir, file) -} - -// Copy less files -function processLessInDir(dir) { - const stat = fs.statSync(dir) - if (stat) { - if (stat.isDirectory()) { - fs.readdir(dir, (err, files) => { - files.forEach((file) => { - processLessInDir(path.join(dir, file)) - }) - }) - } else { - const ext = path.extname(dir) - if (ext === '.less' || ext === '.css') { - fse.copySync(dir, path.join(es, path.relative(src, dir))) - fse.copySync(dir, path.join(lib, path.relative(src, dir))) - } - - if (ext === '.less') { - let source = path.join(es, path.relative(src, dir)) - let target = toCSSPath(source) - compile(dir, target) - - source = path.join(lib, path.relative(src, dir)) - target = toCSSPath(source) - compile(dir, target) - } - } - } -} - -function makeStyleModule() { - const source = path.join(dist, 'x6.css') - const target = path.join(src, 'style/raw.ts') - const content = fs.readFileSync(source, { encoding: 'utf8' }) - const prev = fs.existsSync(target) - ? fs.readFileSync(target, { encoding: 'utf8' }) - : null - const curr = `/* eslint-disable */ - -/** - * Auto generated file, do not modify it! - */ - -export const content = \`${content}\` -` - - if (prev !== curr) { - fs.writeFileSync(target, curr) - } -} - -processLessInDir(src) -makeStyleModule() diff --git a/packages/x6-next/src/config.ts b/packages/x6-next/src/config.ts deleted file mode 100644 index 35ba1b5e939..00000000000 --- a/packages/x6-next/src/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const Config = { - prefixCls: 'x6', - autoInsertCSS: true, - useCSSSelector: true, - - prefix(suffix: string) { - return `${Config.prefixCls}-${suffix}` - }, -} diff --git a/packages/x6-next/src/graph/background.ts b/packages/x6-next/src/graph/background.ts deleted file mode 100644 index 914380971d0..00000000000 --- a/packages/x6-next/src/graph/background.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { ObjectExt } from '@antv/x6-common' -import { Rectangle } from '@antv/x6-geometry' -import { Background } from '../registry' -import { Base } from './base' - -export class BackgroundManager extends Base { - protected optionsCache: BackgroundManager.Options | null - - protected get elem() { - return this.view.background - } - - protected init() { - this.startListening() - if (this.options.background) { - this.draw(this.options.background) - } - } - - protected startListening() { - this.graph.on('scale', this.update, this) - this.graph.on('translate', this.update, this) - } - - protected stopListening() { - this.graph.off('scale', this.update, this) - this.graph.off('translate', this.update, this) - } - - protected updateBackgroundImage(options: BackgroundManager.Options = {}) { - let backgroundSize: any = options.size || 'auto auto' - let backgroundPosition: any = options.position || 'center' - - const scale = this.graph.transform.getScale() - const ts = this.graph.translate() - - // backgroundPosition - if (typeof backgroundPosition === 'object') { - const x = ts.tx + scale.sx * (backgroundPosition.x || 0) - const y = ts.ty + scale.sy * (backgroundPosition.y || 0) - backgroundPosition = `${x}px ${y}px` - } - - // backgroundSize - if (typeof backgroundSize === 'object') { - backgroundSize = Rectangle.fromSize(backgroundSize).scale( - scale.sx, - scale.sy, - ) - backgroundSize = `${backgroundSize.width}px ${backgroundSize.height}px` - } - - this.elem.style.backgroundSize = backgroundSize - this.elem.style.backgroundPosition = backgroundPosition - } - - protected drawBackgroundImage( - img?: HTMLImageElement | null, - options: BackgroundManager.Options = {}, - ) { - if (!(img instanceof HTMLImageElement)) { - this.elem.style.backgroundImage = '' - return - } - - // draw multiple times to show the last image - const cache = this.optionsCache - if (cache && cache.image !== options.image) { - return - } - - let uri - const opacity = options.opacity - const backgroundSize: any = options.size - let backgroundRepeat = options.repeat || 'no-repeat' - - const pattern = Background.registry.get(backgroundRepeat) - if (typeof pattern === 'function') { - const quality = (options as Background.ManaualItem).quality || 1 - img.width *= quality - img.height *= quality - const canvas = pattern(img, options) - if (!(canvas instanceof HTMLCanvasElement)) { - throw new Error( - 'Background pattern must return an HTML Canvas instance', - ) - } - - uri = canvas.toDataURL('image/png') - - // `repeat` was changed in pattern function - if (options.repeat && backgroundRepeat !== options.repeat) { - backgroundRepeat = options.repeat - } else { - backgroundRepeat = 'repeat' - } - - if (typeof backgroundSize === 'object') { - // recalculate the tile size if an object passed in - backgroundSize.width *= canvas.width / img.width - backgroundSize.height *= canvas.height / img.height - } else if (backgroundSize === undefined) { - // calcule the tile size if no provided - options.size = { - width: canvas.width / quality, - height: canvas.height / quality, - } - } - } else { - uri = img.src - if (backgroundSize === undefined) { - options.size = { - width: img.width, - height: img.height, - } - } - } - - if ( - cache != null && - typeof options.size === 'object' && - options.image === cache.image && - options.repeat === cache.repeat && - (options as Background.ManaualItem).quality === - (cache as Background.ManaualItem).quality - ) { - cache.size = ObjectExt.clone(options.size) - } - - const style = this.elem.style - style.backgroundImage = `url(${uri})` - style.backgroundRepeat = backgroundRepeat - style.opacity = opacity == null || opacity >= 1 ? '' : `${opacity}` - - this.updateBackgroundImage(options) - } - - protected updateBackgroundColor(color?: string | null) { - this.elem.style.backgroundColor = color || '' - } - - protected updateBackgroundOptions(options?: BackgroundManager.Options) { - this.graph.options.background = options - } - - update() { - if (this.optionsCache) { - this.updateBackgroundImage(this.optionsCache) - } - } - - draw(options?: BackgroundManager.Options) { - const opts = options || {} - this.updateBackgroundOptions(options) - this.updateBackgroundColor(opts.color) - - if (opts.image) { - this.optionsCache = ObjectExt.clone(opts) - const img = document.createElement('img') - img.onload = () => this.drawBackgroundImage(img, options) - img.setAttribute('crossorigin', 'anonymous') - img.src = opts.image - } else { - this.drawBackgroundImage(null) - this.optionsCache = null - } - } - - clear() { - this.draw() - } - - @Base.dispose() - dispose() { - this.clear() - this.stopListening() - } -} - -export namespace BackgroundManager { - export type Options = - | Background.Options - | Background.NativeItem - | Background.ManaualItem -} diff --git a/packages/x6-next/src/graph/base.ts b/packages/x6-next/src/graph/base.ts deleted file mode 100644 index 80475105884..00000000000 --- a/packages/x6-next/src/graph/base.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Disposable } from '@antv/x6-common' -import { Graph } from './graph' - -export class Base extends Disposable { - public readonly graph: Graph - - public get options() { - return this.graph.options - } - - public get model() { - return this.graph.model - } - - public get view() { - return this.graph.view - } - - constructor(graph: Graph) { - super() - this.graph = graph - this.init() - } - - protected init() {} -} - -export namespace Base {} diff --git a/packages/x6-next/src/graph/coord.ts b/packages/x6-next/src/graph/coord.ts deleted file mode 100644 index 0c951ca37a0..00000000000 --- a/packages/x6-next/src/graph/coord.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { Base } from './base' -import { Util } from '../util' - -export class CoordManager extends Base { - getClientMatrix() { - return Dom.createSVGMatrix(this.view.stage.getScreenCTM()) - } - - /** - * Returns coordinates of the graph viewport, relative to the window. - */ - getClientOffset() { - // see: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect - const rect = this.view.svg.getBoundingClientRect() - return new Point(rect.left, rect.top) - } - - /** - * Returns coordinates of the graph viewport, relative to the document. - */ - getPageOffset() { - // see: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect - return this.getClientOffset().translate(window.scrollX, window.scrollY) - } - - snapToGrid(x: number | Point | Point.PointLike, y?: number) { - const p = - typeof x === 'number' - ? this.clientToLocalPoint(x, y as number) - : this.clientToLocalPoint(x.x, x.y) - return p.snapToGrid(this.graph.getGridSize()) - } - - localToGraphPoint(x: number | Point | Point.PointLike, y?: number) { - const localPoint = Point.create(x, y) - return Util.transformPoint(localPoint, this.graph.matrix()) - } - - localToClientPoint(x: number | Point | Point.PointLike, y?: number) { - const localPoint = Point.create(x, y) - return Util.transformPoint(localPoint, this.getClientMatrix()) - } - - localToPagePoint(x: number | Point | Point.PointLike, y?: number) { - const p = - typeof x === 'number' - ? this.localToGraphPoint(x, y!) - : this.localToGraphPoint(x) - return p.translate(this.getPageOffset()) - } - - localToGraphRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const localRect = Rectangle.create(x, y, width, height) - return Util.transformRectangle(localRect, this.graph.matrix()) - } - - localToClientRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const localRect = Rectangle.create(x, y, width, height) - return Util.transformRectangle(localRect, this.getClientMatrix()) - } - - localToPageRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const rect = - typeof x === 'number' - ? this.localToGraphRect(x, y!, width!, height!) - : this.localToGraphRect(x) - return rect.translate(this.getPageOffset()) - } - - graphToLocalPoint(x: number | Point | Point.PointLike, y?: number) { - const graphPoint = Point.create(x, y) - return Util.transformPoint(graphPoint, this.graph.matrix().inverse()) - } - - clientToLocalPoint(x: number | Point | Point.PointLike, y?: number) { - const clientPoint = Point.create(x, y) - return Util.transformPoint(clientPoint, this.getClientMatrix().inverse()) - } - - clientToGraphPoint(x: number | Point | Point.PointLike, y?: number) { - const clientPoint = Point.create(x, y) - return Util.transformPoint( - clientPoint, - this.graph.matrix().multiply(this.getClientMatrix().inverse()), - ) - } - - pageToLocalPoint(x: number | Point | Point.PointLike, y?: number) { - const pagePoint = Point.create(x, y) - const graphPoint = pagePoint.diff(this.getPageOffset()) - return this.graphToLocalPoint(graphPoint) - } - - graphToLocalRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const graphRect = Rectangle.create(x, y, width, height) - return Util.transformRectangle(graphRect, this.graph.matrix().inverse()) - } - - clientToLocalRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const clientRect = Rectangle.create(x, y, width, height) - return Util.transformRectangle(clientRect, this.getClientMatrix().inverse()) - } - - clientToGraphRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const clientRect = Rectangle.create(x, y, width, height) - return Util.transformRectangle( - clientRect, - this.graph.matrix().multiply(this.getClientMatrix().inverse()), - ) - } - - pageToLocalRect( - x: number | Rectangle | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - const graphRect = Rectangle.create(x, y, width, height) - const pageOffset = this.getPageOffset() - graphRect.x -= pageOffset.x - graphRect.y -= pageOffset.y - return this.graphToLocalRect(graphRect) - } -} - -export namespace CoordManager {} diff --git a/packages/x6-next/src/graph/css.ts b/packages/x6-next/src/graph/css.ts deleted file mode 100644 index 593ff7ddc58..00000000000 --- a/packages/x6-next/src/graph/css.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { CssLoader } from '@antv/x6-common' -import { Config } from '../config' -import { content } from '../style/raw' -import { Base } from './base' - -export class CSSManager extends Base { - protected init() { - if (Config.autoInsertCSS) { - CssLoader.ensure('core', content) - } - } - - @CSSManager.dispose() - dispose() { - CssLoader.clean('core') - } -} diff --git a/packages/x6-next/src/graph/defs.ts b/packages/x6-next/src/graph/defs.ts deleted file mode 100644 index 7e74502fd47..00000000000 --- a/packages/x6-next/src/graph/defs.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { StringExt, Dom, Vector } from '@antv/x6-common' -import { Attr, Filter, Marker } from '../registry' -import { Markup } from '../view' -import { Base } from './base' - -export class DefsManager extends Base { - protected get cid() { - return this.graph.view.cid - } - - protected get svg() { - return this.view.svg - } - - protected get defs() { - return this.view.defs - } - - protected isDefined(id: string) { - return this.svg.getElementById(id) != null - } - - filter(options: DefsManager.FilterOptions) { - let filterId = options.id - const name = options.name - if (!filterId) { - filterId = `filter-${name}-${this.cid}-${StringExt.hashcode( - JSON.stringify(options), - )}` - } - - if (!this.isDefined(filterId)) { - const fn = Filter.registry.get(name) - if (fn == null) { - return Filter.registry.onNotFound(name) - } - - const markup = fn(options.args || {}) - - // Set the filter area to be 3x the bounding box of the cell - // and center the filter around the cell. - const attrs = { - x: -1, - y: -1, - width: 3, - height: 3, - filterUnits: 'objectBoundingBox', - ...options.attrs, - id: filterId, - } - Vector.create(Markup.sanitize(markup), attrs).appendTo(this.defs) - } - - return filterId - } - - gradient(options: DefsManager.GradientOptions) { - let id = options.id - const type = options.type - if (!id) { - id = `gradient-${type}-${this.cid}-${StringExt.hashcode( - JSON.stringify(options), - )}` - } - - if (!this.isDefined(id)) { - const stops = options.stops - const arr = stops.map((stop) => { - const opacity = - stop.opacity != null && Number.isFinite(stop.opacity) - ? stop.opacity - : 1 - - return `` - }) - - const markup = `<${type}>${arr.join('')}` - const attrs = { id, ...options.attrs } - Vector.create(markup, attrs).appendTo(this.defs) - } - - return id - } - - marker(options: DefsManager.MarkerOptions) { - const { - id, - refX, - refY, - markerUnits, - markerOrient, - tagName, - children, - ...attrs - } = options - let markerId = id - if (!markerId) { - markerId = `marker-${this.cid}-${StringExt.hashcode( - JSON.stringify(options), - )}` - } - - if (!this.isDefined(markerId)) { - if (tagName !== 'path') { - // remove unnecessary d attribute inherit from standard edge. - delete attrs.d - } - - const pathMarker = Vector.create( - 'marker', - { - refX, - refY, - id: markerId, - overflow: 'visible', - orient: markerOrient != null ? markerOrient : 'auto', - markerUnits: markerUnits || 'userSpaceOnUse', - }, - children - ? children.map(({ tagName, ...other }) => - Vector.create( - `${tagName}` || 'path', - Dom.kebablizeAttrs({ - ...attrs, - ...other, - }), - ), - ) - : [Vector.create(tagName || 'path', Dom.kebablizeAttrs(attrs))], - ) - - this.defs.appendChild(pathMarker.node) - } - - return markerId - } - - remove(id: string) { - const elem = this.svg.getElementById(id) - if (elem && elem.parentNode) { - elem.parentNode.removeChild(elem) - } - } -} - -export namespace DefsManager { - export type MarkerOptions = Marker.Result - - export interface GradientOptions { - id?: string - type: string - stops: { - offset: number - color: string - opacity?: number - }[] - attrs?: Attr.SimpleAttrs - } - - export type FilterOptions = (Filter.NativeItem | Filter.ManaualItem) & { - id?: string - attrs?: Attr.SimpleAttrs - } -} diff --git a/packages/x6-next/src/graph/events.ts b/packages/x6-next/src/graph/events.ts deleted file mode 100644 index d92025b49ba..00000000000 --- a/packages/x6-next/src/graph/events.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Model } from '../model' -import { CellView } from '../view' - -interface CommonEventArgs { - e: E -} - -interface PositionEventArgs extends CommonEventArgs { - x: number - y: number -} - -export interface EventArgs - extends Omit, - CellView.EventArgs { - 'model:sorted'?: Model.EventArgs['sorted'] - 'model:updated': Model.EventArgs['updated'] - 'model:reseted': Model.EventArgs['reseted'] - - 'blank:click': PositionEventArgs - 'blank:dblclick': PositionEventArgs - 'blank:contextmenu': PositionEventArgs - 'blank:mousedown': PositionEventArgs - 'blank:mousemove': PositionEventArgs - 'blank:mouseup': PositionEventArgs - 'blank:mouseout': CommonEventArgs - 'blank:mouseover': CommonEventArgs - 'graph:mouseenter': CommonEventArgs - 'graph:mouseleave': CommonEventArgs - 'blank:mousewheel': PositionEventArgs & { - delta: number - } - - scale: { sx: number; sy: number; ox: number; oy: number } - resize: { width: number; height: number } - translate: { tx: number; ty: number } -} diff --git a/packages/x6-next/src/graph/graph.ts b/packages/x6-next/src/graph/graph.ts deleted file mode 100644 index 65be3fdf69f..00000000000 --- a/packages/x6-next/src/graph/graph.ts +++ /dev/null @@ -1,1253 +0,0 @@ -import { Basecoat, NumberExt, Dom, KeyValue } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { Model, Collection, Cell, Node, Edge } from '../model' -import * as Registry from '../registry' -import { Base } from './base' -import { GraphView } from './view' -import { EventArgs } from './events' -import { CSSManager as Css } from './css' -import { Options as GraphOptions } from './options' -import { GridManager as Grid } from './grid' -import { TransformManager as Transform } from './transform' -import { BackgroundManager as Background } from './background' -import { PanningManager as Panning } from './panning' -import { MouseWheel as Wheel } from './mousewheel' -import { VirtualRenderManager as VirtualRender } from './virtual-render' -import { Renderer as ViewRenderer } from '../renderer' -import { DefsManager as Defs } from './defs' -import { CoordManager as Coord } from './coord' -import { HighlightManager as Highlight } from './highlight' -import { CellView } from '../view' - -export class Graph extends Basecoat { - private installedPlugins: Set = new Set() - - public readonly options: GraphOptions.Definition - public readonly css: Css - public readonly model: Model - public readonly view: GraphView - public readonly grid: Grid - public readonly defs: Defs - public readonly coord: Coord - public readonly renderer: ViewRenderer - public readonly highlight: Highlight - public readonly transform: Transform - public readonly background: Background - public readonly panning: Panning - public readonly mousewheel: Wheel - public readonly virtualRender: VirtualRender - - public get container() { - return this.options.container - } - - protected get [Symbol.toStringTag]() { - return Graph.toStringTag - } - - constructor(options: Partial) { - super() - this.options = GraphOptions.get(options) - this.css = new Css(this) - this.view = new GraphView(this) - this.defs = new Defs(this) - this.coord = new Coord(this) - this.transform = new Transform(this) - this.highlight = new Highlight(this) - this.grid = new Grid(this) - this.background = new Background(this) - - this.model = this.options.model ? this.options.model : new Model() - this.model.graph = this - - this.renderer = new ViewRenderer(this) - this.panning = new Panning(this) - this.mousewheel = new Wheel(this) - this.virtualRender = new VirtualRender(this) - } - - // #region model - - isNode(cell: Cell): cell is Node { - return cell.isNode() - } - - isEdge(cell: Cell): cell is Edge { - return cell.isEdge() - } - - resetCells(cells: Cell[], options: Collection.SetOptions = {}) { - this.model.resetCells(cells, options) - return this - } - - clearCells(options: Cell.SetOptions = {}) { - this.model.clear(options) - return this - } - - toJSON(options: Model.ToJSONOptions = {}) { - return this.model.toJSON(options) - } - - parseJSON(data: Model.FromJSONData) { - return this.model.parseJSON(data) - } - - fromJSON(data: Model.FromJSONData, options: Model.FromJSONOptions = {}) { - this.model.fromJSON(data, options) - return this - } - - getCellById(id: string) { - return this.model.getCell(id) - } - - addNode(metadata: Node.Metadata, options?: Model.AddOptions): Node - addNode(node: Node, options?: Model.AddOptions): Node - addNode(node: Node | Node.Metadata, options: Model.AddOptions = {}): Node { - return this.model.addNode(node, options) - } - - addNodes(nodes: (Node | Node.Metadata)[], options: Model.AddOptions = {}) { - return this.addCell( - nodes.map((node) => (Node.isNode(node) ? node : this.createNode(node))), - options, - ) - } - - createNode(metadata: Node.Metadata) { - return this.model.createNode(metadata) - } - - removeNode(nodeId: string, options?: Collection.RemoveOptions): Node | null - removeNode(node: Node, options?: Collection.RemoveOptions): Node | null - removeNode(node: Node | string, options: Collection.RemoveOptions = {}) { - return this.model.removeCell(node as Node, options) as Node - } - - addEdge(metadata: Edge.Metadata, options?: Model.AddOptions): Edge - addEdge(edge: Edge, options?: Model.AddOptions): Edge - addEdge(edge: Edge | Edge.Metadata, options: Model.AddOptions = {}): Edge { - return this.model.addEdge(edge, options) - } - - addEdges(edges: (Edge | Edge.Metadata)[], options: Model.AddOptions = {}) { - return this.addCell( - edges.map((edge) => (Edge.isEdge(edge) ? edge : this.createEdge(edge))), - options, - ) - } - - removeEdge(edgeId: string, options?: Collection.RemoveOptions): Edge | null - removeEdge(edge: Edge, options?: Collection.RemoveOptions): Edge | null - removeEdge(edge: Edge | string, options: Collection.RemoveOptions = {}) { - return this.model.removeCell(edge as Edge, options) as Edge - } - - createEdge(metadata: Edge.Metadata) { - return this.model.createEdge(metadata) - } - - addCell(cell: Cell | Cell[], options: Model.AddOptions = {}) { - this.model.addCell(cell, options) - return this - } - - removeCell(cellId: string, options?: Collection.RemoveOptions): Cell | null - removeCell(cell: Cell, options?: Collection.RemoveOptions): Cell | null - removeCell(cell: Cell | string, options: Collection.RemoveOptions = {}) { - return this.model.removeCell(cell as Cell, options) - } - - removeCells(cells: (Cell | string)[], options: Cell.RemoveOptions = {}) { - return this.model.removeCells(cells, options) - } - - removeConnectedEdges(cell: Cell | string, options: Cell.RemoveOptions = {}) { - return this.model.removeConnectedEdges(cell, options) - } - - disconnectConnectedEdges(cell: Cell | string, options: Edge.SetOptions = {}) { - this.model.disconnectConnectedEdges(cell, options) - return this - } - - hasCell(cellId: string): boolean - hasCell(cell: Cell): boolean - hasCell(cell: string | Cell): boolean { - return this.model.has(cell as Cell) - } - - getCells() { - return this.model.getCells() - } - - getCellCount() { - return this.model.total() - } - - /** - * Returns all the nodes in the graph. - */ - getNodes() { - return this.model.getNodes() - } - - /** - * Returns all the edges in the graph. - */ - getEdges() { - return this.model.getEdges() - } - - /** - * Returns all outgoing edges for the node. - */ - getOutgoingEdges(cell: Cell | string) { - return this.model.getOutgoingEdges(cell) - } - - /** - * Returns all incoming edges for the node. - */ - getIncomingEdges(cell: Cell | string) { - return this.model.getIncomingEdges(cell) - } - - /** - * Returns edges connected with cell. - */ - getConnectedEdges( - cell: Cell | string, - options: Model.GetConnectedEdgesOptions = {}, - ) { - return this.model.getConnectedEdges(cell, options) - } - - /** - * Returns an array of all the roots of the graph. - */ - getRootNodes() { - return this.model.getRoots() - } - - /** - * Returns an array of all the leafs of the graph. - */ - getLeafNodes() { - return this.model.getLeafs() - } - - /** - * Returns `true` if the node is a root node, i.e. - * there is no edges coming to the node. - */ - isRootNode(cell: Cell | string) { - return this.model.isRoot(cell) - } - - /** - * Returns `true` if the node is a leaf node, i.e. - * there is no edges going out from the node. - */ - isLeafNode(cell: Cell | string) { - return this.model.isLeaf(cell) - } - - /** - * Returns all the neighbors of node in the graph. Neighbors are all - * the nodes connected to node via either incoming or outgoing edge. - */ - getNeighbors(cell: Cell, options: Model.GetNeighborsOptions = {}) { - return this.model.getNeighbors(cell, options) - } - - /** - * Returns `true` if `cell2` is a neighbor of `cell1`. - */ - isNeighbor( - cell1: Cell, - cell2: Cell, - options: Model.GetNeighborsOptions = {}, - ) { - return this.model.isNeighbor(cell1, cell2, options) - } - - getSuccessors(cell: Cell, options: Model.GetPredecessorsOptions = {}) { - return this.model.getSuccessors(cell, options) - } - - /** - * Returns `true` if `cell2` is a successor of `cell1`. - */ - isSuccessor( - cell1: Cell, - cell2: Cell, - options: Model.GetPredecessorsOptions = {}, - ) { - return this.model.isSuccessor(cell1, cell2, options) - } - - getPredecessors(cell: Cell, options: Model.GetPredecessorsOptions = {}) { - return this.model.getPredecessors(cell, options) - } - - /** - * Returns `true` if `cell2` is a predecessor of `cell1`. - */ - isPredecessor( - cell1: Cell, - cell2: Cell, - options: Model.GetPredecessorsOptions = {}, - ) { - return this.model.isPredecessor(cell1, cell2, options) - } - - getCommonAncestor(...cells: (Cell | null | undefined)[]) { - return this.model.getCommonAncestor(...cells) - } - - /** - * Returns an array of cells that result from finding nodes/edges that - * are connected to any of the cells in the cells array. This function - * loops over cells and if the current cell is a edge, it collects its - * source/target nodes; if it is an node, it collects its incoming and - * outgoing edges if both the edge terminal (source/target) are in the - * cells array. - */ - getSubGraph(cells: Cell[], options: Model.GetSubgraphOptions = {}) { - return this.model.getSubGraph(cells, options) - } - - /** - * Clones the whole subgraph (including all the connected links whose - * source/target is in the subgraph). If `options.deep` is `true`, also - * take into account all the embedded cells of all the subgraph cells. - * - * Returns a map of the form: { [original cell ID]: [clone] }. - */ - cloneSubGraph(cells: Cell[], options: Model.GetSubgraphOptions = {}) { - return this.model.cloneSubGraph(cells, options) - } - - cloneCells(cells: Cell[]) { - return this.model.cloneCells(cells) - } - - /** - * Returns an array of nodes whose bounding box contains point. - * Note that there can be more then one node as nodes might overlap. - */ - getNodesFromPoint(x: number, y: number): Node[] - getNodesFromPoint(p: Point.PointLike): Node[] - getNodesFromPoint(x: number | Point.PointLike, y?: number) { - return this.model.getNodesFromPoint(x as number, y as number) - } - - /** - * Returns an array of nodes whose bounding box top/left coordinate - * falls into the rectangle. - */ - getNodesInArea( - x: number, - y: number, - w: number, - h: number, - options?: Model.GetCellsInAreaOptions, - ): Node[] - getNodesInArea( - rect: Rectangle.RectangleLike, - options?: Model.GetCellsInAreaOptions, - ): Node[] - getNodesInArea( - x: number | Rectangle.RectangleLike, - y?: number | Model.GetCellsInAreaOptions, - w?: number, - h?: number, - options?: Model.GetCellsInAreaOptions, - ): Node[] { - return this.model.getNodesInArea( - x as number, - y as number, - w as number, - h as number, - options, - ) - } - - getNodesUnderNode( - node: Node, - options: { - by?: 'bbox' | Rectangle.KeyPoint - } = {}, - ) { - return this.model.getNodesUnderNode(node, options) - } - - searchCell( - cell: Cell, - iterator: Model.SearchIterator, - options: Model.SearchOptions = {}, - ) { - this.model.search(cell, iterator, options) - return this - } - - /** * - * Returns an array of IDs of nodes on the shortest - * path between source and target. - */ - getShortestPath( - source: Cell | string, - target: Cell | string, - options: Model.GetShortestPathOptions = {}, - ) { - return this.model.getShortestPath(source, target, options) - } - - /** - * Returns the bounding box that surrounds all cells in the graph. - */ - getAllCellsBBox() { - return this.model.getAllCellsBBox() - } - - /** - * Returns the bounding box that surrounds all the given cells. - */ - getCellsBBox(cells: Cell[], options: Cell.GetCellsBBoxOptions = {}) { - return this.model.getCellsBBox(cells, options) - } - - startBatch(name: string | Model.BatchName, data: KeyValue = {}) { - this.model.startBatch(name as Model.BatchName, data) - } - - stopBatch(name: string | Model.BatchName, data: KeyValue = {}) { - this.model.stopBatch(name as Model.BatchName, data) - } - - batchUpdate(execute: () => T, data?: KeyValue): T - batchUpdate( - name: string | Model.BatchName, - execute: () => T, - data?: KeyValue, - ): T - batchUpdate( - arg1: string | Model.BatchName | (() => T), - arg2?: (() => T) | KeyValue, - arg3?: KeyValue, - ): T { - const name = typeof arg1 === 'string' ? arg1 : 'update' - const execute = typeof arg1 === 'string' ? (arg2 as () => T) : arg1 - const data = typeof arg2 === 'function' ? arg3 : arg2 - this.startBatch(name, data) - const result = execute() - this.stopBatch(name, data) - return result - } - - updateCellId(cell: Cell, newId: string) { - return this.model.updateCellId(cell, newId) - } - - // #endregion - - // #region view - - findView(ref: Cell | Element) { - if (Cell.isCell(ref)) { - return this.findViewByCell(ref) - } - - return this.findViewByElem(ref) - } - - findViews(ref: Point.PointLike | Rectangle.RectangleLike) { - if (Rectangle.isRectangleLike(ref)) { - return this.findViewsInArea(ref) - } - - if (Point.isPointLike(ref)) { - return this.findViewsFromPoint(ref) - } - - return [] - } - - findViewByCell(cellId: string | number): CellView | null - findViewByCell(cell: Cell | null): CellView | null - findViewByCell( - cell: Cell | string | number | null | undefined, - ): CellView | null { - return this.renderer.findViewByCell(cell as Cell) - } - - findViewByElem(elem: string | Element | undefined | null) { - return this.renderer.findViewByElem(elem) - } - - findViewsFromPoint(x: number, y: number): CellView[] - findViewsFromPoint(p: Point.PointLike): CellView[] - findViewsFromPoint(x: number | Point.PointLike, y?: number) { - const p = typeof x === 'number' ? { x, y: y as number } : x - return this.renderer.findViewsFromPoint(p) - } - - findViewsInArea( - x: number, - y: number, - width: number, - height: number, - options?: ViewRenderer.FindViewsInAreaOptions, - ): CellView[] - findViewsInArea( - rect: Rectangle.RectangleLike, - options?: ViewRenderer.FindViewsInAreaOptions, - ): CellView[] - findViewsInArea( - x: number | Rectangle.RectangleLike, - y?: number | ViewRenderer.FindViewsInAreaOptions, - width?: number, - height?: number, - options?: ViewRenderer.FindViewsInAreaOptions, - ) { - const rect = - typeof x === 'number' - ? { - x, - y: y as number, - width: width as number, - height: height as number, - } - : x - const localOptions = - typeof x === 'number' - ? options - : (y as ViewRenderer.FindViewsInAreaOptions) - return this.renderer.findViewsInArea(rect, localOptions) - } - - // #endregion - - // #region transform - - /** - * Returns the current transformation matrix of the graph. - */ - matrix(): DOMMatrix - /** - * Sets new transformation with the given `matrix` - */ - matrix(mat: DOMMatrix | Dom.MatrixLike | null): this - matrix(mat?: DOMMatrix | Dom.MatrixLike | null) { - if (typeof mat === 'undefined') { - return this.transform.getMatrix() - } - this.transform.setMatrix(mat) - return this - } - - resize(width?: number, height?: number) { - this.transform.resize(width, height) - return this - } - - scale(): Dom.Scale - scale(sx: number, sy?: number, cx?: number, cy?: number): this - scale(sx?: number, sy: number = sx as number, cx = 0, cy = 0) { - if (typeof sx === 'undefined') { - return this.transform.getScale() - } - this.transform.scale(sx, sy, cx, cy) - return this - } - - zoom(): number - zoom(factor: number, options?: Transform.ZoomOptions): this - zoom(factor?: number, options?: Transform.ZoomOptions) { - if (typeof factor === 'undefined') { - return this.transform.getZoom() - } - this.transform.zoom(factor, options) - - return this - } - - zoomTo( - factor: number, - options: Omit = {}, - ) { - this.transform.zoom(factor, { ...options, absolute: true }) - - return this - } - - zoomToRect( - rect: Rectangle.RectangleLike, - options: Transform.ScaleContentToFitOptions & - Transform.ScaleContentToFitOptions = {}, - ) { - this.transform.zoomToRect(rect, options) - - return this - } - - zoomToFit( - options: Transform.GetContentAreaOptions & - Transform.ScaleContentToFitOptions = {}, - ) { - this.transform.zoomToFit(options) - - return this - } - - rotate(): Dom.Rotation - rotate(angle: number, cx?: number, cy?: number): this - rotate(angle?: number, cx?: number, cy?: number) { - if (typeof angle === 'undefined') { - return this.transform.getRotation() - } - - this.transform.rotate(angle, cx, cy) - return this - } - - translate(): Dom.Translation - translate(tx: number, ty: number): this - translate(tx?: number, ty?: number) { - if (typeof tx === 'undefined') { - return this.transform.getTranslation() - } - - this.transform.translate(tx, ty as number) - return this - } - - translateBy(dx: number, dy: number): this { - const ts = this.translate() - const tx = ts.tx + dx - const ty = ts.ty + dy - return this.translate(tx, ty) - } - - getGraphArea() { - return this.transform.getGraphArea() - } - - getContentArea(options: Transform.GetContentAreaOptions = {}) { - return this.transform.getContentArea(options) - } - - getContentBBox(options: Transform.GetContentAreaOptions = {}) { - return this.transform.getContentBBox(options) - } - - fitToContent( - gridWidth?: number, - gridHeight?: number, - padding?: NumberExt.SideOptions, - options?: Transform.FitToContentOptions, - ): Rectangle - fitToContent(options?: Transform.FitToContentFullOptions): Rectangle - fitToContent( - gridWidth?: number | Transform.FitToContentFullOptions, - gridHeight?: number, - padding?: NumberExt.SideOptions, - options?: Transform.FitToContentOptions, - ) { - return this.transform.fitToContent(gridWidth, gridHeight, padding, options) - } - - scaleContentToFit(options: Transform.ScaleContentToFitOptions = {}) { - this.transform.scaleContentToFit(options) - return this - } - - /** - * Position the center of graph to the center of the viewport. - */ - center() { - return this.centerPoint() - } - - /** - * Position the point (x,y) on the graph (in local coordinates) to the - * center of the viewport. If only one of the coordinates is specified, - * only center along the specified dimension and keep the other coordinate - * unchanged. - */ - centerPoint(x: number, y: null | number): this - centerPoint(x: null | number, y: number): this - centerPoint(): this - centerPoint(x?: number | null, y?: number | null) { - this.transform.centerPoint(x as number, y as number) - return this - } - - centerContent(options?: Transform.PositionContentOptions) { - this.transform.centerContent(options) - return this - } - - centerCell(cell: Cell) { - this.transform.centerCell(cell) - return this - } - - positionPoint( - point: Point.PointLike, - x: number | string, - y: number | string, - ) { - this.transform.positionPoint(point, x, y) - return this - } - - positionRect(rect: Rectangle.RectangleLike, direction: Transform.Direction) { - this.transform.positionRect(rect, direction) - return this - } - - positionCell(cell: Cell, direction: Transform.Direction) { - this.transform.positionCell(cell, direction) - return this - } - - positionContent( - pos: Transform.Direction, - options?: Transform.PositionContentOptions, - ) { - this.transform.positionContent(pos, options) - return this - } - - // #endregion - - // #region coord - - snapToGrid(p: Point.PointLike): Point - snapToGrid(x: number, y: number): Point - snapToGrid(x: number | Point.PointLike, y?: number) { - return this.coord.snapToGrid(x, y) - } - - pageToLocal(rect: Rectangle.RectangleLike): Rectangle - pageToLocal(x: number, y: number, width: number, height: number): Rectangle - pageToLocal(p: Point.PointLike): Point - pageToLocal(x: number, y: number): Point - pageToLocal( - x: number | Point.PointLike | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - if (Rectangle.isRectangleLike(x)) { - return this.coord.pageToLocalRect(x) - } - - if ( - typeof x === 'number' && - typeof y === 'number' && - typeof width === 'number' && - typeof height === 'number' - ) { - return this.coord.pageToLocalRect(x, y, width, height) - } - - return this.coord.pageToLocalPoint(x, y) - } - - localToPage(rect: Rectangle.RectangleLike): Rectangle - localToPage(x: number, y: number, width: number, height: number): Rectangle - localToPage(p: Point.PointLike): Point - localToPage(x: number, y: number): Point - localToPage( - x: number | Point.PointLike | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - if (Rectangle.isRectangleLike(x)) { - return this.coord.localToPageRect(x) - } - - if ( - typeof x === 'number' && - typeof y === 'number' && - typeof width === 'number' && - typeof height === 'number' - ) { - return this.coord.localToPageRect(x, y, width, height) - } - - return this.coord.localToPagePoint(x, y) - } - - clientToLocal(rect: Rectangle.RectangleLike): Rectangle - clientToLocal(x: number, y: number, width: number, height: number): Rectangle - clientToLocal(p: Point.PointLike): Point - clientToLocal(x: number, y: number): Point - clientToLocal( - x: number | Point.PointLike | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - if (Rectangle.isRectangleLike(x)) { - return this.coord.clientToLocalRect(x) - } - - if ( - typeof x === 'number' && - typeof y === 'number' && - typeof width === 'number' && - typeof height === 'number' - ) { - return this.coord.clientToLocalRect(x, y, width, height) - } - - return this.coord.clientToLocalPoint(x, y) - } - - localToClient(rect: Rectangle.RectangleLike): Rectangle - localToClient(x: number, y: number, width: number, height: number): Rectangle - localToClient(p: Point.PointLike): Point - localToClient(x: number, y: number): Point - localToClient( - x: number | Point.PointLike | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - if (Rectangle.isRectangleLike(x)) { - return this.coord.localToClientRect(x) - } - - if ( - typeof x === 'number' && - typeof y === 'number' && - typeof width === 'number' && - typeof height === 'number' - ) { - return this.coord.localToClientRect(x, y, width, height) - } - - return this.coord.localToClientPoint(x, y) - } - - /** - * Transform the rectangle `rect` defined in the local coordinate system to - * the graph coordinate system. - */ - localToGraph(rect: Rectangle.RectangleLike): Rectangle - /** - * Transform the rectangle `x`, `y`, `width`, `height` defined in the local - * coordinate system to the graph coordinate system. - */ - localToGraph(x: number, y: number, width: number, height: number): Rectangle - /** - * Transform the point `p` defined in the local coordinate system to - * the graph coordinate system. - */ - localToGraph(p: Point.PointLike): Point - /** - * Transform the point `x`, `y` defined in the local coordinate system to - * the graph coordinate system. - */ - localToGraph(x: number, y: number): Point - localToGraph( - x: number | Point.PointLike | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - if (Rectangle.isRectangleLike(x)) { - return this.coord.localToGraphRect(x) - } - - if ( - typeof x === 'number' && - typeof y === 'number' && - typeof width === 'number' && - typeof height === 'number' - ) { - return this.coord.localToGraphRect(x, y, width, height) - } - - return this.coord.localToGraphPoint(x, y) - } - - graphToLocal(rect: Rectangle.RectangleLike): Rectangle - graphToLocal(x: number, y: number, width: number, height: number): Rectangle - graphToLocal(p: Point.PointLike): Point - graphToLocal(x: number, y: number): Point - graphToLocal( - x: number | Point.PointLike | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - if (Rectangle.isRectangleLike(x)) { - return this.coord.graphToLocalRect(x) - } - - if ( - typeof x === 'number' && - typeof y === 'number' && - typeof width === 'number' && - typeof height === 'number' - ) { - return this.coord.graphToLocalRect(x, y, width, height) - } - return this.coord.graphToLocalPoint(x, y) - } - - clientToGraph(rect: Rectangle.RectangleLike): Rectangle - clientToGraph(x: number, y: number, width: number, height: number): Rectangle - clientToGraph(p: Point.PointLike): Point - clientToGraph(x: number, y: number): Point - clientToGraph( - x: number | Point.PointLike | Rectangle.RectangleLike, - y?: number, - width?: number, - height?: number, - ) { - if (Rectangle.isRectangleLike(x)) { - return this.coord.clientToGraphRect(x) - } - if ( - typeof x === 'number' && - typeof y === 'number' && - typeof width === 'number' && - typeof height === 'number' - ) { - return this.coord.clientToGraphRect(x, y, width, height) - } - return this.coord.clientToGraphPoint(x, y) - } - - // #endregion - - // #region defs - - defineFilter(options: Defs.FilterOptions) { - return this.defs.filter(options) - } - - defineGradient(options: Defs.GradientOptions) { - return this.defs.gradient(options) - } - - defineMarker(options: Defs.MarkerOptions) { - return this.defs.marker(options) - } - - // #endregion - - // #region grid - - getGridSize() { - return this.grid.getGridSize() - } - - setGridSize(gridSize: number) { - this.grid.setGridSize(gridSize) - return this - } - - showGrid() { - this.grid.show() - return this - } - - hideGrid() { - this.grid.hide() - return this - } - - clearGrid() { - this.grid.clear() - return this - } - - drawGrid(options?: Grid.DrawGridOptions) { - this.grid.draw(options) - return this - } - - // #endregion - - // #region background - - updateBackground() { - this.background.update() - return this - } - - drawBackground(options?: Background.Options) { - this.background.draw(options) - return this - } - - clearBackground() { - this.background.clear() - return this - } - - // #endregion - - // #region virtual-render - - enableVirtualRender() { - this.virtualRender.enableVirtualRender() - return this - } - - disableVirtualRender() { - this.virtualRender.disableVirtualRender() - return this - } - - // #endregion - - // #region mousewheel - - isMouseWheelEnabled() { - return !this.mousewheel.disabled - } - - enableMouseWheel() { - this.mousewheel.enable() - return this - } - - disableMouseWheel() { - this.mousewheel.disable() - return this - } - - toggleMouseWheel(enabled?: boolean) { - if (enabled == null) { - if (this.isMouseWheelEnabled()) { - this.disableMouseWheel() - } else { - this.enableMouseWheel() - } - } else if (enabled) { - this.enableMouseWheel() - } else { - this.disableMouseWheel() - } - return this - } - - // #endregion - - // #region panning - - isPannable() { - return this.panning.pannable - } - - enablePanning() { - this.panning.enablePanning() - return this - } - - disablePanning() { - this.panning.disablePanning() - return this - } - - togglePanning(pannable?: boolean) { - if (pannable == null) { - if (this.isPannable()) { - this.disablePanning() - } else { - this.enablePanning() - } - } else if (pannable !== this.isPannable()) { - if (pannable) { - this.enablePanning() - } else { - this.disablePanning() - } - } - - return this - } - - // #endregion - - // #region plugin - - use(plugin: Graph.Plugin, ...options: any[]) { - if (!this.installedPlugins.has(plugin)) { - this.installedPlugins.add(plugin) - plugin.init(this, ...options) - } - return this - } - - getPlugin(pluginName: string) { - let result: Graph.Plugin | undefined - - this.installedPlugins.forEach((plugin) => { - if (plugin.name === pluginName) { - result = plugin - } - }) - - return result - } - - // #endregion - - // #region dispose - - @Basecoat.dispose() - dispose() { - this.clearCells() - this.off() - - this.css.dispose() - this.defs.dispose() - this.grid.dispose() - this.coord.dispose() - this.transform.dispose() - this.highlight.dispose() - this.background.dispose() - this.mousewheel.dispose() - this.panning.dispose() - this.view.dispose() - this.renderer.dispose() - - this.installedPlugins.forEach((plugin) => { - plugin.dispose() - }) - } - - // #endregion -} - -export namespace Graph { - /* eslint-disable @typescript-eslint/no-unused-vars */ - export import View = GraphView - export import Renderer = ViewRenderer - export import MouseWheel = Wheel - export import BaseManager = Base - export import DefsManager = Defs - export import GridManager = Grid - export import CoordManager = Coord - export import TransformManager = Transform - export import HighlightManager = Highlight - export import BackgroundManager = Background -} - -export namespace Graph { - export interface Options extends GraphOptions.Manual {} -} - -export namespace Graph { - export const toStringTag = `X6.${Graph.name}` - - export function isGraph(instance: any): instance is Graph { - if (instance == null) { - return false - } - - if (instance instanceof Graph) { - return true - } - - const tag = instance[Symbol.toStringTag] - - if (tag == null || tag === toStringTag) { - return true - } - - return false - } -} - -export namespace Graph { - export function render( - options: Partial, - data?: Model.FromJSONData, - ): Graph - export function render( - container: HTMLElement, - data?: Model.FromJSONData, - ): Graph - export function render( - options: Partial | HTMLElement, - data?: Model.FromJSONData, - ): Graph { - const graph = - options instanceof HTMLElement - ? new Graph({ container: options }) - : new Graph(options) - - if (data != null) { - graph.fromJSON(data) - } - - return graph - } -} - -export namespace Graph { - export const registerNode = Node.registry.register - export const registerEdge = Edge.registry.register - export const registerView = CellView.registry.register - export const registerAttr = Registry.Attr.registry.register - export const registerGrid = Registry.Grid.registry.register - export const registerFilter = Registry.Filter.registry.register - export const registerNodeTool = Registry.NodeTool.registry.register - export const registerEdgeTool = Registry.EdgeTool.registry.register - export const registerBackground = Registry.Background.registry.register - export const registerHighlighter = Registry.Highlighter.registry.register - export const registerPortLayout = Registry.PortLayout.registry.register - export const registerPortLabelLayout = - Registry.PortLabelLayout.registry.register - export const registerMarker = Registry.Marker.registry.register - export const registerRouter = Registry.Router.registry.register - export const registerConnector = Registry.Connector.registry.register - export const registerAnchor = Registry.NodeAnchor.registry.register - export const registerEdgeAnchor = Registry.EdgeAnchor.registry.register - export const registerConnectionPoint = - Registry.ConnectionPoint.registry.register -} - -export namespace Graph { - export const unregisterNode = Node.registry.unregister - export const unregisterEdge = Edge.registry.unregister - export const unregisterView = CellView.registry.unregister - export const unregisterAttr = Registry.Attr.registry.unregister - export const unregisterGrid = Registry.Grid.registry.unregister - export const unregisterFilter = Registry.Filter.registry.unregister - export const unregisterNodeTool = Registry.NodeTool.registry.unregister - export const unregisterEdgeTool = Registry.EdgeTool.registry.unregister - export const unregisterBackground = Registry.Background.registry.unregister - export const unregisterHighlighter = Registry.Highlighter.registry.unregister - export const unregisterPortLayout = Registry.PortLayout.registry.unregister - export const unregisterPortLabelLayout = - Registry.PortLabelLayout.registry.unregister - export const unregisterMarker = Registry.Marker.registry.unregister - export const unregisterRouter = Registry.Router.registry.unregister - export const unregisterConnector = Registry.Connector.registry.unregister - export const unregisterAnchor = Registry.NodeAnchor.registry.unregister - export const unregisterEdgeAnchor = Registry.EdgeAnchor.registry.unregister - export const unregisterConnectionPoint = - Registry.ConnectionPoint.registry.unregister -} - -export namespace Graph { - export type Plugin = { - name: string - init: (graph: Graph, ...options: any[]) => any - dispose: () => void - } -} diff --git a/packages/x6-next/src/graph/grid.ts b/packages/x6-next/src/graph/grid.ts deleted file mode 100644 index 40c935e9cbd..00000000000 --- a/packages/x6-next/src/graph/grid.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { Dom, Vector } from '@antv/x6-common' -import * as Registry from '../registry' -import { Base } from './base' - -export class GridManager extends Base { - protected instance: Registry.Grid | null - protected patterns: Registry.Grid.Definition[] - - protected get elem() { - return this.view.grid - } - - protected get grid() { - return this.options.grid - } - - protected init() { - this.startListening() - this.draw(this.grid) - } - - protected startListening() { - this.graph.on('scale', this.update, this) - this.graph.on('translate', this.update, this) - } - - protected stopListening() { - this.graph.off('scale', this.update, this) - this.graph.off('translate', this.update, this) - } - - protected setVisible(visible: boolean) { - if (this.grid.visible !== visible) { - this.grid.visible = visible - this.update() - } - } - - getGridSize() { - return this.grid.size - } - - setGridSize(size: number) { - this.grid.size = Math.max(size, 1) - this.update() - } - - show() { - this.setVisible(true) - this.update() - } - - hide() { - this.setVisible(false) - this.update() - } - - clear() { - this.elem.style.backgroundImage = '' - } - - draw(options?: GridManager.DrawGridOptions) { - this.clear() - this.instance = null - Object.assign(this.grid, options) - this.patterns = this.resolveGrid(options) - this.update() - } - - update( - options: - | Partial - | Partial[] = {}, - ) { - const gridSize = this.grid.size - if (gridSize <= 1 || !this.grid.visible) { - return this.clear() - } - - const ctm = this.graph.matrix() - const grid = this.getInstance() - const items = Array.isArray(options) ? options : [options] - - this.patterns.forEach((settings, index) => { - const id = `pattern_${index}` - const sx = ctm.a || 1 - const sy = ctm.d || 1 - - const { update, markup, ...others } = settings - const options = { - ...others, - ...items[index], - sx, - sy, - ox: ctm.e || 0, - oy: ctm.f || 0, - width: gridSize * sx, - height: gridSize * sy, - } - - if (!grid.has(id)) { - grid.add( - id, - Vector.create( - 'pattern', - { id, patternUnits: 'userSpaceOnUse' }, - Vector.createVectors(markup), - ).node, - ) - } - - const patternElem = grid.get(id) - - if (typeof update === 'function') { - update(patternElem.childNodes[0] as Element, options) - } - - let x = options.ox % options.width - if (x < 0) { - x += options.width - } - - let y = options.oy % options.height - if (y < 0) { - y += options.height - } - - Dom.attr(patternElem, { - x, - y, - width: options.width, - height: options.height, - }) - }) - - const base64 = new XMLSerializer().serializeToString(grid.root) - const url = `url(data:image/svg+xml;base64,${btoa(base64)})` - this.elem.style.backgroundImage = url - } - - protected getInstance() { - if (!this.instance) { - this.instance = new Registry.Grid() - } - - return this.instance - } - - protected resolveGrid( - options?: GridManager.DrawGridOptions, - ): Registry.Grid.Definition[] | never { - if (!options) { - return [] - } - - const type = (options as Registry.Grid.NativeItem).type - if (type == null) { - return [ - { - ...Registry.Grid.presets.dot, - ...options.args, - }, - ] - } - - const items = Registry.Grid.registry.get(type) - if (items) { - let args = options.args || [] - if (!Array.isArray(args)) { - args = [args] - } - - return Array.isArray(items) - ? items.map((item, index) => ({ ...item, ...args[index] })) - : [{ ...items, ...args[0] }] - } - - return Registry.Grid.registry.onNotFound(type) - } - - @Base.dispose() - dispose() { - this.stopListening() - this.clear() - } -} - -export namespace GridManager { - export type DrawGridOptions = - | Registry.Grid.NativeItem - | Registry.Grid.ManaualItem - | { - args?: Registry.Grid.OptionsMap['dot'] - } - - export interface CommonOptions { - size: number - visible: boolean - } - - export type Options = CommonOptions & DrawGridOptions -} diff --git a/packages/x6-next/src/graph/highlight.ts b/packages/x6-next/src/graph/highlight.ts deleted file mode 100644 index 4a78631e294..00000000000 --- a/packages/x6-next/src/graph/highlight.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Dom, KeyValue } from '@antv/x6-common' -import { CellView } from '../view' -import { Highlighter } from '../registry' -import { EventArgs } from './events' -import { Base } from './base' - -export class HighlightManager extends Base { - protected readonly highlights: KeyValue = {} - - protected init() { - this.startListening() - } - - protected startListening() { - this.graph.on('cell:highlight', this.onCellHighlight, this) - this.graph.on('cell:unhighlight', this.onCellUnhighlight, this) - } - - protected stopListening() { - this.graph.off('cell:highlight', this.onCellHighlight, this) - this.graph.off('cell:unhighlight', this.onCellUnhighlight, this) - } - - protected onCellHighlight({ - view: cellView, - magnet, - options = {}, - }: EventArgs['cell:highlight']) { - const resolved = this.resolveHighlighter(options) - if (!resolved) { - return - } - - const key = this.getHighlighterId(magnet, resolved) - if (!this.highlights[key]) { - const highlighter = resolved.highlighter - highlighter.highlight(cellView, magnet, { ...resolved.args }) - - this.highlights[key] = { - cellView, - magnet, - highlighter, - args: resolved.args, - } - } - } - - protected onCellUnhighlight({ - magnet, - options = {}, - }: EventArgs['cell:unhighlight']) { - const resolved = this.resolveHighlighter(options) - if (!resolved) { - return - } - - const id = this.getHighlighterId(magnet, resolved) - this.unhighlight(id) - } - - protected resolveHighlighter(options: CellView.HighlightOptions) { - const graphOptions = this.options - let highlighterDef: string | undefined | Highlighter.ManaualItem = - options.highlighter - - if (highlighterDef == null) { - // check for built-in types - const type = options.type - highlighterDef = - (type && graphOptions.highlighting[type]) || - graphOptions.highlighting.default - } - - if (highlighterDef == null) { - return null - } - - const def: Highlighter.ManaualItem = - typeof highlighterDef === 'string' - ? { - name: highlighterDef, - } - : highlighterDef - - const name = def.name - const highlighter = Highlighter.registry.get(name) - if (highlighter == null) { - return Highlighter.registry.onNotFound(name) - } - - Highlighter.check(name, highlighter) - - return { - name, - highlighter, - args: def.args || {}, - } - } - - protected getHighlighterId( - magnet: Element, - options: NonNullable< - ReturnType - >, - ) { - Dom.ensureId(magnet) - return options.name + magnet.id + JSON.stringify(options.args) - } - - protected unhighlight(id: string) { - const highlight = this.highlights[id] - if (highlight) { - highlight.highlighter.unhighlight( - highlight.cellView, - highlight.magnet, - highlight.args, - ) - - delete this.highlights[id] - } - } - - @HighlightManager.dispose() - dispose() { - Object.keys(this.highlights).forEach((id) => this.unhighlight(id)) - this.stopListening() - } -} - -export namespace HighlightManager { - export interface Cache { - highlighter: Highlighter.Definition - cellView: CellView - magnet: Element - args: KeyValue - } - - export type Options = Highlighter.NativeItem | Highlighter.ManaualItem -} diff --git a/packages/x6-next/src/graph/index.ts b/packages/x6-next/src/graph/index.ts deleted file mode 100644 index f47fc44af6b..00000000000 --- a/packages/x6-next/src/graph/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './graph' -export * from './view' -export * from './events' -export * from './transform' -export * from './background' diff --git a/packages/x6-next/src/graph/mousewheel.ts b/packages/x6-next/src/graph/mousewheel.ts deleted file mode 100644 index 0088cb1d365..00000000000 --- a/packages/x6-next/src/graph/mousewheel.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { ModifierKey, Dom, NumberExt, Disposable } from '@antv/x6-common' -import { Base } from './base' - -export class MouseWheel extends Base { - public target: HTMLElement | Document - public container: HTMLElement - - protected cumulatedFactor = 1 - protected currentScale: number | null - protected startPos: { x: number; y: number } - - private mousewheelHandle: Dom.MouseWheelHandle - - protected get widgetOptions() { - return this.options.mousewheel - } - - protected init() { - this.container = this.graph.container - this.target = this.widgetOptions.global ? document : this.container - this.mousewheelHandle = new Dom.MouseWheelHandle( - this.target, - this.onMouseWheel.bind(this), - this.allowMouseWheel.bind(this), - ) - if (this.widgetOptions.enabled) { - this.enable(true) - } - } - - get disabled() { - return this.widgetOptions.enabled !== true - } - - enable(force?: boolean) { - if (this.disabled || force) { - this.widgetOptions.enabled = true - this.mousewheelHandle.enable() - } - } - - disable() { - if (!this.disabled) { - this.widgetOptions.enabled = false - this.mousewheelHandle.disable() - } - } - - protected allowMouseWheel(e: WheelEvent) { - const guard = this.widgetOptions.guard - - return ( - (guard == null || guard.call(e)) && - ModifierKey.isMatch(e, this.widgetOptions.modifiers) - ) - } - - protected onMouseWheel(e: WheelEvent) { - const guard = this.widgetOptions.guard - - if ( - (guard == null || guard.call(e)) && - ModifierKey.isMatch(e, this.widgetOptions.modifiers) - ) { - const factor = this.widgetOptions.factor || 1.2 - - if (this.currentScale == null) { - this.startPos = { x: e.clientX, y: e.clientY } - this.currentScale = this.graph.transform.getScale().sx - } - - const delta = e.deltaY - if (delta < 0) { - // zoomin - // ------ - // Switches to 1% zoom steps below 15% - if (this.currentScale < 0.15) { - this.cumulatedFactor = (this.currentScale + 0.01) / this.currentScale - } else { - // Uses to 5% zoom steps for better grid rendering in - // webkit and to avoid rounding errors for zoom steps - this.cumulatedFactor = - Math.round(this.currentScale * factor * 20) / 20 / this.currentScale - } - } else { - // zoomout - // ------- - // Switches to 1% zoom steps below 15% - if (this.currentScale <= 0.15) { - this.cumulatedFactor = (this.currentScale - 0.01) / this.currentScale - } else { - // Uses to 5% zoom steps for better grid rendering in - // webkit and to avoid rounding errors for zoom steps - this.cumulatedFactor = - Math.round(this.currentScale * (1 / factor) * 20) / - 20 / - this.currentScale - } - } - - this.cumulatedFactor = Math.max( - 0.01, - Math.min(this.currentScale * this.cumulatedFactor, 160) / - this.currentScale, - ) - - const currentScale = this.currentScale! - let targetScale = this.graph.transform.clampScale( - currentScale * this.cumulatedFactor, - ) - const minScale = this.widgetOptions.minScale || Number.MIN_SAFE_INTEGER - const maxScale = this.widgetOptions.maxScale || Number.MAX_SAFE_INTEGER - targetScale = NumberExt.clamp(targetScale, minScale, maxScale) - - if (targetScale !== currentScale) { - if (this.widgetOptions.zoomAtMousePosition) { - const origin = this.graph.coord.clientToGraphPoint(this.startPos) - this.graph.zoom(targetScale, { - absolute: true, - center: origin.clone(), - }) - } else { - this.graph.zoom(targetScale, { absolute: true }) - } - } - this.currentScale = null - this.cumulatedFactor = 1 - } - } - - @Disposable.dispose() - dispose() { - this.disable() - } -} - -export namespace MouseWheel { - export interface Options { - enabled?: boolean - global?: boolean - factor?: number - minScale?: number - maxScale?: number - modifiers?: string | ModifierKey[] | null - guard?: (e: WheelEvent) => boolean - zoomAtMousePosition?: boolean - } -} diff --git a/packages/x6-next/src/graph/options.ts b/packages/x6-next/src/graph/options.ts deleted file mode 100644 index 8a1d5734652..00000000000 --- a/packages/x6-next/src/graph/options.ts +++ /dev/null @@ -1,469 +0,0 @@ -import { ObjectExt, Dom, Nilable } from '@antv/x6-common' -import { Rectangle } from '@antv/x6-geometry' -import { Config } from '../config' -import { Graph } from '../graph' -import { GridManager } from './grid' -import { BackgroundManager } from './background' -import { PanningManager } from './panning' -import { MouseWheel } from './mousewheel' -import { Edge as StandardEdge } from '../shape' -import { Model, Cell, Node, Edge } from '../model' -import { CellView, NodeView, EdgeView, Markup } from '../view' -import { - Router, - Connector, - NodeAnchor, - EdgeAnchor, - ConnectionPoint, -} from '../registry' -import { HighlightManager } from './highlight' -import { PortManager } from '../model/port' - -export namespace Options { - interface Common { - container: HTMLElement - model?: Model - - x: number - y: number - width: number - height: number - autoResize?: boolean | Element | Document - - background?: false | BackgroundManager.Options - - scaling: { - min?: number - max?: number - } - - moveThreshold: 0 - clickThreshold: number - magnetThreshold: number | 'onleave' - preventDefaultDblClick: boolean - preventDefaultContextMenu: boolean - preventDefaultMouseDown: boolean - preventDefaultBlankAction: boolean - interacting: CellView.Interacting - - virtual?: boolean - - guard: (e: Dom.EventObject, view?: CellView | null) => boolean - - onPortRendered?: (args: OnPortRenderedArgs) => void - } - - export interface ManualBooleans { - panning: boolean | Partial - mousewheel: boolean | Partial - embedding: boolean | Partial - } - - export interface Manual extends Partial, Partial { - grid?: - | boolean - | number - | (Partial & GridManager.DrawGridOptions) - connecting?: Partial - translating?: Partial - highlighting?: Partial - } - - export interface Definition extends Common { - grid: GridManager.Options - panning: PanningManager.Options - mousewheel: MouseWheel.Options - embedding: Embedding - connecting: Connecting - translating: Translating - highlighting: Highlighting - } -} - -export namespace Options { - type OptionItem = S | ((this: Graph, arg: T) => S) - - type NodeAnchorOptions = - | string - | NodeAnchor.NativeItem - | NodeAnchor.ManaualItem - type EdgeAnchorOptions = - | string - | EdgeAnchor.NativeItem - | EdgeAnchor.ManaualItem - type ConnectionPointOptions = - | string - | ConnectionPoint.NativeItem - | ConnectionPoint.ManaualItem - - export interface Connecting { - /** - * Snap edge to the closest node/port in the given radius on dragging. - */ - snap: boolean | { radius: number } - - /** - * Specify whether connect to point on the graph is allowed. - */ - allowBlank?: - | boolean - | ((this: Graph, args: ValidateConnectionArgs) => boolean) - - /** - * When set to `false`, edges can not be connected to the same node, - * meaning the source and target of the edge can not be the same node. - */ - allowLoop: - | boolean - | ((this: Graph, args: ValidateConnectionArgs) => boolean) - - /** - * Specify whether connect to node(not the port on the node) is allowed. - */ - allowNode: - | boolean - | ((this: Graph, args: ValidateConnectionArgs) => boolean) - - /** - * Specify whether connect to edge is allowed. - */ - allowEdge: - | boolean - | ((this: Graph, args: ValidateConnectionArgs) => boolean) - - /** - * Specify whether connect to port is allowed. - */ - allowPort: - | boolean - | ((this: Graph, args: ValidateConnectionArgs) => boolean) - - /** - * Specify whether more than one edge connected to the same source and - * target node is allowed. - */ - allowMulti?: - | boolean - | 'withPort' - | ((this: Graph, args: ValidateConnectionArgs) => boolean) - - /** - * Highlights all the available magnets or nodes when a edge is - * dragging(reconnecting). This gives a hint to the user to what - * other nodes/ports this edge can be connected. What magnets/cells - * are available is determined by the `validateConnection` function. - */ - highlight: boolean - - anchor: NodeAnchorOptions - sourceAnchor?: NodeAnchorOptions - targetAnchor?: NodeAnchorOptions - edgeAnchor: EdgeAnchorOptions - sourceEdgeAnchor?: EdgeAnchorOptions - targetEdgeAnchor?: EdgeAnchorOptions - - connectionPoint: ConnectionPointOptions - sourceConnectionPoint?: ConnectionPointOptions - targetConnectionPoint?: ConnectionPointOptions - - router: string | Router.NativeItem | Router.ManaualItem - connector: string | Connector.NativeItem | Connector.ManaualItem - - /** - * Check whether to add a new edge to the graph when user clicks - * on an a magnet. - */ - validateMagnet?: ( - this: Graph, - args: { - cell: Cell - view: CellView - magnet: Element - e: Dom.MouseDownEvent | Dom.MouseEnterEvent - }, - ) => boolean - - createEdge?: ( - this: Graph, - args: { - sourceCell: Cell - sourceView: CellView - sourceMagnet: Element - }, - ) => Nilable | void - - /** - * Custom validation on stop draggin the edge arrowhead(source/target). - * If the function returns `false`, the edge is either removed(edges - * which are created during the interaction) or reverted to the state - * before the interaction. - */ - validateEdge?: ( - this: Graph, - args: { - edge: Edge - type: Edge.TerminalType - previous: Edge.TerminalData - }, - ) => boolean - - /** - * Check whether to allow or disallow the edge connection while an - * arrowhead end (source/target) being changed. - */ - validateConnection: (this: Graph, args: ValidateConnectionArgs) => boolean - } - - export interface ValidateConnectionArgs { - type?: Edge.TerminalType | null - edge?: Edge | null - edgeView?: EdgeView | null - sourceCell?: Cell | null - targetCell?: Cell | null - sourceView?: CellView | null - targetView?: CellView | null - sourcePort?: string | null - targetPort?: string | null - sourceMagnet?: Element | null - targetMagnet?: Element | null - } - - export interface Translating { - /** - * Restrict the translation (movement) of nodes by a given bounding box. - * If set to `true`, the user will not be able to move nodes outside the - * boundary of the graph area. - */ - restrict: - | boolean - | OptionItem - } - - export interface Embedding { - enabled?: boolean - - /** - * Determines the way how a cell finds a suitable parent when it's dragged - * over the graph. The cell with the highest z-index (visually on the top) - * will be chosen. - */ - findParent?: - | 'bbox' - | 'center' - | 'topLeft' - | 'topRight' - | 'bottomLeft' - | 'bottomRight' - | ((this: Graph, args: { node: Node; view: NodeView }) => Cell[]) - - /** - * If enabled only the node on the very front is taken into account for the - * embedding. If disabled the nodes under the dragged view are tested one by - * one (from front to back) until a valid parent found. - */ - frontOnly?: boolean - - /** - * Check whether to allow or disallow the node embedding while it's being - * translated. By default, all nodes can be embedded into all other nodes. - */ - validate: ( - this: Graph, - args: { - child: Node - parent: Node - childView: CellView - parentView: CellView - }, - ) => boolean - } - - /** - * Configure which highlighter to use (and with which options) for - * each type of interaction. - */ - export interface Highlighting { - /** - * The default highlighter to use (and options) when none is specified - */ - default: HighlightManager.Options - /** - * When a cell is dragged over another cell in embedding mode. - */ - embedding?: HighlightManager.Options | null - /** - * When showing all nodes to which a valid connection can be made. - */ - nodeAvailable?: HighlightManager.Options | null - /** - * When showing all magnets to which a valid connection can be made. - */ - magnetAvailable?: HighlightManager.Options | null - /** - * When a valid edge connection can be made to an node. - */ - magnetAdsorbed?: HighlightManager.Options | null - } -} - -export namespace Options { - export function get(options: Partial) { - const { grid, panning, mousewheel, embedding, ...others } = options - - // size - // ---- - const container = options.container - if (container != null) { - if (others.width == null) { - others.width = container.clientWidth - } - - if (others.height == null) { - others.height = container.clientHeight - } - } else { - throw new Error( - `Ensure the container of the graph is specified and valid`, - ) - } - - const result = ObjectExt.merge({}, defaults, others) as Options.Definition - - // grid - // ---- - const defaultGrid: GridManager.CommonOptions = { size: 10, visible: false } - if (typeof grid === 'number') { - result.grid = { size: grid, visible: false } - } else if (typeof grid === 'boolean') { - result.grid = { ...defaultGrid, visible: grid } - } else { - result.grid = { ...defaultGrid, ...grid } - } - - // booleas - // ------- - const booleas: (keyof Options.ManualBooleans)[] = [ - 'panning', - 'mousewheel', - 'embedding', - ] - - booleas.forEach((key) => { - const val = options[key] - if (typeof val === 'boolean') { - result[key].enabled = val - } else { - result[key] = { - ...result[key], - ...(val as any), - } - } - }) - - return result - } -} - -export namespace Options { - export interface OnPortRenderedArgs { - node: Node - port: PortManager.Port - container: Element - selectors?: Markup.Selectors - labelContainer?: Element - labelSelectors?: Markup.Selectors | null - contentContainer: Element - contentSelectors?: Markup.Selectors - } -} - -export namespace Options { - export const defaults: Partial = { - x: 0, - y: 0, - scaling: { - min: 0.01, - max: 16, - }, - grid: { - size: 10, - visible: false, - }, - background: false, - - panning: { - enabled: false, - eventTypes: ['leftMouseDown'], - }, - mousewheel: { - enabled: false, - factor: 1.2, - zoomAtMousePosition: true, - }, - - highlighting: { - default: { - name: 'stroke', - args: { - padding: 3, - }, - }, - nodeAvailable: { - name: 'className', - args: { - className: Config.prefix('available-node'), - }, - }, - magnetAvailable: { - name: 'className', - args: { - className: Config.prefix('available-magnet'), - }, - }, - }, - connecting: { - snap: false, - allowLoop: true, - allowNode: true, - allowEdge: false, - allowPort: true, - highlight: false, - - anchor: 'center', - edgeAnchor: 'ratio', - connectionPoint: 'boundary', - router: 'normal', - connector: 'normal', - - validateConnection(this: Graph, { type, sourceView, targetView }) { - const view = type === 'target' ? targetView : sourceView - return view != null - }, - - createEdge() { - return new StandardEdge() - }, - }, - translating: { - restrict: false, - }, - embedding: { - enabled: false, - findParent: 'bbox', - frontOnly: true, - validate: () => true, - }, - - moveThreshold: 0, - clickThreshold: 0, - magnetThreshold: 0, - preventDefaultDblClick: true, - preventDefaultMouseDown: false, - preventDefaultContextMenu: true, - preventDefaultBlankAction: true, - interacting: { - edgeLabelMovable: false, - }, - guard: () => false, - } -} diff --git a/packages/x6-next/src/graph/panning.ts b/packages/x6-next/src/graph/panning.ts deleted file mode 100644 index 191afc1657f..00000000000 --- a/packages/x6-next/src/graph/panning.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { ModifierKey, Dom } from '@antv/x6-common' -import { Base } from './base' - -export class PanningManager extends Base { - private panning: boolean - private clientX: number - private clientY: number - private mousewheelHandle: Dom.MouseWheelHandle - - protected get widgetOptions() { - return this.options.panning - } - - get pannable() { - return this.widgetOptions && this.widgetOptions.enabled === true - } - - protected init() { - this.startListening() - this.updateClassName() - } - - protected startListening() { - const eventTypes = this.widgetOptions.eventTypes - if (!eventTypes) { - return - } - if (eventTypes.includes('leftMouseDown')) { - this.graph.on('blank:mousedown', this.preparePanning, this) - this.graph.on('node:unhandled:mousedown', this.preparePanning, this) - this.graph.on('edge:unhandled:mousedown', this.preparePanning, this) - } - if (eventTypes.includes('rightMouseDown')) { - this.onRightMouseDown = this.onRightMouseDown.bind(this) - Dom.Event.on(this.graph.container, 'mousedown', this.onRightMouseDown) - } - if (eventTypes.includes('mouseWheel')) { - this.mousewheelHandle = new Dom.MouseWheelHandle( - this.graph.container, - this.onMouseWheel.bind(this), - this.allowMouseWheel.bind(this), - ) - this.mousewheelHandle.enable() - } - } - - protected stopListening() { - const eventTypes = this.widgetOptions.eventTypes - if (!eventTypes) { - return - } - if (eventTypes.includes('leftMouseDown')) { - this.graph.off('blank:mousedown', this.preparePanning, this) - this.graph.off('node:unhandled:mousedown', this.preparePanning, this) - this.graph.off('edge:unhandled:mousedown', this.preparePanning, this) - } - if (eventTypes.includes('rightMouseDown')) { - Dom.Event.off(this.graph.container, 'mousedown', this.onRightMouseDown) - } - if (eventTypes.includes('mouseWheel')) { - if (this.mousewheelHandle) { - this.mousewheelHandle.disable() - } - } - } - - protected preparePanning({ e }: { e: Dom.MouseDownEvent }) { - // todo 暂时删除 selection 的判断 - if (this.allowPanning(e, true)) { - this.startPanning(e) - } - } - - allowPanning(e: Dom.MouseDownEvent, strict?: boolean) { - return ( - this.pannable && - ModifierKey.isMatch(e, this.widgetOptions.modifiers, strict) - ) - } - - protected startPanning(evt: Dom.MouseDownEvent) { - const e = this.view.normalizeEvent(evt) - this.clientX = e.clientX - this.clientY = e.clientY - this.panning = true - this.updateClassName() - Dom.Event.on(document.body, { - 'mousemove.panning touchmove.panning': this.pan.bind(this), - 'mouseup.panning touchend.panning': this.stopPanning.bind(this), - 'mouseleave.panning': this.stopPanning.bind(this), - }) - Dom.Event.on(window as any, 'mouseup.panning', this.stopPanning.bind(this)) - } - - protected pan(evt: Dom.MouseMoveEvent) { - const e = this.view.normalizeEvent(evt) - const dx = e.clientX - this.clientX - const dy = e.clientY - this.clientY - this.clientX = e.clientX - this.clientY = e.clientY - this.graph.translateBy(dx, dy) - } - - // eslint-disable-next-line - protected stopPanning(e: Dom.MouseUpEvent) { - this.panning = false - this.updateClassName() - Dom.Event.off(document.body, '.panning') - Dom.Event.off(window as any, '.panning') - } - - protected updateClassName() { - const container = this.view.container - const panning = this.view.prefixClassName('graph-panning') - const pannable = this.view.prefixClassName('graph-pannable') - if (this.pannable) { - if (this.panning) { - Dom.addClass(container, panning) - Dom.removeClass(container, pannable) - } else { - Dom.removeClass(container, panning) - Dom.addClass(container, pannable) - } - } else { - Dom.removeClass(container, panning) - Dom.removeClass(container, pannable) - } - } - - protected onRightMouseDown(e: Dom.MouseDownEvent) { - if (e.button === 2 && this.allowPanning(e, true)) { - this.startPanning(e) - } - } - - protected allowMouseWheel(e: WheelEvent) { - return this.pannable && !e.ctrlKey - } - - protected onMouseWheel(e: WheelEvent, deltaX: number, deltaY: number) { - if (!e.ctrlKey) { - this.graph.translateBy(-deltaX, -deltaY) - } - } - - autoPanning(x: number, y: number) { - const buffer = 10 - const graphArea = this.graph.getGraphArea() - - let dx = 0 - let dy = 0 - if (x <= graphArea.left + buffer) { - dx = -buffer - } - - if (y <= graphArea.top + buffer) { - dy = -buffer - } - - if (x >= graphArea.right - buffer) { - dx = buffer - } - - if (y >= graphArea.bottom - buffer) { - dy = buffer - } - - if (dx !== 0 || dy !== 0) { - this.graph.translateBy(-dx, -dy) - } - } - - enablePanning() { - if (!this.pannable) { - this.widgetOptions.enabled = true - this.updateClassName() - } - } - - disablePanning() { - if (this.pannable) { - this.widgetOptions.enabled = false - this.updateClassName() - } - } - - @Base.dispose() - dispose() { - this.stopListening() - } -} - -export namespace PanningManager { - type EventType = 'leftMouseDown' | 'rightMouseDown' | 'mouseWheel' - export interface Options { - enabled?: boolean - modifiers?: string | ModifierKey[] | null - eventTypes?: EventType[] - } -} diff --git a/packages/x6-next/src/graph/transform.ts b/packages/x6-next/src/graph/transform.ts deleted file mode 100644 index d4d34a96f91..00000000000 --- a/packages/x6-next/src/graph/transform.ts +++ /dev/null @@ -1,603 +0,0 @@ -import { Dom, NumberExt } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { Base } from './base' -import { Util } from '../util' -import { Cell } from '../model' - -export class TransformManager extends Base { - protected viewportMatrix: DOMMatrix | null - - protected viewportTransformString: string | null - - protected get container() { - return this.graph.view.container - } - - protected get viewport() { - return this.graph.view.viewport - } - - protected get stage() { - return this.graph.view.stage - } - - protected init() { - this.resize() - } - - /** - * Returns the current transformation matrix of the graph. - */ - getMatrix() { - const transform = this.viewport.getAttribute('transform') - if (transform !== this.viewportTransformString) { - // `getCTM`: top-left relative to the SVG element - // `getScreenCTM`: top-left relative to the document - this.viewportMatrix = this.viewport.getCTM() - this.viewportTransformString = transform - } - - // Clone the cached current transformation matrix. - // If no matrix previously stored the identity matrix is returned. - return Dom.createSVGMatrix(this.viewportMatrix) - } - - /** - * Sets new transformation with the given `matrix` - */ - setMatrix(matrix: DOMMatrix | Dom.MatrixLike | null) { - const ctm = Dom.createSVGMatrix(matrix) - const transform = Dom.matrixToTransformString(ctm) - this.viewport.setAttribute('transform', transform) - this.viewportMatrix = ctm - this.viewportTransformString = transform - } - - resize(width?: number, height?: number) { - let w = width === undefined ? this.options.width : width - let h = height === undefined ? this.options.height : height - - this.options.width = w - this.options.height = h - - if (typeof w === 'number') { - w = Math.round(w) - } - if (typeof h === 'number') { - h = Math.round(h) - } - - this.container.style.width = w == null ? '' : `${w}px` - this.container.style.height = h == null ? '' : `${h}px` - - const size = this.getComputedSize() - this.graph.trigger('resize', { ...size }) - return this - } - - getComputedSize() { - let w = this.options.width - let h = this.options.height - if (!NumberExt.isNumber(w)) { - w = this.container.clientWidth - } - if (!NumberExt.isNumber(h)) { - h = this.container.clientHeight - } - return { width: w, height: h } - } - - getScale() { - return Dom.matrixToScale(this.getMatrix()) - } - - scale(sx: number, sy: number = sx, ox = 0, oy = 0) { - sx = this.clampScale(sx) // eslint-disable-line - sy = this.clampScale(sy) // eslint-disable-line - - if (ox || oy) { - const ts = this.getTranslation() - const tx = ts.tx - ox * (sx - 1) - const ty = ts.ty - oy * (sy - 1) - if (tx !== ts.tx || ty !== ts.ty) { - this.translate(tx, ty) - } - } - - const matrix = this.getMatrix() - matrix.a = sx - matrix.d = sy - - this.setMatrix(matrix) - this.graph.trigger('scale', { sx, sy, ox, oy }) - return this - } - - clampScale(scale: number) { - const range = this.graph.options.scaling - return NumberExt.clamp(scale, range.min || 0.01, range.max || 16) - } - - getZoom() { - return this.getScale().sx - } - - zoom(factor: number, options?: TransformManager.ZoomOptions) { - options = options || {} // eslint-disable-line - - let sx = factor - let sy = factor - const scale = this.getScale() - const clientSize = this.getComputedSize() - let cx = clientSize.width / 2 - let cy = clientSize.height / 2 - - if (!options.absolute) { - sx += scale.sx - sy += scale.sy - } - - if (options.scaleGrid) { - sx = Math.round(sx / options.scaleGrid) * options.scaleGrid - sy = Math.round(sy / options.scaleGrid) * options.scaleGrid - } - - if (options.maxScale) { - sx = Math.min(options.maxScale, sx) - sy = Math.min(options.maxScale, sy) - } - - if (options.minScale) { - sx = Math.max(options.minScale, sx) - sy = Math.max(options.minScale, sy) - } - - if (options.center) { - cx = options.center.x - cy = options.center.y - } - - sx = this.clampScale(sx) - sy = this.clampScale(sy) - - if (cx || cy) { - const ts = this.getTranslation() - const tx = cx - (cx - ts.tx) * (sx / scale.sx) - const ty = cy - (cy - ts.ty) * (sy / scale.sy) - if (tx !== ts.tx || ty !== ts.ty) { - this.translate(tx, ty) - } - } - - this.scale(sx, sy) - - return this - } - - getRotation() { - return Dom.matrixToRotation(this.getMatrix()) - } - - rotate(angle: number, cx?: number, cy?: number) { - if (cx == null || cy == null) { - const bbox = Util.getBBox(this.stage) - cx = bbox.width / 2 // eslint-disable-line - cy = bbox.height / 2 // eslint-disable-line - } - - const ctm = this.getMatrix() - .translate(cx, cy) - .rotate(angle) - .translate(-cx, -cy) - this.setMatrix(ctm) - return this - } - - getTranslation() { - return Dom.matrixToTranslation(this.getMatrix()) - } - - translate(tx: number, ty: number) { - const matrix = this.getMatrix() - matrix.e = tx || 0 - matrix.f = ty || 0 - this.setMatrix(matrix) - const ts = this.getTranslation() - this.options.x = ts.tx - this.options.y = ts.ty - this.graph.trigger('translate', { ...ts }) - return this - } - - setOrigin(ox?: number, oy?: number) { - return this.translate(ox || 0, oy || 0) - } - - fitToContent( - gridWidth?: number | TransformManager.FitToContentFullOptions, - gridHeight?: number, - padding?: NumberExt.SideOptions, - options?: TransformManager.FitToContentOptions, - ) { - if (typeof gridWidth === 'object') { - const opts = gridWidth - gridWidth = opts.gridWidth || 1 // eslint-disable-line - gridHeight = opts.gridHeight || 1 // eslint-disable-line - padding = opts.padding || 0 // eslint-disable-line - options = opts // eslint-disable-line - } else { - gridWidth = gridWidth || 1 // eslint-disable-line - gridHeight = gridHeight || 1 // eslint-disable-line - padding = padding || 0 // eslint-disable-line - if (options == null) { - options = {} // eslint-disable-line - } - } - - const paddings = NumberExt.normalizeSides(padding) - const border = options.border || 0 - const contentArea = options.contentArea - ? Rectangle.create(options.contentArea) - : this.getContentArea(options) - - if (border > 0) { - contentArea.inflate(border) - } - - const scale = this.getScale() - const translate = this.getTranslation() - const sx = scale.sx - const sy = scale.sy - - contentArea.x *= sx - contentArea.y *= sy - contentArea.width *= sx - contentArea.height *= sy - - let width = - Math.max(Math.ceil((contentArea.width + contentArea.x) / gridWidth), 1) * - gridWidth - - let height = - Math.max( - Math.ceil((contentArea.height + contentArea.y) / gridHeight), - 1, - ) * gridHeight - - let tx = 0 - let ty = 0 - - if ( - (options.allowNewOrigin === 'negative' && contentArea.x < 0) || - (options.allowNewOrigin === 'positive' && contentArea.x >= 0) || - options.allowNewOrigin === 'any' - ) { - tx = Math.ceil(-contentArea.x / gridWidth) * gridWidth - tx += paddings.left - width += tx - } - - if ( - (options.allowNewOrigin === 'negative' && contentArea.y < 0) || - (options.allowNewOrigin === 'positive' && contentArea.y >= 0) || - options.allowNewOrigin === 'any' - ) { - ty = Math.ceil(-contentArea.y / gridHeight) * gridHeight - ty += paddings.top - height += ty - } - - width += paddings.right - height += paddings.bottom - - // Make sure the resulting width and height are greater than minimum. - width = Math.max(width, options.minWidth || 0) - height = Math.max(height, options.minHeight || 0) - - // Make sure the resulting width and height are lesser than maximum. - width = Math.min(width, options.maxWidth || Number.MAX_SAFE_INTEGER) - height = Math.min(height, options.maxHeight || Number.MAX_SAFE_INTEGER) - - const size = this.getComputedSize() - const sizeChanged = width !== size.width || height !== size.height - const originChanged = tx !== translate.tx || ty !== translate.ty - - // Change the dimensions only if there is a size discrepency or an origin change - if (originChanged) { - this.translate(tx, ty) - } - - if (sizeChanged) { - this.resize(width, height) - } - - return new Rectangle(-tx / sx, -ty / sy, width / sx, height / sy) - } - - scaleContentToFit(options: TransformManager.ScaleContentToFitOptions = {}) { - this.scaleContentToFitImpl(options) - } - - scaleContentToFitImpl( - options: TransformManager.ScaleContentToFitOptions = {}, - translate = true, - ) { - let contentBBox - let contentLocalOrigin - if (options.contentArea) { - const contentArea = options.contentArea - contentBBox = this.graph.localToGraph(contentArea) - contentLocalOrigin = Point.create(contentArea) - } else { - contentBBox = this.getContentBBox(options) - contentLocalOrigin = this.graph.graphToLocal(contentBBox) - } - - if (!contentBBox.width || !contentBBox.height) { - return - } - - const padding = NumberExt.normalizeSides(options.padding) - const minScale = options.minScale || 0 - const maxScale = options.maxScale || Number.MAX_SAFE_INTEGER - const minScaleX = options.minScaleX || minScale - const maxScaleX = options.maxScaleX || maxScale - const minScaleY = options.minScaleY || minScale - const maxScaleY = options.maxScaleY || maxScale - - let fittingBox - if (options.viewportArea) { - fittingBox = options.viewportArea - } else { - const computedSize = this.getComputedSize() - const currentTranslate = this.getTranslation() - fittingBox = { - x: currentTranslate.tx, - y: currentTranslate.ty, - width: computedSize.width, - height: computedSize.height, - } - } - - fittingBox = Rectangle.create(fittingBox).moveAndExpand({ - x: padding.left, - y: padding.top, - width: -padding.left - padding.right, - height: -padding.top - padding.bottom, - }) - - const currentScale = this.getScale() - - let newSX = (fittingBox.width / contentBBox.width) * currentScale.sx - let newSY = (fittingBox.height / contentBBox.height) * currentScale.sy - - if (options.preserveAspectRatio !== false) { - newSX = newSY = Math.min(newSX, newSY) - } - - // snap scale to a grid - const gridSize = options.scaleGrid - if (gridSize) { - newSX = gridSize * Math.floor(newSX / gridSize) - newSY = gridSize * Math.floor(newSY / gridSize) - } - - // scale min/max boundaries - newSX = NumberExt.clamp(newSX, minScaleX, maxScaleX) - newSY = NumberExt.clamp(newSY, minScaleY, maxScaleY) - - this.scale(newSX, newSY) - - if (translate) { - const origin = this.options - const newOX = fittingBox.x - contentLocalOrigin.x * newSX - origin.x - const newOY = fittingBox.y - contentLocalOrigin.y * newSY - origin.y - this.translate(newOX, newOY) - } - } - - getContentArea(options: TransformManager.GetContentAreaOptions = {}) { - if (options.useCellGeometry) { - return this.model.getAllCellsBBox() || new Rectangle() - } - - return Util.getBBox(this.stage) - } - - getContentBBox(options: TransformManager.GetContentAreaOptions = {}) { - return this.graph.localToGraph(this.getContentArea(options)) - } - - getGraphArea() { - const rect = Rectangle.fromSize(this.getComputedSize()) - return this.graph.graphToLocal(rect) - } - - zoomToRect( - rect: Rectangle.RectangleLike, - options: TransformManager.ScaleContentToFitOptions = {}, - ) { - const area = Rectangle.create(rect) - const graph = this.graph - - options.contentArea = area - if (options.viewportArea == null) { - options.viewportArea = { - x: graph.options.x, - y: graph.options.y, - width: this.options.width, - height: this.options.height, - } - } - - this.scaleContentToFitImpl(options, false) - const center = area.getCenter() - this.centerPoint(center.x, center.y) - - return this - } - - zoomToFit( - options: TransformManager.GetContentAreaOptions & - TransformManager.ScaleContentToFitOptions = {}, - ) { - return this.zoomToRect(this.getContentArea(options), options) - } - - centerPoint(x?: number, y?: number) { - const clientSize = this.getComputedSize() - const scale = this.getScale() - const ts = this.getTranslation() - const cx = clientSize.width / 2 - const cy = clientSize.height / 2 - - x = typeof x === 'number' ? x : cx // eslint-disable-line - y = typeof y === 'number' ? y : cy // eslint-disable-line - - x = cx - x * scale.sx // eslint-disable-line - y = cy - y * scale.sy // eslint-disable-line - - if (ts.tx !== x || ts.ty !== y) { - this.translate(x, y) - } - } - - centerContent(options?: TransformManager.GetContentAreaOptions) { - const rect = this.graph.getContentArea(options) - const center = rect.getCenter() - this.centerPoint(center.x, center.y) - } - - centerCell(cell: Cell) { - return this.positionCell(cell, 'center') - } - - positionPoint( - point: Point.PointLike, - x: number | string, - y: number | string, - ) { - const clientSize = this.getComputedSize() - - // eslint-disable-next-line - x = NumberExt.normalizePercentage(x, Math.max(0, clientSize.width)) - if (x < 0) { - x = clientSize.width + x // eslint-disable-line - } - - // eslint-disable-next-line - y = NumberExt.normalizePercentage(y, Math.max(0, clientSize.height)) - if (y < 0) { - y = clientSize.height + y // eslint-disable-line - } - - const ts = this.getTranslation() - const scale = this.getScale() - const dx = x - point.x * scale.sx - const dy = y - point.y * scale.sy - - if (ts.tx !== dx || ts.ty !== dy) { - this.translate(dx, dy) - } - } - - positionRect(rect: Rectangle.RectangleLike, pos: TransformManager.Direction) { - const bbox = Rectangle.create(rect) - switch (pos) { - case 'center': - return this.positionPoint(bbox.getCenter(), '50%', '50%') - case 'top': - return this.positionPoint(bbox.getTopCenter(), '50%', 0) - case 'top-right': - return this.positionPoint(bbox.getTopRight(), '100%', 0) - case 'right': - return this.positionPoint(bbox.getRightMiddle(), '100%', '50%') - case 'bottom-right': - return this.positionPoint(bbox.getBottomRight(), '100%', '100%') - case 'bottom': - return this.positionPoint(bbox.getBottomCenter(), '50%', '100%') - case 'bottom-left': - return this.positionPoint(bbox.getBottomLeft(), 0, '100%') - case 'left': - return this.positionPoint(bbox.getLeftMiddle(), 0, '50%') - case 'top-left': - return this.positionPoint(bbox.getTopLeft(), 0, 0) - default: - return this - } - } - - positionCell(cell: Cell, pos: TransformManager.Direction) { - const bbox = cell.getBBox() - return this.positionRect(bbox, pos) - } - - positionContent( - pos: TransformManager.Direction, - options?: TransformManager.GetContentAreaOptions, - ) { - const rect = this.graph.getContentArea(options) - return this.positionRect(rect, pos) - } -} - -export namespace TransformManager { - export interface FitToContentOptions extends GetContentAreaOptions { - minWidth?: number - minHeight?: number - maxWidth?: number - maxHeight?: number - contentArea?: Rectangle | Rectangle.RectangleLike - border?: number - allowNewOrigin?: 'negative' | 'positive' | 'any' - } - - export interface FitToContentFullOptions extends FitToContentOptions { - gridWidth?: number - gridHeight?: number - padding?: NumberExt.SideOptions - } - - export interface ScaleContentToFitOptions extends GetContentAreaOptions { - padding?: NumberExt.SideOptions - minScale?: number - maxScale?: number - minScaleX?: number - minScaleY?: number - maxScaleX?: number - maxScaleY?: number - scaleGrid?: number - contentArea?: Rectangle.RectangleLike - viewportArea?: Rectangle.RectangleLike - preserveAspectRatio?: boolean - } - - export interface GetContentAreaOptions { - useCellGeometry?: boolean - } - - export interface ZoomOptions { - absolute?: boolean - minScale?: number - maxScale?: number - scaleGrid?: number - center?: Point.PointLike - } - - export type Direction = - | 'center' - | 'top' - | 'top-right' - | 'top-left' - | 'right' - | 'bottom-right' - | 'bottom' - | 'bottom-left' - | 'left' - - export interface CenterOptions { - padding?: NumberExt.SideOptions - } - - export type PositionContentOptions = GetContentAreaOptions & CenterOptions -} diff --git a/packages/x6-next/src/graph/view.ts b/packages/x6-next/src/graph/view.ts deleted file mode 100644 index fce74233254..00000000000 --- a/packages/x6-next/src/graph/view.ts +++ /dev/null @@ -1,644 +0,0 @@ -import { Dom, FunctionExt } from '@antv/x6-common' -import { Cell } from '../model' -import { Config } from '../config' -import { View, Markup, CellView } from '../view' -import { Graph } from '../graph' - -export class GraphView extends View { - public readonly container: HTMLElement - public readonly background: HTMLDivElement - public readonly grid: HTMLDivElement - public readonly svg: SVGSVGElement - public readonly defs: SVGDefsElement - public readonly viewport: SVGGElement - public readonly primer: SVGGElement - public readonly stage: SVGGElement - public readonly decorator: SVGGElement - public readonly overlay: SVGGElement - - private restore: () => void - - protected get options() { - return this.graph.options - } - - constructor(protected readonly graph: Graph) { - super() - - const { selectors, fragment } = Markup.parseJSONMarkup(GraphView.markup) - this.background = selectors.background as HTMLDivElement - this.grid = selectors.grid as HTMLDivElement - this.svg = selectors.svg as SVGSVGElement - this.defs = selectors.defs as SVGDefsElement - this.viewport = selectors.viewport as SVGGElement - this.primer = selectors.primer as SVGGElement - this.stage = selectors.stage as SVGGElement - this.decorator = selectors.decorator as SVGGElement - this.overlay = selectors.overlay as SVGGElement - this.container = this.options.container - this.restore = GraphView.snapshoot(this.container) - - Dom.addClass(this.container, this.prefixClassName('graph')) - Dom.append(this.container, fragment) - - this.delegateEvents() - } - - delegateEvents() { - const ctor = this.constructor as typeof GraphView - super.delegateEvents(ctor.events) - return this - } - - /** - * Guard the specified event. If the event is not interesting, it - * returns `true`, otherwise returns `false`. - */ - guard(e: Dom.EventObject, view?: CellView | null) { - // handled as `contextmenu` type - if (e.type === 'mousedown' && e.button === 2) { - return true - } - - if (this.options.guard && this.options.guard(e, view)) { - return true - } - - if (e.data && e.data.guarded !== undefined) { - return e.data.guarded - } - - if (view && view.cell && Cell.isCell(view.cell)) { - return false - } - - if ( - this.svg === e.target || - this.container === e.target || - this.svg.contains(e.target) - ) { - return false - } - - return true - } - - protected findView(elem: Element) { - return this.graph.findViewByElem(elem) - } - - protected onDblClick(evt: Dom.DoubleClickEvent) { - if (this.options.preventDefaultDblClick) { - evt.preventDefault() - } - - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - - if (this.guard(e, view)) { - return - } - - const localPoint = this.graph.snapToGrid(e.clientX, e.clientY) - - if (view) { - view.onDblClick(e, localPoint.x, localPoint.y) - } else { - this.graph.trigger('blank:dblclick', { - e, - x: localPoint.x, - y: localPoint.y, - }) - } - } - - protected onClick(evt: Dom.ClickEvent) { - if (this.getMouseMovedCount(evt) <= this.options.clickThreshold) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - const localPoint = this.graph.snapToGrid(e.clientX, e.clientY) - if (view) { - view.onClick(e, localPoint.x, localPoint.y) - } else { - this.graph.trigger('blank:click', { - e, - x: localPoint.x, - y: localPoint.y, - }) - } - } - } - - protected onContextMenu(evt: Dom.ContextMenuEvent) { - if (this.options.preventDefaultContextMenu) { - evt.preventDefault() - } - - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - const localPoint = this.graph.snapToGrid(e.clientX, e.clientY) - - if (view) { - view.onContextMenu(e, localPoint.x, localPoint.y) - } else { - this.graph.trigger('blank:contextmenu', { - e, - x: localPoint.x, - y: localPoint.y, - }) - } - } - - delegateDragEvents(e: Dom.MouseDownEvent, view: CellView | null) { - if (e.data == null) { - e.data = {} - } - this.setEventData(e, { - currentView: view || null, - mouseMovedCount: 0, - startPosition: { - x: e.clientX, - y: e.clientY, - }, - }) - const ctor = this.constructor as typeof GraphView - this.delegateDocumentEvents(ctor.documentEvents, e.data) - this.undelegateEvents() - } - - getMouseMovedCount(e: Dom.EventObject) { - const data = this.getEventData(e) - return data.mouseMovedCount || 0 - } - - protected onMouseDown(evt: Dom.MouseDownEvent) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - if (this.options.preventDefaultMouseDown) { - e.preventDefault() - } - - const localPoint = this.graph.snapToGrid(e.clientX, e.clientY) - - if (view) { - view.onMouseDown(e, localPoint.x, localPoint.y) - } else { - if (this.options.preventDefaultBlankAction) { - e.preventDefault() - } - - this.graph.trigger('blank:mousedown', { - e, - x: localPoint.x, - y: localPoint.y, - }) - } - - this.delegateDragEvents(e, view) - } - - protected onMouseMove(evt: Dom.MouseMoveEvent) { - const data = this.getEventData(evt) - - const startPosition = data.startPosition - if ( - startPosition && - startPosition.x === evt.clientX && - startPosition.y === evt.clientY - ) { - return - } - - if (data.mouseMovedCount == null) { - data.mouseMovedCount = 0 - } - data.mouseMovedCount += 1 - const mouseMovedCount = data.mouseMovedCount - if (mouseMovedCount <= this.options.moveThreshold) { - return - } - - const e = this.normalizeEvent(evt) - const localPoint = this.graph.snapToGrid(e.clientX, e.clientY) - - const view = data.currentView - if (view) { - view.onMouseMove(e, localPoint.x, localPoint.y) - } else { - this.graph.trigger('blank:mousemove', { - e, - x: localPoint.x, - y: localPoint.y, - }) - } - - this.setEventData(e, data) - } - - protected onMouseUp(e: Dom.MouseUpEvent) { - this.undelegateDocumentEvents() - - const normalized = this.normalizeEvent(e) - const localPoint = this.graph.snapToGrid( - normalized.clientX, - normalized.clientY, - ) - const data = this.getEventData(e) - const view = data.currentView - if (view) { - view.onMouseUp(normalized, localPoint.x, localPoint.y) - } else { - this.graph.trigger('blank:mouseup', { - e: normalized, - x: localPoint.x, - y: localPoint.y, - }) - } - - if (!e.isPropagationStopped()) { - const ev = new Dom.EventObject(e as any, { - type: 'click', - data: e.data, - }) as Dom.ClickEvent - this.onClick(ev) - } - - e.stopImmediatePropagation() - - this.delegateEvents() - } - - protected onMouseOver(evt: Dom.MouseOverEvent) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - if (view) { - view.onMouseOver(e) - } else { - // prevent border of paper from triggering this - if (this.container === e.target) { - return - } - this.graph.trigger('blank:mouseover', { e }) - } - } - - protected onMouseOut(evt: Dom.MouseOutEvent) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - - if (this.guard(e, view)) { - return - } - - if (view) { - view.onMouseOut(e) - } else { - if (this.container === e.target) { - return - } - this.graph.trigger('blank:mouseout', { e }) - } - } - - protected onMouseEnter(evt: Dom.MouseEnterEvent) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - const relatedView = this.graph.findViewByElem(e.relatedTarget as Element) - if (view) { - if (relatedView === view) { - // mouse moved from tool to view - return - } - view.onMouseEnter(e) - } else { - if (relatedView) { - return - } - this.graph.trigger('graph:mouseenter', { e }) - } - } - - protected onMouseLeave(evt: Dom.MouseLeaveEvent) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - const relatedView = this.graph.findViewByElem(e.relatedTarget as Element) - - if (view) { - if (relatedView === view) { - // mouse moved from view to tool - return - } - view.onMouseLeave(e) - } else { - if (relatedView) { - return - } - this.graph.trigger('graph:mouseleave', { e }) - } - } - - protected onMouseWheel(evt: Dom.EventObject) { - const e = this.normalizeEvent(evt) - const view = this.findView(e.target) - if (this.guard(e, view)) { - return - } - - const originalEvent = e.originalEvent as WheelEvent - const localPoint = this.graph.snapToGrid( - originalEvent.clientX, - originalEvent.clientY, - ) - const delta = Math.max( - -1, - Math.min(1, (originalEvent as any).wheelDelta || -originalEvent.detail), - ) - - if (view) { - view.onMouseWheel(e, localPoint.x, localPoint.y, delta) - } else { - this.graph.trigger('blank:mousewheel', { - e, - delta, - x: localPoint.x, - y: localPoint.y, - }) - } - } - - protected onCustomEvent(evt: Dom.MouseDownEvent) { - const elem = evt.currentTarget - const event = elem.getAttribute('event') || elem.getAttribute('data-event') - if (event) { - const view = this.findView(elem) - if (view) { - const e = this.normalizeEvent(evt) - if (this.guard(e, view)) { - return - } - - const localPoint = this.graph.snapToGrid( - e.clientX as number, - e.clientY as number, - ) - view.onCustomEvent(e, event, localPoint.x, localPoint.y) - } - } - } - - protected handleMagnetEvent( - evt: T, - handler: ( - this: Graph, - view: CellView, - e: T, - magnet: Element, - x: number, - y: number, - ) => void, - ) { - const magnetElem = evt.currentTarget - const magnetValue = magnetElem.getAttribute('magnet') as string - if (magnetValue && magnetValue.toLowerCase() !== 'false') { - const view = this.findView(magnetElem) - if (view) { - const e = this.normalizeEvent(evt) - if (this.guard(e, view)) { - return - } - const localPoint = this.graph.snapToGrid( - e.clientX as number, - e.clientY as number, - ) - FunctionExt.call( - handler, - this.graph, - view, - e, - magnetElem, - localPoint.x, - localPoint.y, - ) - } - } - } - - protected onMagnetMouseDown(e: Dom.MouseDownEvent) { - this.handleMagnetEvent(e, (view, e, magnet, x, y) => { - view.onMagnetMouseDown(e, magnet, x, y) - }) - } - - protected onMagnetDblClick(e: Dom.DoubleClickEvent) { - this.handleMagnetEvent(e, (view, e, magnet, x, y) => { - view.onMagnetDblClick(e, magnet, x, y) - }) - } - - protected onMagnetContextMenu(e: Dom.ContextMenuEvent) { - if (this.options.preventDefaultContextMenu) { - e.preventDefault() - } - this.handleMagnetEvent(e, (view, e, magnet, x, y) => { - view.onMagnetContextMenu(e, magnet, x, y) - }) - } - - protected onLabelMouseDown(evt: Dom.MouseDownEvent) { - const labelNode = evt.currentTarget - const view = this.findView(labelNode) - if (view) { - const e = this.normalizeEvent(evt) - if (this.guard(e, view)) { - return - } - - const localPoint = this.graph.snapToGrid(e.clientX, e.clientY) - view.onLabelMouseDown(e, localPoint.x, localPoint.y) - } - } - - protected onImageDragStart() { - // This is the only way to prevent image dragging in Firefox that works. - // Setting -moz-user-select: none, draggable="false" attribute or - // user-drag: none didn't help. - return false - } - - @View.dispose() - dispose() { - this.undelegateEvents() - this.undelegateDocumentEvents() - this.restore() - this.restore = () => {} - } -} - -export namespace GraphView { - export type SortType = 'none' | 'approx' | 'exact' -} - -export namespace GraphView { - const prefixCls = `${Config.prefixCls}-graph` - - export const markup: Markup.JSONMarkup[] = [ - { - ns: Dom.ns.xhtml, - tagName: 'div', - selector: 'background', - className: `${prefixCls}-background`, - }, - { - ns: Dom.ns.xhtml, - tagName: 'div', - selector: 'grid', - className: `${prefixCls}-grid`, - }, - { - ns: Dom.ns.svg, - tagName: 'svg', - selector: 'svg', - className: `${prefixCls}-svg`, - attrs: { - width: '100%', - height: '100%', - 'xmlns:xlink': Dom.ns.xlink, - }, - children: [ - { - tagName: 'defs', - selector: 'defs', - }, - { - tagName: 'g', - selector: 'viewport', - className: `${prefixCls}-svg-viewport`, - children: [ - { - tagName: 'g', - selector: 'primer', - className: `${prefixCls}-svg-primer`, - }, - { - tagName: 'g', - selector: 'stage', - className: `${prefixCls}-svg-stage`, - }, - { - tagName: 'g', - selector: 'decorator', - className: `${prefixCls}-svg-decorator`, - }, - { - tagName: 'g', - selector: 'overlay', - className: `${prefixCls}-svg-overlay`, - }, - ], - }, - ], - }, - ] - - export function snapshoot(elem: Element) { - const cloned = elem.cloneNode() as Element - elem.childNodes.forEach((child) => cloned.appendChild(child)) - - return () => { - // remove all children - Dom.empty(elem) - - // remove all attributes - while (elem.attributes.length > 0) { - elem.removeAttribute(elem.attributes[0].name) - } - - // restore attributes - for (let i = 0, l = cloned.attributes.length; i < l; i += 1) { - const attr = cloned.attributes[i] - elem.setAttribute(attr.name, attr.value) - } - - // restore children - cloned.childNodes.forEach((child) => elem.appendChild(child)) - } - } -} - -export namespace GraphView { - const prefixCls = Config.prefixCls - - export const events = { - dblclick: 'onDblClick', - contextmenu: 'onContextMenu', - touchstart: 'onMouseDown', - mousedown: 'onMouseDown', - mouseover: 'onMouseOver', - mouseout: 'onMouseOut', - mouseenter: 'onMouseEnter', - mouseleave: 'onMouseLeave', - mousewheel: 'onMouseWheel', - DOMMouseScroll: 'onMouseWheel', - [`mouseenter .${prefixCls}-cell`]: 'onMouseEnter', - [`mouseleave .${prefixCls}-cell`]: 'onMouseLeave', - [`mouseenter .${prefixCls}-cell-tools`]: 'onMouseEnter', - [`mouseleave .${prefixCls}-cell-tools`]: 'onMouseLeave', - [`mousedown .${prefixCls}-cell [event]`]: 'onCustomEvent', - [`touchstart .${prefixCls}-cell [event]`]: 'onCustomEvent', - [`mousedown .${prefixCls}-cell [data-event]`]: 'onCustomEvent', - [`touchstart .${prefixCls}-cell [data-event]`]: 'onCustomEvent', - [`dblclick .${prefixCls}-cell [magnet]`]: 'onMagnetDblClick', - [`contextmenu .${prefixCls}-cell [magnet]`]: 'onMagnetContextMenu', - [`mousedown .${prefixCls}-cell [magnet]`]: 'onMagnetMouseDown', - [`touchstart .${prefixCls}-cell [magnet]`]: 'onMagnetMouseDown', - [`dblclick .${prefixCls}-cell [data-magnet]`]: 'onMagnetDblClick', - [`contextmenu .${prefixCls}-cell [data-magnet]`]: 'onMagnetContextMenu', - [`mousedown .${prefixCls}-cell [data-magnet]`]: 'onMagnetMouseDown', - [`touchstart .${prefixCls}-cell [data-magnet]`]: 'onMagnetMouseDown', - [`dragstart .${prefixCls}-cell image`]: 'onImageDragStart', - [`mousedown .${prefixCls}-edge .${prefixCls}-edge-label`]: - 'onLabelMouseDown', - [`touchstart .${prefixCls}-edge .${prefixCls}-edge-label`]: - 'onLabelMouseDown', - } - - export const documentEvents = { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - } -} - -namespace EventData { - export interface Moving { - mouseMovedCount?: number - startPosition?: { x: number; y: number } - currentView?: CellView | null - } -} diff --git a/packages/x6-next/src/index.less b/packages/x6-next/src/index.less deleted file mode 100644 index f3259a1a009..00000000000 --- a/packages/x6-next/src/index.less +++ /dev/null @@ -1,4 +0,0 @@ -@import './style/index'; - -// tools -@import './registry/tool/editor'; diff --git a/packages/x6-next/src/index.ts b/packages/x6-next/src/index.ts deleted file mode 100644 index 95db87f8c63..00000000000 --- a/packages/x6-next/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as Shape from './shape' -import * as Registry from './registry' - -export * from './model' -export * from './view' -export * from './graph' -export * from './config' -export * from './util' - -export { Shape, Registry } diff --git a/packages/x6-next/src/model/animation.ts b/packages/x6-next/src/model/animation.ts deleted file mode 100644 index 4d126aa196e..00000000000 --- a/packages/x6-next/src/model/animation.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { ObjectExt, Dom, KeyValue, Timing, Interp } from '@antv/x6-common' -import { Cell } from './cell' - -export class Animation { - protected readonly ids: { [path: string]: number } = {} - protected readonly cache: { - [path: string]: { - startValue: any - targetValue: any - options: Animation.StartOptions - } - } = {} - - constructor(protected readonly cell: Cell) {} - - get() { - return Object.keys(this.ids) - } - - start( - path: string | string[], - targetValue: T, - options: Animation.StartOptions = {}, - delim = '/', - ): () => void { - const startValue = this.cell.getPropByPath(path) - const localOptions = ObjectExt.defaults(options, Animation.defaultOptions) - const timing = this.getTiming(localOptions.timing) - const interpolate = this.getInterp( - localOptions.interp, - startValue, - targetValue, - ) - - let startTime = 0 - const key = Array.isArray(path) ? path.join(delim) : path - const paths = Array.isArray(path) ? path : path.split(delim) - const iterate = () => { - const now = new Date().getTime() - if (startTime === 0) { - startTime = now - } - - const elaspe = now - startTime - let progress = elaspe / localOptions.duration - if (progress < 1) { - this.ids[key] = Dom.requestAnimationFrame(iterate) - } else { - progress = 1 - } - - const currentValue = interpolate(timing(progress)) as T - this.cell.setPropByPath(paths, currentValue) - - if (options.progress) { - options.progress({ progress, currentValue, ...this.getArgs(key) }) - } - - if (progress === 1) { - this.cell.notify('transition:complete', this.getArgs(key)) - options.complete && options.complete(this.getArgs(key)) - - this.cell.notify('transition:finish', this.getArgs(key)) - options.finish && options.finish(this.getArgs(key)) - this.clean(key) - } - } - - setTimeout(() => { - this.stop(path, undefined, delim) - this.cache[key] = { startValue, targetValue, options: localOptions } - this.ids[key] = Dom.requestAnimationFrame(iterate) - - this.cell.notify('transition:start', this.getArgs(key)) - options.start && options.start(this.getArgs(key)) - }, options.delay) - - return this.stop.bind(this, path, delim, options) - } - - stop( - path: string | string[], - options: Animation.StopOptions = {}, - delim = '/', - ) { - const paths = Array.isArray(path) ? path : path.split(delim) - Object.keys(this.ids) - .filter((key) => - ObjectExt.isEqual(paths, key.split(delim).slice(0, paths.length)), - ) - .forEach((key) => { - Dom.cancelAnimationFrame(this.ids[key]) - const data = this.cache[key] - const commonArgs = this.getArgs(key) - const localOptions = { ...data.options, ...options } - const jumpedToEnd = localOptions.jumpedToEnd - if (jumpedToEnd && data.targetValue != null) { - this.cell.setPropByPath(key, data.targetValue) - - this.cell.notify('transition:end', { ...commonArgs }) - this.cell.notify('transition:complete', { ...commonArgs }) - localOptions.complete && localOptions.complete({ ...commonArgs }) - } - - const stopArgs = { jumpedToEnd, ...commonArgs } - this.cell.notify('transition:stop', { ...stopArgs }) - localOptions.stop && localOptions.stop({ ...stopArgs }) - - this.cell.notify('transition:finish', { ...commonArgs }) - localOptions.finish && localOptions.finish({ ...commonArgs }) - - this.clean(key) - }) - - return this - } - - private clean(key: string) { - delete this.ids[key] - delete this.cache[key] - } - - private getTiming(timing: Timing.Names | Timing.Definition) { - return typeof timing === 'string' ? Timing[timing] : timing - } - - private getInterp( - interp: Interp.Definition | undefined, - startValue: T, - targetValue: T, - ) { - if (interp) { - return interp(startValue, targetValue) - } - - if (typeof targetValue === 'number') { - return Interp.number(startValue as number, targetValue) - } - - if (typeof targetValue === 'string') { - if (targetValue[0] === '#') { - return Interp.color(startValue as string, targetValue) - } - - return Interp.unit(startValue as string, targetValue) - } - - return Interp.object( - startValue as KeyValue, - targetValue as KeyValue, - ) - } - - private getArgs( - key: string, - ): Animation.CallbackArgs { - const data = this.cache[key] - return { - path: key, - startValue: data.startValue, - targetValue: data.targetValue, - cell: this.cell, - } - } -} - -export namespace Animation { - export interface BaseOptions { - delay: number - duration: number - timing: Timing.Names | Timing.Definition - } - - export type TargetValue = string | number | KeyValue - - export interface CallbackArgs { - cell: Cell - path: string - startValue: T - targetValue: T - } - - export interface ProgressArgs extends CallbackArgs { - progress: number - currentValue: T - } - - export interface StopArgs extends CallbackArgs { - jumpedToEnd?: boolean - } - - export interface StartOptions - extends Partial, - StopOptions { - interp?: Interp.Definition - /** - * A function to call when the animation begins. - */ - start?: (options: CallbackArgs) => void - /** - * A function to be called after each step of the animation, only once per - * animated element regardless of the number of animated properties. - */ - progress?: (options: ProgressArgs) => void - } - - export interface StopOptions { - /** - * A Boolean indicating whether to complete the animation immediately. - * Defaults to `false`. - */ - jumpedToEnd?: boolean - /** - * A function that is called once the animation completes. - */ - complete?: (options: CallbackArgs) => void - /** - * A function to be called when the animation stops. - */ - stop?: (options: StopArgs) => void - /** - * A function to be called when the animation completes or stops. - */ - finish?: (options: CallbackArgs) => void - } - - export const defaultOptions: BaseOptions = { - delay: 10, - duration: 100, - timing: 'linear', - } -} diff --git a/packages/x6-next/src/model/cell.ts b/packages/x6-next/src/model/cell.ts deleted file mode 100644 index 8f82e32c545..00000000000 --- a/packages/x6-next/src/model/cell.ts +++ /dev/null @@ -1,1865 +0,0 @@ -/* eslint-disable no-underscore-dangle */ - -import { - ArrayExt, - StringExt, - ObjectExt, - FunctionExt, - KeyValue, - Size, - Basecoat, -} from '@antv/x6-common' -import { Rectangle, Point } from '@antv/x6-geometry' -import { NonUndefined } from 'utility-types' -import { Attr } from '../registry' -import { Model } from './model' -import { PortManager } from './port' -import { Store } from './store' -import { Edge } from './edge' -import { Animation } from './animation' -import { CellView, Markup } from '../view' -import { Node } from './node' -import { Graph } from '../graph' - -export class Cell< - Properties extends Cell.Properties = Cell.Properties, -> extends Basecoat { - // #region static - - protected static markup: Markup - protected static defaults: Cell.Defaults = {} - protected static attrHooks: Attr.Definitions = {} - protected static propHooks: Cell.PropHook[] = [] - - public static config(presets: C) { - const { markup, propHooks, attrHooks, ...others } = presets - - if (markup != null) { - this.markup = markup - } - - if (propHooks) { - this.propHooks = this.propHooks.slice() - if (Array.isArray(propHooks)) { - this.propHooks.push(...propHooks) - } else if (typeof propHooks === 'function') { - this.propHooks.push(propHooks) - } else { - Object.keys(propHooks).forEach((name) => { - const hook = propHooks[name] - if (typeof hook === 'function') { - this.propHooks.push(hook) - } - }) - } - } - - if (attrHooks) { - this.attrHooks = { ...this.attrHooks, ...attrHooks } - } - - this.defaults = ObjectExt.merge({}, this.defaults, others) - } - - public static getMarkup() { - return this.markup - } - - public static getDefaults( - raw?: boolean, - ): T { - return (raw ? this.defaults : ObjectExt.cloneDeep(this.defaults)) as T - } - - public static getAttrHooks() { - return this.attrHooks - } - - public static applyPropHooks( - cell: Cell, - metadata: Cell.Metadata, - ): Cell.Metadata { - return this.propHooks.reduce((memo, hook) => { - return hook ? FunctionExt.call(hook, cell, memo) : memo - }, metadata) - } - - // #endregion - - protected get [Symbol.toStringTag]() { - return Cell.toStringTag - } - - public readonly id: string - protected readonly store: Store - protected readonly animation: Animation - protected _model: Model | null // eslint-disable-line - protected _parent: Cell | null // eslint-disable-line - protected _children: Cell[] | null // eslint-disable-line - - constructor(metadata: Cell.Metadata = {}) { - super() - - const ctor = this.constructor as typeof Cell - const defaults = ctor.getDefaults(true) - const props = ObjectExt.merge( - {}, - this.preprocess(defaults), - this.preprocess(metadata), - ) - - this.id = props.id || StringExt.uuid() - this.store = new Store(props) - this.animation = new Animation(this) - this.setup() - this.init() - this.postprocess(metadata) - } - - init() {} - - // #region model - - get model() { - return this._model - } - - set model(model: Model | null) { - if (this._model !== model) { - this._model = model - } - } - - // #endregion - - protected preprocess( - metadata: Cell.Metadata, - ignoreIdCheck?: boolean, - ): Properties { - const id = metadata.id - const ctor = this.constructor as typeof Cell - const props = ctor.applyPropHooks(this, metadata) - - if (id == null && ignoreIdCheck !== true) { - props.id = StringExt.uuid() - } - - return props as Properties - } - - protected postprocess(metadata: Cell.Metadata) {} // eslint-disable-line - - protected setup() { - this.store.on('change:*', (metadata) => { - const { key, current, previous, options } = metadata - - this.notify('change:*', { - key, - options, - current, - previous, - cell: this, - }) - - this.notify(`change:${key}` as keyof Cell.EventArgs, { - options, - current, - previous, - cell: this, - }) - - const type = key as Edge.TerminalType - if (type === 'source' || type === 'target') { - this.notify(`change:terminal`, { - type, - current, - previous, - options, - cell: this, - }) - } - }) - - this.store.on('changed', ({ options }) => - this.notify('changed', { options, cell: this }), - ) - } - - notify( - name: Key, - args: Cell.EventArgs[Key], - ): this - notify(name: Exclude, args: any): this - notify( - name: Key, - args: Cell.EventArgs[Key], - ) { - this.trigger(name, args) - const model = this.model - if (model) { - model.notify(`cell:${name}`, args) - if (this.isNode()) { - model.notify(`node:${name}`, { ...args, node: this }) - } else if (this.isEdge()) { - model.notify(`edge:${name}`, { ...args, edge: this }) - } - } - return this - } - - isNode(): this is Node { - return false - } - - isEdge(): this is Edge { - return false - } - - isSameStore(cell: Cell) { - return this.store === cell.store - } - - get view() { - return this.store.get('view') - } - - get shape() { - return this.store.get('shape', '') - } - - // #region get/set - - getProp(): Properties - getProp(key: K): Properties[K] - getProp( - key: K, - defaultValue: Properties[K], - ): NonUndefined - getProp(key: string): T - getProp(key: string, defaultValue: T): T - getProp(key?: string, defaultValue?: any) { - if (key == null) { - return this.store.get() - } - - return this.store.get(key, defaultValue) - } - - setProp( - key: K, - value: Properties[K] | null | undefined | void, - options?: Cell.SetOptions, - ): this - setProp(key: string, value: any, options?: Cell.SetOptions): this - setProp(props: Partial, options?: Cell.SetOptions): this - setProp( - key: string | Partial, - value?: any, - options?: Cell.SetOptions, - ) { - if (typeof key === 'string') { - this.store.set(key, value, options) - } else { - const props = this.preprocess(key, true) - this.store.set(ObjectExt.merge({}, this.getProp(), props), value) - this.postprocess(key) - } - return this - } - - removeProp( - key: K | K[], - options?: Cell.SetOptions, - ): this - removeProp(key: string | string[], options?: Cell.SetOptions): this - removeProp(options?: Cell.SetOptions): this - removeProp( - key?: string | string[] | Cell.SetOptions, - options?: Cell.SetOptions, - ) { - if (typeof key === 'string' || Array.isArray(key)) { - this.store.removeByPath(key, options) - } else { - this.store.remove(options) - } - return this - } - - hasChanged(): boolean - hasChanged(key: K | null): boolean - hasChanged(key: string | null): boolean - hasChanged(key?: string | null) { - return key == null ? this.store.hasChanged() : this.store.hasChanged(key) - } - - getPropByPath(path: string | string[]) { - return this.store.getByPath(path) - } - - setPropByPath( - path: string | string[], - value: any, - options: Cell.SetByPathOptions = {}, - ) { - if (this.model) { - // update inner reference - if (path === 'children') { - this._children = value - ? value - .map((id: string) => this.model!.getCell(id)) - .filter((child: Cell) => child != null) - : null - } else if (path === 'parent') { - this._parent = value ? this.model.getCell(value) : null - } - } - - this.store.setByPath(path, value, options) - return this - } - - removePropByPath(path: string | string[], options: Cell.SetOptions = {}) { - const paths = Array.isArray(path) ? path : path.split('/') - // Once a property is removed from the `attrs` the CellView will - // recognize a `dirty` flag and re-render itself in order to remove - // the attribute from SVGElement. - if (paths[0] === 'attrs') { - options.dirty = true - } - this.store.removeByPath(paths, options) - return this - } - - prop(): Properties - prop(key: K): Properties[K] - prop(key: string): T - prop(path: string[]): T - prop( - key: K, - value: Properties[K] | null | undefined | void, - options?: Cell.SetOptions, - ): this - prop(key: string, value: any, options?: Cell.SetOptions): this - prop(path: string[], value: any, options?: Cell.SetOptions): this - prop(props: Partial, options?: Cell.SetOptions): this - prop( - key?: string | string[] | Partial, - value?: any, - options?: Cell.SetOptions, - ) { - if (key == null) { - return this.getProp() - } - - if (typeof key === 'string' || Array.isArray(key)) { - if (arguments.length === 1) { - return this.getPropByPath(key) - } - - if (value == null) { - return this.removePropByPath(key, options || {}) - } - - return this.setPropByPath(key, value, options || {}) - } - - return this.setProp(key, value || {}) - } - - previous(name: K): Properties[K] | undefined - previous(name: string): T | undefined - previous(name: string) { - return this.store.getPrevious(name as keyof Cell.Properties) - } - - // #endregion - - // #region zIndex - - get zIndex() { - return this.getZIndex() - } - - set zIndex(z: number | undefined | null) { - if (z == null) { - this.removeZIndex() - } else { - this.setZIndex(z) - } - } - - getZIndex() { - return this.store.get('zIndex') - } - - setZIndex(z: number, options: Cell.SetOptions = {}) { - this.store.set('zIndex', z, options) - return this - } - - removeZIndex(options: Cell.SetOptions = {}) { - this.store.remove('zIndex', options) - return this - } - - toFront(options: Cell.ToFrontOptions = {}) { - const model = this.model - if (model) { - let z = model.getMaxZIndex() - let cells: Cell[] - if (options.deep) { - cells = this.getDescendants({ deep: true, breadthFirst: true }) - cells.unshift(this) - } else { - cells = [this] - } - - z = z - cells.length + 1 - - const count = model.total() - let changed = model.indexOf(this) !== count - cells.length - if (!changed) { - changed = cells.some((cell, index) => cell.getZIndex() !== z + index) - } - - if (changed) { - this.batchUpdate('to-front', () => { - z += cells.length - cells.forEach((cell, index) => { - cell.setZIndex(z + index, options) - }) - }) - } - } - - return this - } - - toBack(options: Cell.ToBackOptions = {}) { - const model = this.model - if (model) { - let z = model.getMinZIndex() - let cells: Cell[] - - if (options.deep) { - cells = this.getDescendants({ deep: true, breadthFirst: true }) - cells.unshift(this) - } else { - cells = [this] - } - - let changed = model.indexOf(this) !== 0 - if (!changed) { - changed = cells.some((cell, index) => cell.getZIndex() !== z + index) - } - - if (changed) { - this.batchUpdate('to-back', () => { - z -= cells.length - cells.forEach((cell, index) => { - cell.setZIndex(z + index, options) - }) - }) - } - } - - return this - } - - // #endregion - - // #region markup - - get markup() { - return this.getMarkup() - } - - set markup(value: Markup | undefined | null) { - if (value == null) { - this.removeMarkup() - } else { - this.setMarkup(value) - } - } - - getMarkup() { - let markup = this.store.get('markup') - if (markup == null) { - const ctor = this.constructor as typeof Cell - markup = ctor.getMarkup() - } - return markup - } - - setMarkup(markup: Markup, options: Cell.SetOptions = {}) { - this.store.set('markup', markup, options) - return this - } - - removeMarkup(options: Cell.SetOptions = {}) { - this.store.remove('markup', options) - return this - } - - // #endregion - - // #region attrs - - get attrs() { - return this.getAttrs() - } - - set attrs(value: Attr.CellAttrs | null | undefined) { - if (value == null) { - this.removeAttrs() - } else { - this.setAttrs(value) - } - } - - getAttrs() { - const result = this.store.get('attrs') - return result ? { ...result } : {} - } - - setAttrs( - attrs: Attr.CellAttrs | null | undefined, - options: Cell.SetAttrOptions = {}, - ) { - if (attrs == null) { - this.removeAttrs(options) - } else { - const set = (attrs: Attr.CellAttrs) => - this.store.set('attrs', attrs, options) - - if (options.overwrite === true) { - set(attrs) - } else { - const prev = this.getAttrs() - if (options.deep === false) { - set({ ...prev, ...attrs }) - } else { - set(ObjectExt.merge({}, prev, attrs)) - } - } - } - - return this - } - - replaceAttrs(attrs: Attr.CellAttrs, options: Cell.SetOptions = {}) { - return this.setAttrs(attrs, { ...options, overwrite: true }) - } - - updateAttrs(attrs: Attr.CellAttrs, options: Cell.SetOptions = {}) { - return this.setAttrs(attrs, { ...options, deep: false }) - } - - removeAttrs(options: Cell.SetOptions = {}) { - this.store.remove('attrs', options) - return this - } - - getAttrDefinition(attrName: string) { - if (!attrName) { - return null - } - - const ctor = this.constructor as typeof Cell - const hooks = ctor.getAttrHooks() || {} - let definition = hooks[attrName] || Attr.registry.get(attrName) - if (!definition) { - const name = StringExt.camelCase(attrName) - definition = hooks[name] || Attr.registry.get(name) - } - - return definition || null - } - - getAttrByPath(): Attr.CellAttrs - getAttrByPath(path: string | string[]): T - getAttrByPath(path?: string | string[]) { - if (path == null || path === '') { - return this.getAttrs() - } - return this.getPropByPath(this.prefixAttrPath(path)) - } - - setAttrByPath( - path: string | string[], - value: Attr.ComplexAttrValue, - options: Cell.SetOptions = {}, - ) { - this.setPropByPath(this.prefixAttrPath(path), value, options) - return this - } - - removeAttrByPath(path: string | string[], options: Cell.SetOptions = {}) { - this.removePropByPath(this.prefixAttrPath(path), options) - return this - } - - protected prefixAttrPath(path: string | string[]) { - return Array.isArray(path) ? ['attrs'].concat(path) : `attrs/${path}` - } - - attr(): Attr.CellAttrs - attr(path: string | string[]): T - attr( - path: string | string[], - value: Attr.ComplexAttrValue | null, - options?: Cell.SetOptions, - ): this - attr(attrs: Attr.CellAttrs, options?: Cell.SetAttrOptions): this - attr( - path?: string | string[] | Attr.CellAttrs, - value?: Attr.ComplexAttrValue | Cell.SetOptions, - options?: Cell.SetOptions, - ) { - if (path == null) { - return this.getAttrByPath() - } - - if (typeof path === 'string' || Array.isArray(path)) { - if (arguments.length === 1) { - return this.getAttrByPath(path) - } - if (value == null) { - return this.removeAttrByPath(path, options || {}) - } - return this.setAttrByPath( - path, - value as Attr.ComplexAttrValue, - options || {}, - ) - } - - return this.setAttrs(path, (value || {}) as Cell.SetOptions) - } - - // #endregion - - // #region visible - - get visible() { - return this.isVisible() - } - - set visible(value: boolean) { - this.setVisible(value) - } - - setVisible(visible: boolean, options: Cell.SetOptions = {}) { - this.store.set('visible', visible, options) - return this - } - - isVisible() { - return this.store.get('visible') !== false - } - - show(options: Cell.SetOptions = {}) { - if (!this.isVisible()) { - this.setVisible(true, options) - } - return this - } - - hide(options: Cell.SetOptions = {}) { - if (this.isVisible()) { - this.setVisible(false, options) - } - return this - } - - toggleVisible(visible: boolean, options?: Cell.SetOptions): this - toggleVisible(options?: Cell.SetOptions): this - toggleVisible( - isVisible?: boolean | Cell.SetOptions, - options: Cell.SetOptions = {}, - ) { - const visible = - typeof isVisible === 'boolean' ? isVisible : !this.isVisible() - const localOptions = typeof isVisible === 'boolean' ? options : isVisible - if (visible) { - this.show(localOptions) - } else { - this.hide(localOptions) - } - return this - } - - // #endregion - - // #region data - - get data(): Properties['data'] { - return this.getData() - } - - set data(val: Properties['data']) { - this.setData(val) - } - - getData(): T { - return this.store.get('data') - } - - setData(data: T, options: Cell.SetDataOptions = {}) { - if (data == null) { - this.removeData(options) - } else { - const set = (data: T) => this.store.set('data', data, options) - - if (options.overwrite === true) { - set(data) - } else { - const prev = this.getData>() - if (options.deep === false) { - set(typeof data === 'object' ? { ...prev, ...data } : data) - } else { - set(ObjectExt.merge({}, prev, data)) - } - } - } - - return this - } - - replaceData(data: T, options: Cell.SetOptions = {}) { - return this.setData(data, { ...options, overwrite: true }) - } - - updateData(data: T, options: Cell.SetOptions = {}) { - return this.setData(data, { ...options, deep: false }) - } - - removeData(options: Cell.SetOptions = {}) { - this.store.remove('data', options) - return this - } - - // #endregion - - // #region parent children - - get parent(): Cell | null { - return this.getParent() - } - - get children() { - return this.getChildren() - } - - getParentId() { - return this.store.get('parent') - } - - getParent(): T | null { - const parentId = this.getParentId() - if (parentId && this.model) { - const parent = this.model.getCell(parentId) - this._parent = parent - return parent - } - return null - } - - getChildren() { - const childrenIds = this.store.get('children') - if (childrenIds && childrenIds.length && this.model) { - const children = childrenIds - .map((id) => this.model?.getCell(id)) - .filter((cell) => cell != null) as Cell[] - this._children = children - return [...children] - } - return null - } - - hasParent() { - return this.parent != null - } - - isParentOf(child: Cell | null): boolean { - return child != null && child.getParent() === this - } - - isChildOf(parent: Cell | null): boolean { - return parent != null && this.getParent() === parent - } - - eachChild( - iterator: (child: Cell, index: number, children: Cell[]) => void, - context?: any, - ) { - if (this.children) { - this.children.forEach(iterator, context) - } - return this - } - - filterChild( - filter: (cell: Cell, index: number, arr: Cell[]) => boolean, - context?: any, - ): Cell[] { - return this.children ? this.children.filter(filter, context) : [] - } - - getChildCount() { - return this.children == null ? 0 : this.children.length - } - - getChildIndex(child: Cell) { - return this.children == null ? -1 : this.children.indexOf(child) - } - - getChildAt(index: number) { - return this.children != null && index >= 0 ? this.children[index] : null - } - - getAncestors(options: { deep?: boolean } = {}): Cell[] { - const ancestors: Cell[] = [] - let parent = this.getParent() - while (parent) { - ancestors.push(parent) - parent = options.deep !== false ? parent.getParent() : null - } - return ancestors - } - - getDescendants(options: Cell.GetDescendantsOptions = {}): Cell[] { - if (options.deep !== false) { - // breadth first - if (options.breadthFirst) { - const cells = [] - const queue = this.getChildren() || [] - - while (queue.length > 0) { - const parent = queue.shift()! - const children = parent.getChildren() - cells.push(parent) - if (children) { - queue.push(...children) - } - } - return cells - } - - // depth first - { - const cells = this.getChildren() || [] - cells.forEach((cell) => { - cells.push(...cell.getDescendants(options)) - }) - return cells - } - } - - return this.getChildren() || [] - } - - isDescendantOf( - ancestor: Cell | null, - options: { deep?: boolean } = {}, - ): boolean { - if (ancestor == null) { - return false - } - - if (options.deep !== false) { - let current = this.getParent() - while (current) { - if (current === ancestor) { - return true - } - current = current.getParent() - } - - return false - } - - return this.isChildOf(ancestor) - } - - isAncestorOf( - descendant: Cell | null, - options: { deep?: boolean } = {}, - ): boolean { - if (descendant == null) { - return false - } - - return descendant.isDescendantOf(this, options) - } - - contains(cell: Cell | null) { - return this.isAncestorOf(cell) - } - - getCommonAncestor(...cells: (Cell | null | undefined)[]): Cell | null { - return Cell.getCommonAncestor(this, ...cells) - } - - setParent(parent: Cell | null, options: Cell.SetOptions = {}) { - this._parent = parent - if (parent) { - this.store.set('parent', parent.id, options) - } else { - this.store.remove('parent', options) - } - return this - } - - setChildren(children: Cell[] | null, options: Cell.SetOptions = {}) { - this._children = children - if (children != null) { - this.store.set( - 'children', - children.map((child) => child.id), - options, - ) - } else { - this.store.remove('children', options) - } - return this - } - - unembed(child: Cell, options: Cell.SetOptions = {}) { - const children = this.children - if (children != null && child != null) { - const index = this.getChildIndex(child) - if (index !== -1) { - children.splice(index, 1) - child.setParent(null, options) - this.setChildren(children, options) - } - } - return this - } - - embed(child: Cell, options: Cell.SetOptions = {}) { - child.addTo(this, options) - return this - } - - addTo(model: Model, options?: Cell.SetOptions): this - addTo(parent: Cell, options?: Cell.SetOptions): this - addTo(target: Model | Cell, options: Cell.SetOptions = {}) { - if (Cell.isCell(target)) { - target.addChild(this, options) - } else { - target.addCell(this, options) - } - return this - } - - insertTo(parent: Cell, index?: number, options: Cell.SetOptions = {}) { - parent.insertChild(this, index, options) - return this - } - - addChild(child: Cell | null, options: Cell.SetOptions = {}) { - return this.insertChild(child, undefined, options) - } - - insertChild( - child: Cell | null, - index?: number, - options: Cell.SetOptions = {}, - ): this { - if (child != null && child !== this) { - const oldParent = child.getParent() - const changed = this !== oldParent - - let pos = index - if (pos == null) { - pos = this.getChildCount() - if (!changed) { - pos -= 1 - } - } - - // remove from old parent - if (oldParent) { - const children = oldParent.getChildren() - if (children) { - const index = children.indexOf(child) - if (index >= 0) { - child.setParent(null, options) - children.splice(index, 1) - oldParent.setChildren(children, options) - } - } - } - - let children = this.children - if (children == null) { - children = [] - children.push(child) - } else { - children.splice(pos, 0, child) - } - - child.setParent(this, options) - this.setChildren(children, options) - - if (changed && this.model) { - const incomings = this.model.getIncomingEdges(this) - const outgoings = this.model.getOutgoingEdges(this) - - if (incomings) { - incomings.forEach((edge) => edge.updateParent(options)) - } - - if (outgoings) { - outgoings.forEach((edge) => edge.updateParent(options)) - } - } - - if (this.model) { - this.model.addCell(child, options) - } - } - - return this - } - - removeFromParent(options: Cell.RemoveOptions = {}) { - const parent = this.getParent() - if (parent != null) { - const index = parent.getChildIndex(this) - parent.removeChildAt(index, options) - } - return this - } - - removeChild(child: Cell, options: Cell.RemoveOptions = {}) { - const index = this.getChildIndex(child) - return this.removeChildAt(index, options) - } - - removeChildAt(index: number, options: Cell.RemoveOptions = {}) { - const child = this.getChildAt(index) - const children = this.children - - if (children != null && child != null) { - this.unembed(child, options) - child.remove(options) - } - - return child - } - - remove(options: Cell.RemoveOptions = {}) { - this.batchUpdate('remove', () => { - const parent = this.getParent() - if (parent) { - parent.removeChild(this, options) - } - - if (options.deep !== false) { - this.eachChild((child) => child.remove(options)) - } - - if (this.model) { - this.model.removeCell(this, options) - } - }) - return this - } - - // #endregion - - // #region transition - - transition( - path: K, - target: Properties[K], - options?: Animation.StartOptions, - delim?: string, - ): () => void - transition( - path: string | string[], - target: T, - options?: Animation.StartOptions, - delim?: string, - ): () => void - transition( - path: string | string[], - target: T, - options: Animation.StartOptions = {}, - delim = '/', - ) { - return this.animation.start(path, target, options, delim) - } - - stopTransition( - path: string | string[], - options?: Animation.StopOptions, - delim = '/', - ) { - this.animation.stop(path, options, delim) - return this - } - - getTransitions() { - return this.animation.get() - } - - // #endregion - - // #region transform - - // eslint-disable-next-line - translate(tx: number, ty: number, options?: Cell.TranslateOptions) { - return this - } - - scale( - sx: number, // eslint-disable-line - sy: number, // eslint-disable-line - origin?: Point | Point.PointLike, // eslint-disable-line - options?: Node.SetOptions, // eslint-disable-line - ) { - return this - } - - // #endregion - - // #region tools - - addTools( - items: Cell.ToolItem | Cell.ToolItem[], - options?: Cell.AddToolOptions, - ): void - addTools( - items: Cell.ToolItem | Cell.ToolItem[], - name: string, - options?: Cell.AddToolOptions, - ): void - addTools( - items: Cell.ToolItem | Cell.ToolItem[], - obj?: string | Cell.AddToolOptions, - options?: Cell.AddToolOptions, - ) { - const toolItems = Array.isArray(items) ? items : [items] - const name = typeof obj === 'string' ? obj : null - const config = - typeof obj === 'object' ? obj : typeof options === 'object' ? options : {} - - if (config.reset) { - return this.setTools( - { name, items: toolItems, local: config.local }, - config, - ) - } - let tools = ObjectExt.cloneDeep(this.getTools()) - if (tools == null || name == null || tools.name === name) { - if (tools == null) { - tools = {} as Cell.Tools - } - - if (!tools.items) { - tools.items = [] - } - - tools.name = name - tools.items = [...tools.items, ...toolItems] - - return this.setTools({ ...tools }, config) - } - } - - setTools(tools?: Cell.ToolsLoose | null, options: Cell.SetOptions = {}) { - if (tools == null) { - this.removeTools() - } else { - this.store.set('tools', Cell.normalizeTools(tools), options) - } - return this - } - - getTools(): Cell.Tools | null { - return this.store.get('tools') - } - - removeTools(options: Cell.SetOptions = {}) { - this.store.remove('tools', options) - return this - } - - hasTools(name?: string) { - const tools = this.getTools() - if (tools == null) { - return false - } - - if (name == null) { - return true - } - - return tools.name === name - } - - hasTool(name: string) { - const tools = this.getTools() - if (tools == null) { - return false - } - return tools.items.some((item) => - typeof item === 'string' ? item === name : item.name === name, - ) - } - - removeTool(name: string, options?: Cell.SetOptions): this - removeTool(index: number, options?: Cell.SetOptions): this - removeTool(nameOrIndex: string | number, options: Cell.SetOptions = {}) { - const tools = ObjectExt.cloneDeep(this.getTools()) - if (tools) { - let updated = false - const items = tools.items.slice() - const remove = (index: number) => { - items.splice(index, 1) - updated = true - } - - if (typeof nameOrIndex === 'number') { - remove(nameOrIndex) - } else { - for (let i = items.length - 1; i >= 0; i -= 1) { - const item = items[i] - const exist = - typeof item === 'string' - ? item === nameOrIndex - : item.name === nameOrIndex - if (exist) { - remove(i) - } - } - } - - if (updated) { - tools.items = items - this.setTools(tools, options) - } - } - return this - } - - // #endregion - - // #region common - - // eslint-disable-next-line - getBBox(options?: { deep?: boolean }) { - return new Rectangle() - } - - // eslint-disable-next-line - getConnectionPoint(edge: Edge, type: Edge.TerminalType) { - return new Point() - } - - toJSON( - options: Cell.ToJSONOptions = {}, - ): this extends Node - ? Node.Properties - : this extends Edge - ? Edge.Properties - : Properties { - const props = { ...this.store.get() } - const toString = Object.prototype.toString - const cellType = this.isNode() ? 'node' : this.isEdge() ? 'edge' : 'cell' - - if (!props.shape) { - const ctor = this.constructor - throw new Error( - `Unable to serialize ${cellType} missing "shape" prop, check the ${cellType} "${ - ctor.name || toString.call(ctor) - }"`, - ) - } - - const ctor = this.constructor as typeof Cell - const diff = options.diff === true - const attrs = props.attrs || {} - const presets = ctor.getDefaults(true) as Properties - // When `options.diff` is `true`, we should process the custom options, - // such as `width`, `height` etc. to ensure the comparing work correctly. - const defaults = diff ? this.preprocess(presets, true) : presets - const defaultAttrs = defaults.attrs || {} - const finalAttrs: Attr.CellAttrs = {} - - Object.keys(props).forEach((key) => { - const val = props[key] - if ( - val != null && - !Array.isArray(val) && - typeof val === 'object' && - !ObjectExt.isPlainObject(val) - ) { - throw new Error( - `Can only serialize ${cellType} with plain-object props, but got a "${toString.call( - val, - )}" type of key "${key}" on ${cellType} "${this.id}"`, - ) - } - - if (key !== 'attrs' && key !== 'shape' && diff) { - const preset = defaults[key] - if (ObjectExt.isEqual(val, preset)) { - delete props[key] - } - } - }) - - Object.keys(attrs).forEach((key) => { - const attr = attrs[key] - const defaultAttr = defaultAttrs[key] - - Object.keys(attr).forEach((name) => { - const value = attr[name] as KeyValue - const defaultValue = defaultAttr ? defaultAttr[name] : null - - if ( - value != null && - typeof value === 'object' && - !Array.isArray(value) - ) { - Object.keys(value).forEach((subName) => { - const subValue = value[subName] - if ( - defaultAttr == null || - defaultValue == null || - !ObjectExt.isObject(defaultValue) || - !ObjectExt.isEqual(defaultValue[subName], subValue) - ) { - if (finalAttrs[key] == null) { - finalAttrs[key] = {} - } - if (finalAttrs[key][name] == null) { - finalAttrs[key][name] = {} - } - const tmp = finalAttrs[key][name] as KeyValue - tmp[subName] = subValue - } - }) - } else if ( - defaultAttr == null || - !ObjectExt.isEqual(defaultValue, value) - ) { - // `value` is not an object, default attribute with `key` does not - // exist or it is different than the attribute value set on the cell. - if (finalAttrs[key] == null) { - finalAttrs[key] = {} - } - finalAttrs[key][name] = value as any - } - }) - }) - - const finalProps = { - ...props, - attrs: ObjectExt.isEmpty(finalAttrs) ? undefined : finalAttrs, - } - - if (finalProps.attrs == null) { - delete finalProps.attrs - } - - const ret = finalProps as any - if (ret.angle === 0) { - delete ret.angle - } - - return ObjectExt.cloneDeep(ret) - } - - clone( - options: Cell.CloneOptions = {}, - ): this extends Node ? Node : this extends Edge ? Edge : Cell { - if (!options.deep) { - const data = { ...this.store.get() } - if (!options.keepId) { - delete data.id - } - delete data.parent - delete data.children - const ctor = this.constructor as typeof Cell - return new ctor(data) as any // eslint-disable-line new-cap - } - - // Deep cloning. Clone the cell itself and all its children. - const map = Cell.deepClone(this) - return map[this.id] as any - } - - findView(graph: Graph): CellView | null { - return graph.findViewByCell(this) - } - - // #endregion - - // #region batch - - startBatch( - name: Model.BatchName, - data: KeyValue = {}, - model: Model | null = this.model, - ) { - this.notify('batch:start', { name, data, cell: this }) - - if (model) { - model.startBatch(name, { ...data, cell: this }) - } - - return this - } - - stopBatch( - name: Model.BatchName, - data: KeyValue = {}, - model: Model | null = this.model, - ) { - if (model) { - model.stopBatch(name, { ...data, cell: this }) - } - - this.notify('batch:stop', { name, data, cell: this }) - return this - } - - batchUpdate(name: Model.BatchName, execute: () => T, data?: KeyValue): T { - // The model is null after cell was removed(remove batch). - // So we should temp save model to trigger pairing batch event. - const model = this.model - this.startBatch(name, data, model) - const result = execute() - this.stopBatch(name, data, model) - return result - } - - // #endregion - - // #region IDisposable - - @Basecoat.dispose() - dispose() { - this.removeFromParent() - this.store.dispose() - } - - // #endregion -} - -export namespace Cell { - export interface Common { - view?: string - shape?: string - markup?: Markup - attrs?: Attr.CellAttrs - zIndex?: number - visible?: boolean - data?: any - } - - export interface Defaults extends Common {} - - export interface Metadata extends Common, KeyValue { - id?: string - tools?: ToolsLoose - } - - export interface Properties extends Defaults, Metadata { - parent?: string - children?: string[] - tools?: Tools - } -} - -export namespace Cell { - export type ToolItem = - | string - | { - name: string - args?: any - } - - export interface Tools { - name?: string | null - local?: boolean - items: ToolItem[] - } - - export type ToolsLoose = ToolItem | ToolItem[] | Tools - - export function normalizeTools(raw: ToolsLoose): Tools { - if (typeof raw === 'string') { - return { items: [raw] } - } - - if (Array.isArray(raw)) { - return { items: raw } - } - - if ((raw as Tools).items) { - return raw as Tools - } - - return { - items: [raw as ToolItem], - } - } -} - -export namespace Cell { - export interface SetOptions extends Store.SetOptions {} - - export interface MutateOptions extends Store.MutateOptions {} - - export interface RemoveOptions extends SetOptions { - deep?: boolean - } - - export interface SetAttrOptions extends SetOptions { - deep?: boolean - overwrite?: boolean - } - - export interface SetDataOptions extends SetOptions { - deep?: boolean - overwrite?: boolean - } - - export interface SetByPathOptions extends Store.SetByPathOptions {} - - export interface ToFrontOptions extends SetOptions { - deep?: boolean - } - - export interface ToBackOptions extends ToFrontOptions {} - - export interface TranslateOptions extends SetOptions { - tx?: number - ty?: number - translateBy?: string | number - } - - export interface AddToolOptions extends SetOptions { - reset?: boolean - local?: boolean - } - - export interface GetDescendantsOptions { - deep?: boolean - breadthFirst?: boolean - } - - export interface ToJSONOptions { - diff?: boolean - } - - export interface CloneOptions { - deep?: boolean - keepId?: boolean - } -} - -export namespace Cell { - export interface EventArgs { - 'transition:start': Animation.CallbackArgs - 'transition:progress': Animation.ProgressArgs - 'transition:complete': Animation.CallbackArgs - 'transition:stop': Animation.StopArgs - 'transition:finish': Animation.CallbackArgs - - // common - 'change:*': ChangeAnyKeyArgs - 'change:attrs': ChangeArgs - 'change:zIndex': ChangeArgs - 'change:markup': ChangeArgs - 'change:visible': ChangeArgs - 'change:parent': ChangeArgs - 'change:children': ChangeArgs - 'change:tools': ChangeArgs - 'change:view': ChangeArgs - 'change:data': ChangeArgs - - // node - 'change:size': NodeChangeArgs - 'change:angle': NodeChangeArgs - 'change:position': NodeChangeArgs - 'change:ports': NodeChangeArgs - 'change:portMarkup': NodeChangeArgs - 'change:portLabelMarkup': NodeChangeArgs - 'change:portContainerMarkup': NodeChangeArgs - 'ports:removed': { - cell: Cell - node: Node - removed: PortManager.Port[] - } - 'ports:added': { - cell: Cell - node: Node - added: PortManager.Port[] - } - - // edge - 'change:source': EdgeChangeArgs - 'change:target': EdgeChangeArgs - 'change:terminal': EdgeChangeArgs & { - type: Edge.TerminalType - } - 'change:router': EdgeChangeArgs - 'change:connector': EdgeChangeArgs - 'change:vertices': EdgeChangeArgs - 'change:labels': EdgeChangeArgs - 'change:defaultLabel': EdgeChangeArgs - 'change:vertexMarkup': EdgeChangeArgs - 'change:arrowheadMarkup': EdgeChangeArgs - 'vertexs:added': { - cell: Cell - edge: Edge - added: Point.PointLike[] - } - 'vertexs:removed': { - cell: Cell - edge: Edge - removed: Point.PointLike[] - } - 'labels:added': { - cell: Cell - edge: Edge - added: Edge.Label[] - } - 'labels:removed': { - cell: Cell - edge: Edge - removed: Edge.Label[] - } - - 'batch:start': { - name: Model.BatchName - data: KeyValue - cell: Cell - } - - 'batch:stop': { - name: Model.BatchName - data: KeyValue - cell: Cell - } - - changed: { - cell: Cell - options: MutateOptions - } - - added: { - cell: Cell - index: number - options: Cell.SetOptions - } - - removed: { - cell: Cell - index: number - options: Cell.RemoveOptions - } - } - - interface ChangeAnyKeyArgs { - key: T - current: Properties[T] - previous: Properties[T] - options: MutateOptions - cell: Cell - } - - export interface ChangeArgs { - cell: Cell - current?: T - previous?: T - options: MutateOptions - } - - interface NodeChangeArgs extends ChangeArgs { - node: Node - } - - interface EdgeChangeArgs extends ChangeArgs { - edge: Edge - } -} - -export namespace Cell { - export const toStringTag = `X6.${Cell.name}` - - export function isCell(instance: any): instance is Cell { - if (instance == null) { - return false - } - - if (instance instanceof Cell) { - return true - } - - const tag = instance[Symbol.toStringTag] - const cell = instance as Cell - - if ( - (tag == null || tag === toStringTag) && - typeof cell.isNode === 'function' && - typeof cell.isEdge === 'function' && - typeof cell.prop === 'function' && - typeof cell.attr === 'function' - ) { - return true - } - - return false - } -} - -export namespace Cell { - export function getCommonAncestor( - ...cells: (Cell | null | undefined)[] - ): Cell | null { - const ancestors = cells - .filter((cell) => cell != null) - .map((cell) => cell!.getAncestors()) - .sort((a, b) => { - return a.length - b.length - }) - - const first = ancestors.shift()! - return ( - first.find((cell) => ancestors.every((item) => item.includes(cell))) || - null - ) - } - - export interface GetCellsBBoxOptions { - deep?: boolean - } - - export function getCellsBBox( - cells: Cell[], - options: GetCellsBBoxOptions = {}, - ) { - let bbox: Rectangle | null = null - - for (let i = 0, ii = cells.length; i < ii; i += 1) { - const cell = cells[i] - let rect = cell.getBBox(options) - if (rect) { - if (cell.isNode()) { - const angle = cell.getAngle() - if (angle != null && angle !== 0) { - rect = rect.bbox(angle) - } - } - bbox = bbox == null ? rect : bbox.union(rect) - } - } - - return bbox - } - - export function deepClone(cell: Cell) { - const cells = [cell, ...cell.getDescendants({ deep: true })] - return Cell.cloneCells(cells) - } - - export function cloneCells(cells: Cell[]) { - const inputs = ArrayExt.uniq(cells) - const cloneMap = inputs.reduce>((map, cell) => { - map[cell.id] = cell.clone() - return map - }, {}) - - inputs.forEach((cell) => { - const clone = cloneMap[cell.id] - if (clone.isEdge()) { - const sourceId = clone.getSourceCellId() - const targetId = clone.getTargetCellId() - if (sourceId && cloneMap[sourceId]) { - // Source is a node and the node is among the clones. - // Then update the source of the cloned edge. - clone.setSource({ - ...clone.getSource(), - cell: cloneMap[sourceId].id, - }) - } - if (targetId && cloneMap[targetId]) { - // Target is a node and the node is among the clones. - // Then update the target of the cloned edge. - clone.setTarget({ - ...clone.getTarget(), - cell: cloneMap[targetId].id, - }) - } - } - - // Find the parent of the original cell - const parent = cell.getParent() - if (parent && cloneMap[parent.id]) { - clone.setParent(cloneMap[parent.id]) - } - - // Find the children of the original cell - const children = cell.getChildren() - if (children && children.length) { - const embeds = children.reduce((memo, child) => { - // Embedded cells that are not being cloned can not be carried - // over with other embedded cells. - if (cloneMap[child.id]) { - memo.push(cloneMap[child.id]) - } - return memo - }, []) - - if (embeds.length > 0) { - clone.setChildren(embeds) - } - } - }) - - return cloneMap - } -} - -export namespace Cell { - export type Definition = typeof Cell - - export type PropHook = ( - this: C, - metadata: M, - ) => M - - export type PropHooks = - | KeyValue> - | PropHook - | PropHook[] - - export interface Config - extends Defaults, - KeyValue { - constructorName?: string - overwrite?: boolean - propHooks?: PropHooks - attrHooks?: Attr.Definitions - } -} - -export namespace Cell { - Cell.config({ - propHooks({ tools, ...metadata }) { - if (tools) { - metadata.tools = normalizeTools(tools) - } - return metadata - }, - }) -} diff --git a/packages/x6-next/src/model/collection.ts b/packages/x6-next/src/model/collection.ts deleted file mode 100644 index de2160f44e0..00000000000 --- a/packages/x6-next/src/model/collection.ts +++ /dev/null @@ -1,541 +0,0 @@ -import { ArrayExt, Basecoat } from '@antv/x6-common' -import { Cell } from './cell' -import { Node } from './node' -import { Edge } from './edge' - -export class Collection extends Basecoat { - public length = 0 - public comparator: Collection.Comparator | null - private cells: Cell[] - private map: { [id: string]: Cell } - - constructor(cells: Cell | Cell[], options: Collection.Options = {}) { - super() - this.comparator = options.comparator || 'zIndex' - this.clean() - if (cells) { - this.reset(cells, { silent: true }) - } - } - - toJSON() { - return this.cells.map((cell) => cell.toJSON()) - } - - add(cells: Cell | Cell[], options?: Collection.AddOptions): this - add( - cells: Cell | Cell[], - index: number, - options?: Collection.AddOptions, - ): this - add( - cells: Cell | Cell[], - index?: number | Collection.AddOptions, - options?: Collection.AddOptions, - ) { - let localIndex: number - let localOptions: Collection.AddOptions - - if (typeof index === 'number') { - localIndex = index - localOptions = { merge: false, ...options } - } else { - localIndex = this.length - localOptions = { merge: false, ...index } - } - - if (localIndex > this.length) { - localIndex = this.length - } - if (localIndex < 0) { - localIndex += this.length + 1 - } - - const entities = Array.isArray(cells) ? cells : [cells] - const sortable = - this.comparator && - typeof index !== 'number' && - localOptions.sort !== false - const sortAttr = this.comparator || null - - let sort = false - const added: Cell[] = [] - const merged: Cell[] = [] - - entities.forEach((cell) => { - const existing = this.get(cell) - if (existing) { - if (localOptions.merge && !cell.isSameStore(existing)) { - existing.setProp(cell.getProp(), options) // merge - merged.push(existing) - if (sortable && !sort) { - if (sortAttr == null || typeof sortAttr === 'function') { - sort = existing.hasChanged() - } else if (typeof sortAttr === 'string') { - sort = existing.hasChanged(sortAttr) - } else { - sort = sortAttr.some((key) => existing.hasChanged(key)) - } - } - } - } else { - added.push(cell) - this.reference(cell) - } - }) - - if (added.length) { - if (sortable) { - sort = true - } - this.cells.splice(localIndex, 0, ...added) - this.length = this.cells.length - } - - if (sort) { - this.sort({ silent: true }) - } - - if (!localOptions.silent) { - added.forEach((cell, i) => { - const args = { - cell, - index: localIndex + i, - options: localOptions, - } - this.trigger('added', args) - if (!localOptions.dryrun) { - cell.notify('added', { ...args }) - } - }) - - if (sort) { - this.trigger('sorted') - } - - if (added.length || merged.length) { - this.trigger('updated', { - added, - merged, - removed: [], - options: localOptions, - }) - } - } - - return this - } - - remove(cell: Cell, options?: Collection.RemoveOptions): Cell - remove(cells: Cell[], options?: Collection.RemoveOptions): Cell[] - remove(cells: Cell | Cell[], options: Collection.RemoveOptions = {}) { - const arr = Array.isArray(cells) ? cells : [cells] - const removed = this.removeCells(arr, options) - if (!options.silent && removed.length > 0) { - this.trigger('updated', { - options, - removed, - added: [], - merged: [], - }) - } - return Array.isArray(cells) ? removed : removed[0] - } - - protected removeCells(cells: Cell[], options: Collection.RemoveOptions) { - const removed = [] - - for (let i = 0; i < cells.length; i += 1) { - const cell = this.get(cells[i]) - if (cell == null) { - continue - } - - const index = this.cells.indexOf(cell) - this.cells.splice(index, 1) - this.length -= 1 - delete this.map[cell.id] - removed.push(cell) - this.unreference(cell) - - if (!options.dryrun) { - cell.remove() - } - - if (!options.silent) { - this.trigger('removed', { cell, index, options }) - - if (!options.dryrun) { - cell.notify('removed', { cell, index, options }) - } - } - } - - return removed - } - - reset(cells: Cell | Cell[], options: Collection.SetOptions = {}) { - const previous = this.cells.slice() - previous.forEach((cell) => this.unreference(cell)) - this.clean() - this.add(cells, { silent: true, ...options }) - if (!options.silent) { - const current = this.cells.slice() - this.trigger('reseted', { - options, - previous, - current, - }) - - const added: Cell[] = [] - const removed: Cell[] = [] - - current.forEach((a) => { - const exist = previous.some((b) => b.id === a.id) - if (!exist) { - added.push(a) - } - }) - - previous.forEach((a) => { - const exist = current.some((b) => b.id === a.id) - if (!exist) { - removed.push(a) - } - }) - - this.trigger('updated', { options, added, removed, merged: [] }) - } - - return this - } - - push(cell: Cell, options?: Collection.SetOptions) { - return this.add(cell, this.length, options) - } - - pop(options?: Collection.SetOptions) { - const cell = this.at(this.length - 1)! - return this.remove(cell, options) - } - - unshift(cell: Cell, options?: Collection.SetOptions) { - return this.add(cell, 0, options) - } - - shift(options?: Collection.SetOptions) { - const cell = this.at(0)! - return this.remove(cell, options) - } - - get(cell?: string | number | Cell | null): Cell | null { - if (cell == null) { - return null - } - - const id = - typeof cell === 'string' || typeof cell === 'number' ? cell : cell.id - return this.map[id] || null - } - - has(cell: string | Cell): boolean { - return this.get(cell as any) != null - } - - at(index: number): Cell | null { - if (index < 0) { - index += this.length // eslint-disable-line - } - return this.cells[index] || null - } - - first() { - return this.at(0) - } - - last() { - return this.at(-1) - } - - indexOf(cell: Cell) { - return this.cells.indexOf(cell) - } - - toArray() { - return this.cells.slice() - } - - sort(options: Collection.SetOptions = {}) { - if (this.comparator != null) { - this.cells = ArrayExt.sortBy(this.cells, this.comparator) - if (!options.silent) { - this.trigger('sorted') - } - } - - return this - } - - clone() { - const constructor = this.constructor as any - return new constructor(this.cells.slice(), { - comparator: this.comparator, - }) as Collection - } - - protected reference(cell: Cell) { - this.map[cell.id] = cell - cell.on('*', this.notifyCellEvent, this) - } - - protected unreference(cell: Cell) { - cell.off('*', this.notifyCellEvent, this) - delete this.map[cell.id] - } - - protected notifyCellEvent( - name: K, - args: Cell.EventArgs[K], - ) { - const cell = args.cell - this.trigger(`cell:${name}`, args) - if (cell) { - if (cell.isNode()) { - this.trigger(`node:${name}`, { ...args, node: cell }) - } else if (cell.isEdge()) { - this.trigger(`edge:${name}`, { ...args, edge: cell }) - } - } - } - - protected clean() { - this.length = 0 - this.cells = [] - this.map = {} - } -} - -export namespace Collection { - export type Comparator = string | string[] | ((cell: Cell) => number) - - export interface Options { - comparator?: Comparator - } - - export interface SetOptions extends Cell.SetOptions {} - - export interface RemoveOptions extends Cell.SetOptions { - /** - * The default is to remove all the associated links. - * Set `disconnectEdges` option to `true` to disconnect edges - * when a cell is removed. - */ - disconnectEdges?: boolean - - dryrun?: boolean - } - - export interface AddOptions extends SetOptions { - sort?: boolean - merge?: boolean - dryrun?: boolean - } -} - -export namespace Collection { - export interface EventArgs - extends CellEventArgs, - NodeEventArgs, - EdgeEventArgs { - sorted?: null - reseted: { - current: Cell[] - previous: Cell[] - options: SetOptions - } - updated: { - added: Cell[] - merged: Cell[] - removed: Cell[] - options: SetOptions - } - added: { - cell: Cell - index: number - options: AddOptions - } - removed: { - cell: Cell - index: number - options: RemoveOptions - } - } - - interface NodeEventCommonArgs { - node: Node - } - - interface EdgeEventCommonArgs { - edge: Edge - } - - export interface CellEventArgs { - 'cell:transition:start': Cell.EventArgs['transition:start'] - 'cell:transition:progress': Cell.EventArgs['transition:progress'] - 'cell:transition:complete': Cell.EventArgs['transition:complete'] - 'cell:transition:stop': Cell.EventArgs['transition:stop'] - 'cell:transition:finish': Cell.EventArgs['transition:finish'] - - 'cell:changed': Cell.EventArgs['changed'] - 'cell:added': Cell.EventArgs['added'] - 'cell:removed': Cell.EventArgs['removed'] - - 'cell:change:*': Cell.EventArgs['change:*'] - 'cell:change:attrs': Cell.EventArgs['change:attrs'] - 'cell:change:zIndex': Cell.EventArgs['change:zIndex'] - 'cell:change:markup': Cell.EventArgs['change:markup'] - 'cell:change:visible': Cell.EventArgs['change:visible'] - 'cell:change:parent': Cell.EventArgs['change:parent'] - 'cell:change:children': Cell.EventArgs['change:children'] - 'cell:change:tools': Cell.EventArgs['change:tools'] - 'cell:change:view': Cell.EventArgs['change:view'] - 'cell:change:data': Cell.EventArgs['change:data'] - - 'cell:change:size': Cell.EventArgs['change:size'] - 'cell:change:angle': Cell.EventArgs['change:angle'] - 'cell:change:position': Cell.EventArgs['change:position'] - 'cell:change:ports': Cell.EventArgs['change:ports'] - 'cell:change:portMarkup': Cell.EventArgs['change:portMarkup'] - 'cell:change:portLabelMarkup': Cell.EventArgs['change:portLabelMarkup'] - 'cell:change:portContainerMarkup': Cell.EventArgs['change:portContainerMarkup'] - 'cell:ports:added': Cell.EventArgs['ports:added'] - 'cell:ports:removed': Cell.EventArgs['ports:removed'] - - 'cell:change:source': Cell.EventArgs['change:source'] - 'cell:change:target': Cell.EventArgs['change:target'] - 'cell:change:router': Cell.EventArgs['change:router'] - 'cell:change:connector': Cell.EventArgs['change:connector'] - 'cell:change:vertices': Cell.EventArgs['change:vertices'] - 'cell:change:labels': Cell.EventArgs['change:labels'] - 'cell:change:defaultLabel': Cell.EventArgs['change:defaultLabel'] - 'cell:change:vertexMarkup': Cell.EventArgs['change:vertexMarkup'] - 'cell:change:arrowheadMarkup': Cell.EventArgs['change:arrowheadMarkup'] - 'cell:vertexs:added': Cell.EventArgs['vertexs:added'] - 'cell:vertexs:removed': Cell.EventArgs['vertexs:removed'] - 'cell:labels:added': Cell.EventArgs['labels:added'] - 'cell:labels:removed': Cell.EventArgs['labels:removed'] - - 'cell:batch:start': Cell.EventArgs['batch:start'] - 'cell:batch:stop': Cell.EventArgs['batch:stop'] - } - - export interface NodeEventArgs { - 'node:transition:start': NodeEventCommonArgs & - Cell.EventArgs['transition:start'] - 'node:transition:progress': NodeEventCommonArgs & - Cell.EventArgs['transition:progress'] - 'node:transition:complete': NodeEventCommonArgs & - Cell.EventArgs['transition:complete'] - 'node:transition:stop': NodeEventCommonArgs & - Cell.EventArgs['transition:stop'] - 'node:transition:finish': NodeEventCommonArgs & - Cell.EventArgs['transition:finish'] - - 'node:changed': NodeEventCommonArgs & CellEventArgs['cell:changed'] - 'node:added': NodeEventCommonArgs & CellEventArgs['cell:added'] - 'node:removed': NodeEventCommonArgs & CellEventArgs['cell:removed'] - - 'node:change:*': NodeEventCommonArgs & Cell.EventArgs['change:*'] - 'node:change:attrs': NodeEventCommonArgs & Cell.EventArgs['change:attrs'] - 'node:change:zIndex': NodeEventCommonArgs & Cell.EventArgs['change:zIndex'] - 'node:change:markup': NodeEventCommonArgs & Cell.EventArgs['change:markup'] - 'node:change:visible': NodeEventCommonArgs & - Cell.EventArgs['change:visible'] - 'node:change:parent': NodeEventCommonArgs & Cell.EventArgs['change:parent'] - 'node:change:children': NodeEventCommonArgs & - Cell.EventArgs['change:children'] - 'node:change:tools': NodeEventCommonArgs & Cell.EventArgs['change:tools'] - 'node:change:view': NodeEventCommonArgs & Cell.EventArgs['change:view'] - 'node:change:data': NodeEventCommonArgs & Cell.EventArgs['change:data'] - - 'node:change:size': NodeEventCommonArgs & Cell.EventArgs['change:size'] - 'node:change:position': NodeEventCommonArgs & - Cell.EventArgs['change:position'] - 'node:change:angle': NodeEventCommonArgs & Cell.EventArgs['change:angle'] - 'node:change:ports': NodeEventCommonArgs & Cell.EventArgs['change:ports'] - 'node:change:portMarkup': NodeEventCommonArgs & - Cell.EventArgs['change:portMarkup'] - 'node:change:portLabelMarkup': NodeEventCommonArgs & - Cell.EventArgs['change:portLabelMarkup'] - 'node:change:portContainerMarkup': NodeEventCommonArgs & - Cell.EventArgs['change:portContainerMarkup'] - 'node:ports:added': NodeEventCommonArgs & Cell.EventArgs['ports:added'] - 'node:ports:removed': NodeEventCommonArgs & Cell.EventArgs['ports:removed'] - - 'node:batch:start': NodeEventCommonArgs & Cell.EventArgs['batch:start'] - 'node:batch:stop': NodeEventCommonArgs & Cell.EventArgs['batch:stop'] - - // 'node:translate': NodeEventCommonArgs - // 'node:translating': NodeEventCommonArgs - // 'node:translated': NodeEventCommonArgs - // 'node:resize': NodeEventCommonArgs - // 'node:resizing': NodeEventCommonArgs - // 'node:resized': NodeEventCommonArgs - // 'node:rotate': NodeEventCommonArgs - // 'node:rotating': NodeEventCommonArgs - // 'node:rotated': NodeEventCommonArgs - } - - export interface EdgeEventArgs { - 'edge:transition:start': EdgeEventCommonArgs & - Cell.EventArgs['transition:start'] - 'edge:transition:progress': EdgeEventCommonArgs & - Cell.EventArgs['transition:progress'] - 'edge:transition:complete': EdgeEventCommonArgs & - Cell.EventArgs['transition:complete'] - 'edge:transition:stop': EdgeEventCommonArgs & - Cell.EventArgs['transition:stop'] - 'edge:transition:finish': EdgeEventCommonArgs & - Cell.EventArgs['transition:finish'] - - 'edge:changed': EdgeEventCommonArgs & CellEventArgs['cell:changed'] - 'edge:added': EdgeEventCommonArgs & CellEventArgs['cell:added'] - 'edge:removed': EdgeEventCommonArgs & CellEventArgs['cell:removed'] - - 'edge:change:*': EdgeEventCommonArgs & Cell.EventArgs['change:*'] - 'edge:change:attrs': EdgeEventCommonArgs & Cell.EventArgs['change:attrs'] - 'edge:change:zIndex': EdgeEventCommonArgs & Cell.EventArgs['change:zIndex'] - 'edge:change:markup': EdgeEventCommonArgs & Cell.EventArgs['change:markup'] - 'edge:change:visible': EdgeEventCommonArgs & - Cell.EventArgs['change:visible'] - 'edge:change:parent': EdgeEventCommonArgs & Cell.EventArgs['change:parent'] - 'edge:change:children': EdgeEventCommonArgs & - Cell.EventArgs['change:children'] - 'edge:change:tools': EdgeEventCommonArgs & Cell.EventArgs['change:tools'] - 'edge:change:data': EdgeEventCommonArgs & Cell.EventArgs['change:data'] - - 'edge:change:source': EdgeEventCommonArgs & Cell.EventArgs['change:source'] - 'edge:change:target': EdgeEventCommonArgs & Cell.EventArgs['change:target'] - 'edge:change:router': EdgeEventCommonArgs & Cell.EventArgs['change:router'] - 'edge:change:connector': EdgeEventCommonArgs & - Cell.EventArgs['change:connector'] - 'edge:change:vertices': EdgeEventCommonArgs & - Cell.EventArgs['change:vertices'] - 'edge:change:labels': EdgeEventCommonArgs & Cell.EventArgs['change:labels'] - 'edge:change:defaultLabel': EdgeEventCommonArgs & - Cell.EventArgs['change:defaultLabel'] - 'edge:change:vertexMarkup': EdgeEventCommonArgs & - Cell.EventArgs['change:vertexMarkup'] - 'edge:change:arrowheadMarkup': EdgeEventCommonArgs & - Cell.EventArgs['change:arrowheadMarkup'] - 'edge:vertexs:added': EdgeEventCommonArgs & Cell.EventArgs['vertexs:added'] - 'edge:vertexs:removed': EdgeEventCommonArgs & - Cell.EventArgs['vertexs:removed'] - 'edge:labels:added': EdgeEventCommonArgs & Cell.EventArgs['labels:added'] - 'edge:labels:removed': EdgeEventCommonArgs & - Cell.EventArgs['labels:removed'] - - 'edge:batch:start': EdgeEventCommonArgs & Cell.EventArgs['batch:start'] - 'edge:batch:stop': EdgeEventCommonArgs & Cell.EventArgs['batch:stop'] - } -} diff --git a/packages/x6-next/src/model/edge.ts b/packages/x6-next/src/model/edge.ts deleted file mode 100644 index b9d7dc434e7..00000000000 --- a/packages/x6-next/src/model/edge.ts +++ /dev/null @@ -1,1207 +0,0 @@ -import { ObjectExt, StringExt, Registry, Size, KeyValue } from '@antv/x6-common' -import { Point, Polyline } from '@antv/x6-geometry' -import { - Attr, - Router, - Connector, - EdgeAnchor, - NodeAnchor, - ConnectionPoint, -} from '../registry' -import { Markup } from '../view/markup' -import { ShareRegistry } from './registry' -import { Store } from './store' -import { Cell } from './cell' -import { Node } from './node' - -export class Edge< - Properties extends Edge.Properties = Edge.Properties, -> extends Cell { - protected static defaults: Edge.Defaults = {} - protected readonly store: Store - - protected get [Symbol.toStringTag]() { - return Edge.toStringTag - } - - constructor(metadata: Edge.Metadata = {}) { - super(metadata) - } - - protected preprocess(metadata: Edge.Metadata, ignoreIdCheck?: boolean) { - const { - source, - sourceCell, - sourcePort, - sourcePoint, - target, - targetCell, - targetPort, - targetPoint, - ...others - } = metadata - - const data = others as Edge.BaseOptions - const isValidId = (val: any): val is string => - typeof val === 'string' || typeof val === 'number' - - if (source != null) { - if (Cell.isCell(source)) { - data.source = { cell: source.id } - } else if (isValidId(source)) { - data.source = { cell: source } - } else if (Point.isPoint(source)) { - data.source = source.toJSON() - } else if (Array.isArray(source)) { - data.source = { x: source[0], y: source[1] } - } else { - const cell = (source as Edge.TerminalCellLooseData).cell - if (Cell.isCell(cell)) { - data.source = { - ...source, - cell: cell.id, - } - } else { - data.source = source as Edge.TerminalCellData - } - } - } - - if (sourceCell != null || sourcePort != null) { - let terminal = data.source as Edge.TerminalCellData - if (sourceCell != null) { - const id = isValidId(sourceCell) ? sourceCell : sourceCell.id - if (terminal) { - terminal.cell = id - } else { - terminal = data.source = { cell: id } - } - } - - if (sourcePort != null && terminal) { - terminal.port = sourcePort - } - } else if (sourcePoint != null) { - data.source = Point.create(sourcePoint).toJSON() - } - - if (target != null) { - if (Cell.isCell(target)) { - data.target = { cell: target.id } - } else if (isValidId(target)) { - data.target = { cell: target } - } else if (Point.isPoint(target)) { - data.target = target.toJSON() - } else if (Array.isArray(target)) { - data.target = { x: target[0], y: target[1] } - } else { - const cell = (target as Edge.TerminalCellLooseData).cell - if (Cell.isCell(cell)) { - data.target = { - ...target, - cell: cell.id, - } - } else { - data.target = target as Edge.TerminalCellData - } - } - } - - if (targetCell != null || targetPort != null) { - let terminal = data.target as Edge.TerminalCellData - - if (targetCell != null) { - const id = isValidId(targetCell) ? targetCell : targetCell.id - if (terminal) { - terminal.cell = id - } else { - terminal = data.target = { cell: id } - } - } - - if (targetPort != null && terminal) { - terminal.port = targetPort - } - } else if (targetPoint != null) { - data.target = Point.create(targetPoint).toJSON() - } - - return super.preprocess(data, ignoreIdCheck) - } - - protected setup() { - super.setup() - this.on('change:labels', (args) => this.onLabelsChanged(args)) - this.on('change:vertices', (args) => this.onVertexsChanged(args)) - } - - isEdge(): this is Edge { - return true - } - - // #region terminal - - disconnect(options: Edge.SetOptions = {}) { - this.store.set( - { - source: { x: 0, y: 0 }, - target: { x: 0, y: 0 }, - }, - options, - ) - return this - } - - get source() { - return this.getSource() - } - - set source(data: Edge.TerminalData) { - this.setSource(data) - } - - getSource() { - return this.getTerminal('source') - } - - getSourceCellId() { - return (this.source as Edge.TerminalCellData).cell - } - - getSourcePortId() { - return (this.source as Edge.TerminalCellData).port - } - - setSource( - node: Node, - args?: Edge.SetCellTerminalArgs, - options?: Edge.SetOptions, - ): this - setSource( - edge: Edge, - args?: Edge.SetEdgeTerminalArgs, - options?: Edge.SetOptions, - ): this - setSource( - point: Point | Point.PointLike, - args?: Edge.SetTerminalCommonArgs, - options?: Edge.SetOptions, - ): this - setSource(args: Edge.TerminalData, options?: Edge.SetOptions): this - setSource( - source: Node | Edge | Point | Point.PointLike | Edge.TerminalData, - args?: Edge.SetTerminalCommonArgs | Edge.SetOptions, - options: Edge.SetOptions = {}, - ) { - return this.setTerminal('source', source, args, options) - } - - get target() { - return this.getTarget() - } - - set target(data: Edge.TerminalData) { - this.setTarget(data) - } - - getTarget() { - return this.getTerminal('target') - } - - getTargetCellId() { - return (this.target as Edge.TerminalCellData).cell - } - - getTargetPortId() { - return (this.target as Edge.TerminalCellData).port - } - - setTarget( - edge: Node, - args?: Edge.SetCellTerminalArgs, - options?: Edge.SetOptions, - ): this - setTarget( - edge: Edge, - args?: Edge.SetEdgeTerminalArgs, - options?: Edge.SetOptions, - ): this - setTarget( - point: Point | Point.PointLike, - args?: Edge.SetTerminalCommonArgs, - options?: Edge.SetOptions, - ): this - setTarget(args: Edge.TerminalData, options?: Edge.SetOptions): this - setTarget( - target: Node | Edge | Point | Point.PointLike | Edge.TerminalData, - args?: Edge.SetTerminalCommonArgs | Edge.SetOptions, - options: Edge.SetOptions = {}, - ) { - return this.setTerminal('target', target, args, options) - } - - getTerminal(type: Edge.TerminalType) { - return { ...this.store.get(type) } as Edge.TerminalData - } - - setTerminal( - type: Edge.TerminalType, - terminal: Node | Edge | Point | Point.PointLike | Edge.TerminalData, - args?: Edge.SetTerminalCommonArgs | Edge.SetOptions, - options: Edge.SetOptions = {}, - ): this { - // `terminal` is a cell - if (Cell.isCell(terminal)) { - this.store.set( - type, - ObjectExt.merge({}, args, { cell: terminal.id }), - options, - ) - return this - } - - // `terminal` is a point-like object - const p = terminal as Point.PointLike - if (Point.isPoint(terminal) || (p.x != null && p.y != null)) { - this.store.set( - type, - ObjectExt.merge({}, args, { x: p.x, y: p.y }), - options, - ) - return this - } - - // `terminal` is an object - this.store.set( - type, - ObjectExt.cloneDeep(terminal as Edge.TerminalData), - options, - ) - - return this - } - - getSourcePoint() { - return this.getTerminalPoint('source') - } - - getTargetPoint() { - return this.getTerminalPoint('target') - } - - protected getTerminalPoint(type: Edge.TerminalType): Point { - const terminal = this[type] - if (Point.isPointLike(terminal)) { - return Point.create(terminal) - } - - const cell = this.getTerminalCell(type) - if (cell) { - return cell.getConnectionPoint(this, type) - } - - return new Point() - } - - getSourceCell() { - return this.getTerminalCell('source') - } - - getTargetCell() { - return this.getTerminalCell('target') - } - - protected getTerminalCell(type: Edge.TerminalType) { - if (this.model) { - const cellId = - type === 'source' ? this.getSourceCellId() : this.getTargetCellId() - if (cellId) { - return this.model.getCell(cellId) - } - } - - return null - } - - getSourceNode() { - return this.getTerminalNode('source') - } - - getTargetNode() { - return this.getTerminalNode('target') - } - - protected getTerminalNode(type: Edge.TerminalType): Node | null { - let cell: Cell | null = this // eslint-disable-line - const visited: { [id: string]: boolean } = {} - - while (cell && cell.isEdge()) { - if (visited[cell.id]) { - return null - } - visited[cell.id] = true - cell = cell.getTerminalCell(type) - } - - return cell && cell.isNode() ? cell : null - } - - // #endregion - - // #region router - - get router() { - return this.getRouter() - } - - set router(data: Edge.RouterData | undefined) { - if (data == null) { - this.removeRouter() - } else { - this.setRouter(data) - } - } - - getRouter() { - return this.store.get('router') - } - - setRouter(name: string, args?: KeyValue, options?: Edge.SetOptions): this - setRouter(router: Edge.RouterData, options?: Edge.SetOptions): this - setRouter( - name?: string | Edge.RouterData, - args?: KeyValue, - options?: Edge.SetOptions, - ) { - if (typeof name === 'object') { - this.store.set('router', name, args) - } else { - this.store.set('router', { name, args }, options) - } - return this - } - - removeRouter(options: Edge.SetOptions = {}) { - this.store.remove('router', options) - return this - } - - // #endregion - - // #region connector - - get connector() { - return this.getConnector() - } - - set connector(data: Edge.ConnectorData | undefined) { - if (data == null) { - this.removeConnector() - } else { - this.setConnector(data) - } - } - - getConnector() { - return this.store.get('connector') - } - - setConnector(name: string, args?: KeyValue, options?: Edge.SetOptions): this - setConnector(connector: Edge.ConnectorData, options?: Edge.SetOptions): this - setConnector( - name?: string | Edge.ConnectorData, - args?: KeyValue | Edge.SetOptions, - options?: Edge.SetOptions, - ) { - if (typeof name === 'object') { - this.store.set('connector', name, args) - } else { - this.store.set('connector', { name, args }, options) - } - return this - } - - removeConnector(options: Edge.SetOptions = {}) { - return this.store.remove('connector', options) - } - - // #endregion - - // #region labels - - getDefaultLabel(): Edge.Label { - const ctor = this.constructor as Edge.Definition - const defaults = this.store.get('defaultLabel') || ctor.defaultLabel || {} - return ObjectExt.cloneDeep(defaults) - } - - get labels() { - return this.getLabels() - } - - set labels(labels: Edge.Label[]) { - this.setLabels(labels) - } - - getLabels(): Edge.Label[] { - return [...this.store.get('labels', [])].map((item) => - this.parseLabel(item), - ) - } - - setLabels( - labels: Edge.Label | Edge.Label[] | string | string[], - options: Edge.SetOptions = {}, - ) { - this.store.set('labels', Array.isArray(labels) ? labels : [labels], options) - return this - } - - insertLabel( - label: Edge.Label | string, - index?: number, - options: Edge.SetOptions = {}, - ) { - const labels = this.getLabels() - const len = labels.length - let idx = index != null && Number.isFinite(index) ? index : len - if (idx < 0) { - idx = len + idx + 1 - } - - labels.splice(idx, 0, this.parseLabel(label)) - return this.setLabels(labels, options) - } - - appendLabel(label: Edge.Label | string, options: Edge.SetOptions = {}) { - return this.insertLabel(label, -1, options) - } - - getLabelAt(index: number) { - const labels = this.getLabels() - if (index != null && Number.isFinite(index)) { - return this.parseLabel(labels[index]) - } - return null - } - - setLabelAt( - index: number, - label: Edge.Label | string, - options: Edge.SetOptions = {}, - ) { - if (index != null && Number.isFinite(index)) { - const labels = this.getLabels() - labels[index] = this.parseLabel(label) - this.setLabels(labels, options) - } - return this - } - - removeLabelAt(index: number, options: Edge.SetOptions = {}) { - const labels = this.getLabels() - const idx = index != null && Number.isFinite(index) ? index : -1 - - const removed = labels.splice(idx, 1) - this.setLabels(labels, options) - return removed.length ? removed[0] : null - } - - protected parseLabel(label: string | Edge.Label) { - if (typeof label === 'string') { - const ctor = this.constructor as Edge.Definition - return ctor.parseStringLabel(label) - } - return label - } - - protected onLabelsChanged({ - previous, - current, - }: Cell.ChangeArgs) { - const added = - previous && current - ? current.filter((label1) => { - if ( - !previous.find( - (label2) => - label1 === label2 || ObjectExt.isEqual(label1, label2), - ) - ) { - return label1 - } - return null - }) - : current - ? [...current] - : [] - - const removed = - previous && current - ? previous.filter((label1) => { - if ( - !current.find( - (label2) => - label1 === label2 || ObjectExt.isEqual(label1, label2), - ) - ) { - return label1 - } - return null - }) - : previous - ? [...previous] - : [] - - if (added.length > 0) { - this.notify('labels:added', { added, cell: this, edge: this }) - } - - if (removed.length > 0) { - this.notify('labels:removed', { removed, cell: this, edge: this }) - } - } - - // #endregion - - // #region vertices - get vertices() { - return this.getVertices() - } - - set vertices(vertices: Point.PointLike | Point.PointLike[]) { - this.setVertices(vertices) - } - - getVertices() { - return [...this.store.get('vertices', [])] - } - - setVertices( - vertices: Point.PointLike | Point.PointLike[], - options: Edge.SetOptions = {}, - ) { - const points = Array.isArray(vertices) ? vertices : [vertices] - this.store.set( - 'vertices', - points.map((p) => Point.toJSON(p)), - options, - ) - return this - } - - insertVertex( - vertice: Point.PointLike, - index?: number, - options: Edge.SetOptions = {}, - ) { - const vertices = this.getVertices() - const len = vertices.length - let idx = index != null && Number.isFinite(index) ? index : len - if (idx < 0) { - idx = len + idx + 1 - } - - vertices.splice(idx, 0, Point.toJSON(vertice)) - return this.setVertices(vertices, options) - } - - appendVertex(vertex: Point.PointLike, options: Edge.SetOptions = {}) { - return this.insertVertex(vertex, -1, options) - } - - getVertexAt(index: number) { - if (index != null && Number.isFinite(index)) { - const vertices = this.getVertices() - return vertices[index] - } - return null - } - - setVertexAt( - index: number, - vertice: Point.PointLike, - options: Edge.SetOptions = {}, - ) { - if (index != null && Number.isFinite(index)) { - const vertices = this.getVertices() - vertices[index] = vertice - this.setVertices(vertices, options) - } - return this - } - - removeVertexAt(index: number, options: Edge.SetOptions = {}) { - const vertices = this.getVertices() - const idx = index != null && Number.isFinite(index) ? index : -1 - vertices.splice(idx, 1) - return this.setVertices(vertices, options) - } - - protected onVertexsChanged({ - previous, - current, - }: Cell.ChangeArgs) { - const added = - previous && current - ? current.filter((p1) => { - if (!previous.find((p2) => Point.equals(p1, p2))) { - return p1 - } - return null - }) - : current - ? [...current] - : [] - - const removed = - previous && current - ? previous.filter((p1) => { - if (!current.find((p2) => Point.equals(p1, p2))) { - return p1 - } - return null - }) - : previous - ? [...previous] - : [] - - if (added.length > 0) { - this.notify('vertexs:added', { added, cell: this, edge: this }) - } - - if (removed.length > 0) { - this.notify('vertexs:removed', { removed, cell: this, edge: this }) - } - } - - // #endregion - - // #region markup - - getDefaultMarkup() { - return this.store.get('defaultMarkup') || Markup.getEdgeMarkup() - } - - getMarkup() { - return super.getMarkup() || this.getDefaultMarkup() - } - - // #endregion - - // #region transform - - /** - * Translate the edge vertices (and source and target if they are points) - * by `tx` pixels in the x-axis and `ty` pixels in the y-axis. - */ - translate(tx: number, ty: number, options: Cell.TranslateOptions = {}) { - options.translateBy = options.translateBy || this.id - options.tx = tx - options.ty = ty - - return this.applyToPoints( - (p) => ({ - x: (p.x || 0) + tx, - y: (p.y || 0) + ty, - }), - options, - ) - } - - /** - * Scales the edge's points (vertices) relative to the given origin. - */ - scale( - sx: number, - sy: number, - origin?: Point | Point.PointLike, - options: Edge.SetOptions = {}, - ) { - return this.applyToPoints((p) => { - return Point.create(p).scale(sx, sy, origin).toJSON() - }, options) - } - - protected applyToPoints( - worker: (p: Point.PointLike) => Point.PointLike, - options: Edge.SetOptions = {}, - ) { - const attrs: { - source?: Edge.TerminalPointData - target?: Edge.TerminalPointData - vertices?: Point.PointLike[] - } = {} - - const source = this.getSource() - const target = this.getTarget() - if (Point.isPointLike(source)) { - attrs.source = worker(source) - } - - if (Point.isPointLike(target)) { - attrs.target = worker(target) - } - - const vertices = this.getVertices() - if (vertices.length > 0) { - attrs.vertices = vertices.map(worker) - } - - this.store.set(attrs, options) - return this - } - - // #endregion - - // #region common - - getBBox() { - return this.getPolyline().bbox() - } - - getConnectionPoint() { - return this.getPolyline().pointAt(0.5)! - } - - getPolyline() { - const points = [ - this.getSourcePoint(), - ...this.getVertices().map((vertice) => Point.create(vertice)), - this.getTargetPoint(), - ] - return new Polyline(points) - } - - updateParent(options?: Edge.SetOptions) { - let newParent: Cell | null = null - - const source = this.getSourceCell() - const target = this.getTargetCell() - const prevParent = this.getParent() - - if (source && target) { - if (source === target || source.isDescendantOf(target)) { - newParent = target - } else if (target.isDescendantOf(source)) { - newParent = source - } else { - newParent = Cell.getCommonAncestor(source, target) - } - } - - // Unembeds the edge if source and target has no common - // ancestor or common ancestor changed - if (prevParent && (!newParent || newParent.id !== prevParent.id)) { - prevParent.unembed(this, options) - } - - if (newParent) { - newParent.embed(this, options) - } - - return newParent - } - - hasLoop(options: { deep?: boolean } = {}) { - const source = this.getSource() as Edge.TerminalCellData - const target = this.getTarget() as Edge.TerminalCellData - const sourceId = source.cell - const targetId = target.cell - - if (!sourceId || !targetId) { - return false - } - - let loop = sourceId === targetId - - // Note that there in the deep mode a edge can have a loop, - // even if it connects only a parent and its embed. - // A loop "target equals source" is valid in both shallow and deep mode. - // eslint-disable-next-line - if (!loop && options.deep && this._model) { - const sourceCell = this.getSourceCell() - const targetCell = this.getTargetCell() - - if (sourceCell && targetCell) { - loop = - sourceCell.isAncestorOf(targetCell, options) || - targetCell.isAncestorOf(sourceCell, options) - } - } - - return loop - } - - getFragmentAncestor(): Cell | null { - const cells = [this, this.getSourceNode(), this.getTargetNode()].filter( - (item) => item != null, - ) - return this.getCommonAncestor(...cells) - } - - isFragmentDescendantOf(cell: Cell) { - const ancestor = this.getFragmentAncestor() - return ( - !!ancestor && (ancestor.id === cell.id || ancestor.isDescendantOf(cell)) - ) - } - - // #endregion -} - -export namespace Edge { - export type RouterData = Router.NativeItem | Router.ManaualItem - export type ConnectorData = Connector.NativeItem | Connector.ManaualItem -} - -export namespace Edge { - interface Common extends Cell.Common { - source?: TerminalData - target?: TerminalData - router?: RouterData - connector?: ConnectorData - labels?: Label[] | string[] - defaultLabel?: Label - vertices?: (Point.PointLike | Point.PointData)[] - defaultMarkup?: Markup - } - - interface TerminalOptions { - sourceCell?: Cell | string - sourcePort?: string - sourcePoint?: Point.PointLike | Point.PointData - targetCell?: Cell | string - targetPort?: string - targetPoint?: Point.PointLike | Point.PointData - source?: - | string - | Cell - | Point.PointLike - | Point.PointData - | TerminalPointData - | TerminalCellLooseData - target?: - | string - | Cell - | Point.PointLike - | Point.PointData - | TerminalPointData - | TerminalCellLooseData - } - - export interface BaseOptions extends Common, Cell.Metadata {} - - export interface Metadata - extends Omit, - TerminalOptions {} - - export interface Defaults extends Common, Cell.Defaults {} - - export interface Properties - extends Cell.Properties, - Omit {} - - export interface Config - extends Omit, - TerminalOptions, - Cell.Config {} -} - -export namespace Edge { - export interface SetOptions extends Cell.SetOptions {} - - export type TerminalType = 'source' | 'target' - - export interface SetTerminalCommonArgs { - selector?: string - magnet?: string - connectionPoint?: - | string - | ConnectionPoint.NativeItem - | ConnectionPoint.ManaualItem - } - - export interface SetCellTerminalArgs extends SetTerminalCommonArgs { - port?: string - priority?: boolean - anchor?: string | NodeAnchor.NativeItem | NodeAnchor.ManaualItem - } - - export interface SetEdgeTerminalArgs extends SetTerminalCommonArgs { - anchor?: string | EdgeAnchor.NativeItem | EdgeAnchor.ManaualItem - } - - export interface TerminalPointData - extends SetTerminalCommonArgs, - Point.PointLike {} - - export interface TerminalCellData extends SetCellTerminalArgs { - cell: string - port?: string - } - - export interface TerminalCellLooseData extends SetCellTerminalArgs { - cell: string | Cell - port?: string - } - - export type TerminalData = TerminalPointData | TerminalCellData - - export function equalTerminals(a: TerminalData, b: TerminalData) { - const a1 = a as TerminalCellData - const b1 = b as TerminalCellData - if (a1.cell === b1.cell) { - return a1.port === b1.port || (a1.port == null && b1.port == null) - } - return false - } -} - -export namespace Edge { - export interface Label extends KeyValue { - markup?: Markup - attrs?: Attr.CellAttrs - /** - * If the distance is in the `[0,1]` range (inclusive), then the position - * of the label is defined as a percentage of the total length of the edge - * (the normalized length). For example, passing the number `0.5` positions - * the label to the middle of the edge. - * - * If the distance is larger than `1` (exclusive), the label will be - * positioned distance pixels away from the beginning of the path along - * the edge. - * - * If the distance is a negative number, the label will be positioned - * distance pixels away from the end of the path along the edge. - */ - position?: LabelPosition - size?: Size - } - - export interface LabelPositionOptions { - /** - * Forces absolute coordinates for distance. - */ - absoluteDistance?: boolean - /** - * Forces reverse absolute coordinates (if absoluteDistance = true) - */ - reverseDistance?: boolean - /** - * Forces absolute coordinates for offset. - */ - absoluteOffset?: boolean - /** - * Auto-adjusts the angle of the label to match path gradient at position. - */ - keepGradient?: boolean - /** - * Whether rotates labels so they are never upside-down. - */ - ensureLegibility?: boolean - } - - export interface LabelPositionObject { - distance: number - offset?: - | number - | { - x?: number - y?: number - } - angle?: number - options?: LabelPositionOptions - } - - export type LabelPosition = number | LabelPositionObject - - export const defaultLabel: Label = { - markup: [ - { - tagName: 'rect', - selector: 'body', - }, - { - tagName: 'text', - selector: 'label', - }, - ], - attrs: { - text: { - fill: '#000', - fontSize: 14, - textAnchor: 'middle', - textVerticalAnchor: 'middle', - pointerEvents: 'none', - }, - rect: { - ref: 'label', - fill: '#fff', - rx: 3, - ry: 3, - refWidth: 1, - refHeight: 1, - refX: 0, - refY: 0, - }, - }, - position: { - distance: 0.5, - }, - } - - export function parseStringLabel(text: string): Label { - return { - attrs: { label: { text } }, - } - } -} - -export namespace Edge { - export const toStringTag = `X6.${Edge.name}` - - export function isEdge(instance: any): instance is Edge { - if (instance == null) { - return false - } - - if (instance instanceof Edge) { - return true - } - - const tag = instance[Symbol.toStringTag] - const edge = instance as Edge - - if ( - (tag == null || tag === toStringTag) && - typeof edge.isNode === 'function' && - typeof edge.isEdge === 'function' && - typeof edge.prop === 'function' && - typeof edge.attr === 'function' && - typeof edge.disconnect === 'function' && - typeof edge.getSource === 'function' && - typeof edge.getTarget === 'function' - ) { - return true - } - - return false - } -} - -export namespace Edge { - export const registry = Registry.create< - Definition, - never, - Config & { inherit?: string | Definition } - >({ - type: 'edge', - process(shape, options) { - if (ShareRegistry.exist(shape, false)) { - throw new Error( - `Edge with name '${shape}' was registered by anthor Node`, - ) - } - - if (typeof options === 'function') { - options.config({ shape }) - return options - } - - let parent = Edge - - // default inherit from 'dege' - const { inherit = 'edge', ...others } = options - if (typeof inherit === 'string') { - const base = this.get(inherit || 'edge') - if (base == null && inherit) { - this.onNotFound(inherit, 'inherited') - } else { - parent = base - } - } else { - parent = inherit - } - - if (others.constructorName == null) { - others.constructorName = shape - } - - const ctor: Definition = parent.define.call(parent, others) - ctor.config({ shape }) - return ctor as any - }, - }) - - ShareRegistry.setEdgeRegistry(registry) -} - -export namespace Edge { - type EdgeClass = typeof Edge - - export interface Definition extends EdgeClass { - new (metadata: T): Edge - } - - let counter = 0 - function getClassName(name?: string) { - if (name) { - return StringExt.pascalCase(name) - } - counter += 1 - return `CustomEdge${counter}` - } - - export function define(config: Config) { - const { constructorName, overwrite, ...others } = config - const ctor = ObjectExt.createClass( - getClassName(constructorName || others.shape), - this as Definition, - ) - - ctor.config(others) - - if (others.shape) { - registry.register(others.shape, ctor, overwrite) - } - - return ctor - } - - export function create(options: Metadata) { - const shape = options.shape || 'edge' - const Ctor = registry.get(shape) - if (Ctor) { - return new Ctor(options) - } - return registry.onNotFound(shape) - } -} - -export namespace Edge { - const shape = 'basic.edge' - Edge.config({ - shape, - propHooks(metadata: Properties) { - const { label, vertices, ...others } = metadata - if (label) { - if (others.labels == null) { - others.labels = [] - } - const formated = - typeof label === 'string' ? parseStringLabel(label) : label - others.labels.push(formated) - } - - if (vertices) { - if (Array.isArray(vertices)) { - others.vertices = vertices.map((item) => Point.create(item).toJSON()) - } - } - - return others - }, - }) - registry.register(shape, Edge) -} diff --git a/packages/x6-next/src/model/index.ts b/packages/x6-next/src/model/index.ts deleted file mode 100644 index 990424f14a5..00000000000 --- a/packages/x6-next/src/model/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './cell' -export * from './node' -export * from './edge' -export * from './model' -export * from './collection' diff --git a/packages/x6-next/src/model/model.ts b/packages/x6-next/src/model/model.ts deleted file mode 100644 index bf9935b3f91..00000000000 --- a/packages/x6-next/src/model/model.ts +++ /dev/null @@ -1,1482 +0,0 @@ -import { FunctionExt, Dijkstra, KeyValue, Basecoat } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { Cell } from './cell' -import { Edge } from './edge' -import { Node } from './node' -import { Collection } from './collection' -import { Graph } from '../graph' - -export class Model extends Basecoat { - public readonly collection: Collection - protected readonly batches: KeyValue = {} - protected readonly addings: WeakMap = new WeakMap() - public graph: Graph - protected nodes: KeyValue = {} - protected edges: KeyValue = {} - protected outgoings: KeyValue = {} - protected incomings: KeyValue = {} - - protected get [Symbol.toStringTag]() { - return Model.toStringTag - } - - constructor(cells: Cell[] = []) { - super() - this.collection = new Collection(cells) - this.setup() - } - - notify( - name: Key, - args: Model.EventArgs[Key], - ): this - notify(name: Exclude, args: any): this - notify( - name: Key, - args: Model.EventArgs[Key], - ) { - this.trigger(name, args) - const graph = this.graph - if (graph) { - if (name === 'sorted' || name === 'reseted' || name === 'updated') { - graph.trigger(`model:${name}`, args) - } else { - graph.trigger(name, args) - } - } - return this - } - - protected setup() { - const collection = this.collection - - collection.on('sorted', () => this.notify('sorted', null)) - collection.on('updated', (args) => this.notify('updated', args)) - collection.on('cell:change:zIndex', () => this.sortOnChangeZ()) - - collection.on('added', ({ cell }) => { - this.onCellAdded(cell) - }) - - collection.on('removed', (args) => { - const cell = args.cell - this.onCellRemoved(cell, args.options) - - // Should trigger remove-event manually after cell was removed. - this.notify('cell:removed', args) - if (cell.isNode()) { - this.notify('node:removed', { ...args, node: cell }) - } else if (cell.isEdge()) { - this.notify('edge:removed', { ...args, edge: cell }) - } - }) - - collection.on('reseted', (args) => { - this.onReset(args.current) - this.notify('reseted', args) - }) - - collection.on('edge:change:source', ({ edge }) => - this.onEdgeTerminalChanged(edge, 'source'), - ) - - collection.on('edge:change:target', ({ edge }) => { - this.onEdgeTerminalChanged(edge, 'target') - }) - } - - protected sortOnChangeZ() { - this.collection.sort() - } - - protected onCellAdded(cell: Cell) { - const cellId = cell.id - if (cell.isEdge()) { - // Auto update edge's parent - cell.updateParent() - this.edges[cellId] = true - this.onEdgeTerminalChanged(cell, 'source') - this.onEdgeTerminalChanged(cell, 'target') - } else { - this.nodes[cellId] = true - } - } - - protected onCellRemoved(cell: Cell, options: Collection.RemoveOptions) { - const cellId = cell.id - if (cell.isEdge()) { - delete this.edges[cellId] - - const source = cell.getSource() as Edge.TerminalCellData - const target = cell.getTarget() as Edge.TerminalCellData - if (source && source.cell) { - const cache = this.outgoings[source.cell] - const index = cache ? cache.indexOf(cellId) : -1 - if (index >= 0) { - cache.splice(index, 1) - if (cache.length === 0) { - delete this.outgoings[source.cell] - } - } - } - - if (target && target.cell) { - const cache = this.incomings[target.cell] - const index = cache ? cache.indexOf(cellId) : -1 - if (index >= 0) { - cache.splice(index, 1) - if (cache.length === 0) { - delete this.incomings[target.cell] - } - } - } - } else { - delete this.nodes[cellId] - } - - if (!options.clear) { - if (options.disconnectEdges) { - this.disconnectConnectedEdges(cell, options) - } else { - this.removeConnectedEdges(cell, options) - } - } - - if (cell.model === this) { - cell.model = null - } - } - - protected onReset(cells: Cell[]) { - this.nodes = {} - this.edges = {} - this.outgoings = {} - this.incomings = {} - cells.forEach((cell) => this.onCellAdded(cell)) - } - - protected onEdgeTerminalChanged(edge: Edge, type: Edge.TerminalType) { - const ref = type === 'source' ? this.outgoings : this.incomings - const prev = edge.previous(type) - - if (prev && prev.cell) { - const cache = ref[prev.cell] - const index = cache ? cache.indexOf(edge.id) : -1 - if (index >= 0) { - cache.splice(index, 1) - if (cache.length === 0) { - delete ref[prev.cell] - } - } - } - - const terminal = edge.getTerminal(type) as Edge.TerminalCellData - if (terminal && terminal.cell) { - const cache = ref[terminal.cell] || [] - const index = cache.indexOf(edge.id) - if (index === -1) { - cache.push(edge.id) - } - ref[terminal.cell] = cache - } - } - - protected prepareCell(cell: Cell, options: Collection.AddOptions) { - if (!cell.model && (!options || !options.dryrun)) { - cell.model = this - } - - if (cell.zIndex == null) { - cell.setZIndex(this.getMaxZIndex() + 1, { silent: true }) - } - - return cell - } - - resetCells(cells: Cell[], options: Collection.SetOptions = {}) { - // Do not update model at this time. Because if we just update the graph - // with the same json-data, the edge will reference to the old nodes. - cells.map((cell) => this.prepareCell(cell, { ...options, dryrun: true })) - this.collection.reset(cells, options) - // Update model and trigger edge update it's references - cells.map((cell) => this.prepareCell(cell, { options })) - return this - } - - clear(options: Cell.SetOptions = {}) { - const raw = this.getCells() - if (raw.length === 0) { - return this - } - const localOptions = { ...options, clear: true } - this.batchUpdate( - 'clear', - () => { - // The nodes come after the edges. - const cells = raw.sort((a, b) => { - const v1 = a.isEdge() ? 1 : 2 - const v2 = b.isEdge() ? 1 : 2 - return v1 - v2 - }) - - while (cells.length > 0) { - // Note that all the edges are removed first, so it's safe to - // remove the nodes without removing the connected edges first. - const cell = cells.shift() - if (cell) { - cell.remove(localOptions) - } - } - }, - localOptions, - ) - - return this - } - - addNode(metadata: Node | Node.Metadata, options: Model.AddOptions = {}) { - const node = Node.isNode(metadata) ? metadata : this.createNode(metadata) - this.addCell(node, options) - return node - } - - createNode(metadata: Node.Metadata) { - return Node.create(metadata) - } - - addEdge(metadata: Edge.Metadata | Edge, options: Model.AddOptions = {}) { - const edge = Edge.isEdge(metadata) ? metadata : this.createEdge(metadata) - this.addCell(edge, options) - return edge - } - - createEdge(metadata: Edge.Metadata) { - return Edge.create(metadata) - } - - addCell(cell: Cell | Cell[], options: Model.AddOptions = {}) { - if (Array.isArray(cell)) { - return this.addCells(cell, options) - } - - if (!this.collection.has(cell) && !this.addings.has(cell)) { - this.addings.set(cell, true) - this.collection.add(this.prepareCell(cell, options), options) - cell.eachChild((child) => this.addCell(child, options)) - this.addings.delete(cell) - } - - return this - } - - addCells(cells: Cell[], options: Model.AddOptions = {}) { - const count = cells.length - if (count === 0) { - return this - } - - const localOptions = { - ...options, - position: count - 1, - maxPosition: count - 1, - } - - this.startBatch('add', { ...localOptions, cells }) - cells.forEach((cell) => { - this.addCell(cell, localOptions) - localOptions.position -= 1 - }) - this.stopBatch('add', { ...localOptions, cells }) - - return this - } - - removeCell(cellId: string, options?: Collection.RemoveOptions): Cell | null - removeCell(cell: Cell, options?: Collection.RemoveOptions): Cell | null - removeCell( - obj: Cell | string, - options: Collection.RemoveOptions = {}, - ): Cell | null { - const cell = typeof obj === 'string' ? this.getCell(obj) : obj - if (cell && this.has(cell)) { - return this.collection.remove(cell, options) - } - return null - } - - updateCellId(cell: Cell, newId: string) { - this.startBatch('update', { id: newId }) - cell.prop('id', newId) - const newCell = cell.clone({ keepId: true }) - this.addCell(newCell) - - // update connected edge terminal - const edges = this.getConnectedEdges(cell) - edges.forEach((edge) => { - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - if (sourceCell === cell) { - edge.setSource({ - ...edge.getSource(), - cell: newId, - }) - } - if (targetCell === cell) { - edge.setTarget({ - ...edge.getTarget(), - cell: newId, - }) - } - }) - - this.removeCell(cell) - this.stopBatch('update', { id: newId }) - return newCell - } - - removeCells(cells: (Cell | string)[], options: Cell.RemoveOptions = {}) { - if (cells.length) { - return this.batchUpdate('remove', () => { - return cells.map((cell) => this.removeCell(cell as Cell, options)) - }) - } - return [] - } - - removeConnectedEdges(cell: Cell | string, options: Cell.RemoveOptions = {}) { - const edges = this.getConnectedEdges(cell) - edges.forEach((edge) => { - edge.remove(options) - }) - return edges - } - - disconnectConnectedEdges(cell: Cell | string, options: Edge.SetOptions = {}) { - const cellId = typeof cell === 'string' ? cell : cell.id - this.getConnectedEdges(cell).forEach((edge) => { - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - - if (sourceCell && sourceCell.id === cellId) { - edge.setSource({ x: 0, y: 0 }, options) - } - - if (targetCell && targetCell.id === cellId) { - edge.setTarget({ x: 0, y: 0 }, options) - } - }) - } - - has(id: string): boolean - has(cell: Cell): boolean - has(obj: string | Cell): boolean { - return this.collection.has(obj) - } - - total() { - return this.collection.length - } - - indexOf(cell: Cell) { - return this.collection.indexOf(cell) - } - - /** - * Returns a cell from the graph by its id. - */ - getCell(id: string) { - return this.collection.get(id) as T - } - - /** - * Returns all the nodes and edges in the graph. - */ - getCells() { - return this.collection.toArray() - } - - /** - * Returns the first cell (node or edge) in the graph. The first cell is - * defined as the cell with the lowest `zIndex`. - */ - getFirstCell() { - return this.collection.first() - } - - /** - * Returns the last cell (node or edge) in the graph. The last cell is - * defined as the cell with the highest `zIndex`. - */ - getLastCell() { - return this.collection.last() - } - - /** - * Returns the lowest `zIndex` value in the graph. - */ - getMinZIndex() { - const first = this.collection.first() - return first ? first.getZIndex() || 0 : 0 - } - - /** - * Returns the highest `zIndex` value in the graph. - */ - getMaxZIndex() { - const last = this.collection.last() - return last ? last.getZIndex() || 0 : 0 - } - - protected getCellsFromCache(cache: { - [key: string]: boolean - }) { - return cache - ? Object.keys(cache) - .map((id) => this.getCell(id)) - .filter((cell) => cell != null) - : [] - } - - /** - * Returns all the nodes in the graph. - */ - getNodes() { - return this.getCellsFromCache(this.nodes) - } - - /** - * Returns all the edges in the graph. - */ - getEdges() { - return this.getCellsFromCache(this.edges) - } - - /** - * Returns all outgoing edges for the node. - */ - getOutgoingEdges(cell: Cell | string) { - const cellId = typeof cell === 'string' ? cell : cell.id - const cellIds = this.outgoings[cellId] - return cellIds - ? cellIds - .map((id) => this.getCell(id) as Edge) - .filter((cell) => cell && cell.isEdge()) - : null - } - - /** - * Returns all incoming edges for the node. - */ - getIncomingEdges(cell: Cell | string) { - const cellId = typeof cell === 'string' ? cell : cell.id - const cellIds = this.incomings[cellId] - return cellIds - ? cellIds - .map((id) => this.getCell(id) as Edge) - .filter((cell) => cell && cell.isEdge()) - : null - } - - /** - * Returns edges connected with cell. - */ - getConnectedEdges( - cell: Cell | string, - options: Model.GetConnectedEdgesOptions = {}, - ) { - const result: Edge[] = [] - const node = typeof cell === 'string' ? this.getCell(cell) : cell - if (node == null) { - return result - } - - const cache: { [id: string]: boolean } = {} - const indirect = options.indirect - let incoming = options.incoming - let outgoing = options.outgoing - if (incoming == null && outgoing == null) { - incoming = outgoing = true - } - - const collect = (cell: Cell, isOutgoing: boolean) => { - const edges = isOutgoing - ? this.getOutgoingEdges(cell) - : this.getIncomingEdges(cell) - - if (edges != null) { - edges.forEach((edge) => { - if (cache[edge.id]) { - return - } - - result.push(edge) - cache[edge.id] = true - - if (indirect) { - if (incoming) { - collect(edge, false) - } - - if (outgoing) { - collect(edge, true) - } - } - }) - } - - if (indirect && cell.isEdge()) { - const terminal = isOutgoing - ? cell.getTargetCell() - : cell.getSourceCell() - if (terminal && terminal.isEdge()) { - if (!cache[terminal.id]) { - result.push(terminal) - collect(terminal, isOutgoing) - } - } - } - } - - if (outgoing) { - collect(node, true) - } - - if (incoming) { - collect(node, false) - } - - if (options.deep) { - const descendants = node.getDescendants({ deep: true }) - const embedsCache: KeyValue = {} - descendants.forEach((cell) => { - if (cell.isNode()) { - embedsCache[cell.id] = true - } - }) - - const collectSub = (cell: Cell, isOutgoing: boolean) => { - const edges = isOutgoing - ? this.getOutgoingEdges(cell.id) - : this.getIncomingEdges(cell.id) - - if (edges != null) { - edges.forEach((edge) => { - if (!cache[edge.id]) { - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - - if ( - !options.enclosed && - sourceCell && - embedsCache[sourceCell.id] && - targetCell && - embedsCache[targetCell.id] - ) { - return - } - - result.push(edge) - cache[edge.id] = true - } - }) - } - } - - descendants.forEach((cell) => { - if (cell.isEdge()) { - return - } - - if (outgoing) { - collectSub(cell, true) - } - - if (incoming) { - collectSub(cell, false) - } - }) - } - - return result - } - - protected isBoundary(cell: Cell | string, isOrigin: boolean) { - const node = typeof cell === 'string' ? this.getCell(cell) : cell - const arr = isOrigin - ? this.getIncomingEdges(node) - : this.getOutgoingEdges(node) - return arr == null || arr.length === 0 - } - - protected getBoundaryNodes(isOrigin: boolean) { - const result: Node[] = [] - Object.keys(this.nodes).forEach((nodeId) => { - if (this.isBoundary(nodeId, isOrigin)) { - const node = this.getCell(nodeId) - if (node) { - result.push(node) - } - } - }) - return result - } - - /** - * Returns an array of all the roots of the graph. - */ - getRoots() { - return this.getBoundaryNodes(true) - } - - /** - * Returns an array of all the leafs of the graph. - */ - getLeafs() { - return this.getBoundaryNodes(false) - } - - /** - * Returns `true` if the node is a root node, i.e. there is no edges - * coming to the node. - */ - isRoot(cell: Cell | string) { - return this.isBoundary(cell, true) - } - - /** - * Returns `true` if the node is a leaf node, i.e. there is no edges - * going out from the node. - */ - isLeaf(cell: Cell | string) { - return this.isBoundary(cell, false) - } - - /** - * Returns all the neighbors of node in the graph. Neighbors are all - * the nodes connected to node via either incoming or outgoing edge. - */ - getNeighbors(cell: Cell, options: Model.GetNeighborsOptions = {}) { - let incoming = options.incoming - let outgoing = options.outgoing - if (incoming == null && outgoing == null) { - incoming = outgoing = true - } - - const edges = this.getConnectedEdges(cell, options) - const map = edges.reduce>((memo, edge) => { - const hasLoop = edge.hasLoop(options) - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - - if ( - incoming && - sourceCell && - sourceCell.isNode() && - !memo[sourceCell.id] - ) { - if ( - hasLoop || - (sourceCell !== cell && - (!options.deep || !sourceCell.isDescendantOf(cell))) - ) { - memo[sourceCell.id] = sourceCell - } - } - - if ( - outgoing && - targetCell && - targetCell.isNode() && - !memo[targetCell.id] - ) { - if ( - hasLoop || - (targetCell !== cell && - (!options.deep || !targetCell.isDescendantOf(cell))) - ) { - memo[targetCell.id] = targetCell - } - } - - return memo - }, {}) - - if (cell.isEdge()) { - if (incoming) { - const sourceCell = cell.getSourceCell() - if (sourceCell && sourceCell.isNode() && !map[sourceCell.id]) { - map[sourceCell.id] = sourceCell - } - } - if (outgoing) { - const targetCell = cell.getTargetCell() - if (targetCell && targetCell.isNode() && !map[targetCell.id]) { - map[targetCell.id] = targetCell - } - } - } - - return Object.keys(map).map((id) => map[id]) - } - - /** - * Returns `true` if `cell2` is a neighbor of `cell1`. - */ - isNeighbor( - cell1: Cell, - cell2: Cell, - options: Model.GetNeighborsOptions = {}, - ) { - let incoming = options.incoming - let outgoing = options.outgoing - if (incoming == null && outgoing == null) { - incoming = outgoing = true - } - - return this.getConnectedEdges(cell1, options).some((edge) => { - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - - if (incoming && sourceCell && sourceCell.id === cell2.id) { - return true - } - - if (outgoing && targetCell && targetCell.id === cell2.id) { - return true - } - - return false - }) - } - - getSuccessors(cell: Cell, options: Model.GetPredecessorsOptions = {}) { - const successors: Cell[] = [] - this.search( - cell, - (curr, distance) => { - if (curr !== cell && this.matchDistance(distance, options.distance)) { - successors.push(curr) - } - }, - { ...options, outgoing: true }, - ) - return successors - } - - /** - * Returns `true` if `cell2` is a successor of `cell1`. - */ - isSuccessor( - cell1: Cell, - cell2: Cell, - options: Model.GetPredecessorsOptions = {}, - ) { - let result = false - this.search( - cell1, - (curr, distance) => { - if ( - curr === cell2 && - curr !== cell1 && - this.matchDistance(distance, options.distance) - ) { - result = true - return false - } - }, - { ...options, outgoing: true }, - ) - return result - } - - getPredecessors(cell: Cell, options: Model.GetPredecessorsOptions = {}) { - const predecessors: Cell[] = [] - this.search( - cell, - (curr, distance) => { - if (curr !== cell && this.matchDistance(distance, options.distance)) { - predecessors.push(curr) - } - }, - { ...options, incoming: true }, - ) - return predecessors - } - - /** - * Returns `true` if `cell2` is a predecessor of `cell1`. - */ - isPredecessor( - cell1: Cell, - cell2: Cell, - options: Model.GetPredecessorsOptions = {}, - ) { - let result = false - this.search( - cell1, - (curr, distance) => { - if ( - curr === cell2 && - curr !== cell1 && - this.matchDistance(distance, options.distance) - ) { - result = true - return false - } - }, - { ...options, incoming: true }, - ) - return result - } - - protected matchDistance( - distance: number, - preset?: number | number[] | ((d: number) => boolean), - ) { - if (preset == null) { - return true - } - - if (typeof preset === 'function') { - return preset(distance) - } - - if (Array.isArray(preset) && preset.includes(distance)) { - return true - } - - return distance === preset - } - - /** - * Returns the common ancestor of the passed cells. - */ - getCommonAncestor(...cells: (Cell | Cell[] | null | undefined)[]) { - const arr: Cell[] = [] - cells.forEach((item) => { - if (item) { - if (Array.isArray(item)) { - arr.push(...item) - } else { - arr.push(item) - } - } - }) - return Cell.getCommonAncestor(...arr) - } - - /** - * Returns an array of cells that result from finding nodes/edges that - * are connected to any of the cells in the cells array. This function - * loops over cells and if the current cell is a edge, it collects its - * source/target nodes; if it is an node, it collects its incoming and - * outgoing edges if both the edge terminal (source/target) are in the - * cells array. - */ - getSubGraph(cells: Cell[], options: Model.GetSubgraphOptions = {}) { - const subgraph: Cell[] = [] - const cache: KeyValue = {} - const nodes: Node[] = [] - const edges: Edge[] = [] - const collect = (cell: Cell) => { - if (!cache[cell.id]) { - subgraph.push(cell) - cache[cell.id] = cell - if (cell.isEdge()) { - edges.push(cell) - } - - if (cell.isNode()) { - nodes.push(cell) - } - } - } - - cells.forEach((cell) => { - collect(cell) - if (options.deep) { - const descendants = cell.getDescendants({ deep: true }) - descendants.forEach((descendant) => collect(descendant)) - } - }) - - edges.forEach((edge) => { - // For edges, include their source & target - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - if (sourceCell && !cache[sourceCell.id]) { - subgraph.push(sourceCell) - cache[sourceCell.id] = sourceCell - if (sourceCell.isNode()) { - nodes.push(sourceCell) - } - } - if (targetCell && !cache[targetCell.id]) { - subgraph.push(targetCell) - cache[targetCell.id] = targetCell - if (targetCell.isNode()) { - nodes.push(targetCell) - } - } - }) - - nodes.forEach((node) => { - // For nodes, include their connected edges if their source/target - // is in the subgraph. - const edges = this.getConnectedEdges(node, options) - edges.forEach((edge) => { - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - if ( - !cache[edge.id] && - sourceCell && - cache[sourceCell.id] && - targetCell && - cache[targetCell.id] - ) { - subgraph.push(edge) - cache[edge.id] = edge - } - }) - }) - - return subgraph - } - - /** - * Clones the whole subgraph (including all the connected links whose - * source/target is in the subgraph). If `options.deep` is `true`, also - * take into account all the embedded cells of all the subgraph cells. - * - * Returns a map of the form: { [original cell ID]: [clone] }. - */ - cloneSubGraph(cells: Cell[], options: Model.GetSubgraphOptions = {}) { - const subgraph = this.getSubGraph(cells, options) - return this.cloneCells(subgraph) - } - - cloneCells(cells: Cell[]) { - return Cell.cloneCells(cells) - } - - /** - * Returns an array of nodes whose bounding box contains point. - * Note that there can be more then one node as nodes might overlap. - */ - getNodesFromPoint(x: number, y: number): Node[] - getNodesFromPoint(p: Point.PointLike): Node[] - getNodesFromPoint(x: number | Point.PointLike, y?: number) { - const p = typeof x === 'number' ? { x, y: y || 0 } : x - return this.getNodes().filter((node) => { - return node.getBBox().containsPoint(p) - }) - } - - /** - * Returns an array of nodes whose bounding box top/left coordinate - * falls into the rectangle. - */ - getNodesInArea( - x: number, - y: number, - w: number, - h: number, - options?: Model.GetCellsInAreaOptions, - ): Node[] - getNodesInArea( - rect: Rectangle.RectangleLike, - options?: Model.GetCellsInAreaOptions, - ): Node[] - getNodesInArea( - x: number | Rectangle.RectangleLike, - y?: number | Model.GetCellsInAreaOptions, - w?: number, - h?: number, - options?: Model.GetCellsInAreaOptions, - ): Node[] { - const rect = - typeof x === 'number' - ? new Rectangle(x, y as number, w as number, h as number) - : Rectangle.create(x) - const opts = - typeof x === 'number' ? options : (y as Model.GetCellsInAreaOptions) - const strict = opts && opts.strict - return this.getNodes().filter((node) => { - const bbox = node.getBBox() - return strict ? rect.containsRect(bbox) : rect.isIntersectWithRect(bbox) - }) - } - - /** - * Returns an array of edges whose bounding box top/left coordinate - * falls into the rectangle. - */ - getEdgesInArea( - x: number, - y: number, - w: number, - h: number, - options?: Model.GetCellsInAreaOptions, - ): Edge[] - getEdgesInArea( - rect: Rectangle.RectangleLike, - options?: Model.GetCellsInAreaOptions, - ): Edge[] - getEdgesInArea( - x: number | Rectangle.RectangleLike, - y?: number | Model.GetCellsInAreaOptions, - w?: number, - h?: number, - options?: Model.GetCellsInAreaOptions, - ): Edge[] { - const rect = - typeof x === 'number' - ? new Rectangle(x, y as number, w as number, h as number) - : Rectangle.create(x) - const opts = - typeof x === 'number' ? options : (y as Model.GetCellsInAreaOptions) - const strict = opts && opts.strict - return this.getEdges().filter((edge) => { - const bbox = edge.getBBox() - if (bbox.width === 0) { - bbox.inflate(1, 0) - } else if (bbox.height === 0) { - bbox.inflate(0, 1) - } - return strict ? rect.containsRect(bbox) : rect.isIntersectWithRect(bbox) - }) - } - - getNodesUnderNode( - node: Node, - options: { - by?: 'bbox' | Rectangle.KeyPoint - } = {}, - ) { - const bbox = node.getBBox() - const nodes = - options.by == null || options.by === 'bbox' - ? this.getNodesInArea(bbox) - : this.getNodesFromPoint(bbox[options.by]) - - return nodes.filter( - (curr) => node.id !== curr.id && !curr.isDescendantOf(node), - ) - } - - /** - * Returns the bounding box that surrounds all cells in the graph. - */ - getAllCellsBBox() { - return this.getCellsBBox(this.getCells()) - } - - /** - * Returns the bounding box that surrounds all the given cells. - */ - getCellsBBox(cells: Cell[], options: Cell.GetCellsBBoxOptions = {}) { - return Cell.getCellsBBox(cells, options) - } - - // #region search - - search( - cell: Cell, - iterator: Model.SearchIterator, - options: Model.SearchOptions = {}, - ) { - if (options.breadthFirst) { - this.breadthFirstSearch(cell, iterator, options) - } else { - this.depthFirstSearch(cell, iterator, options) - } - } - - breadthFirstSearch( - cell: Cell, - iterator: Model.SearchIterator, - options: Model.GetNeighborsOptions = {}, - ) { - const queue: Cell[] = [] - const visited: KeyValue = {} - const distance: KeyValue = {} - - queue.push(cell) - distance[cell.id] = 0 - - while (queue.length > 0) { - const next = queue.shift() - if (next == null || visited[next.id]) { - continue - } - visited[next.id] = true - if (FunctionExt.call(iterator, this, next, distance[next.id]) === false) { - continue - } - const neighbors = this.getNeighbors(next, options) - neighbors.forEach((neighbor) => { - distance[neighbor.id] = distance[next.id] + 1 - queue.push(neighbor) - }) - } - } - - depthFirstSearch( - cell: Cell, - iterator: Model.SearchIterator, - options: Model.GetNeighborsOptions = {}, - ) { - const queue: Cell[] = [] - const visited: KeyValue = {} - const distance: KeyValue = {} - - queue.push(cell) - distance[cell.id] = 0 - - while (queue.length > 0) { - const next = queue.pop() - if (next == null || visited[next.id]) { - continue - } - visited[next.id] = true - - if (FunctionExt.call(iterator, this, next, distance[next.id]) === false) { - continue - } - - const neighbors = this.getNeighbors(next, options) - const lastIndex = queue.length - neighbors.forEach((neighbor) => { - distance[neighbor.id] = distance[next.id] + 1 - queue.splice(lastIndex, 0, neighbor) - }) - } - } - - // #endregion - - // #region shortest path - - /** * - * Returns an array of IDs of nodes on the shortest - * path between source and target. - */ - getShortestPath( - source: Cell | string, - target: Cell | string, - options: Model.GetShortestPathOptions = {}, - ) { - const adjacencyList: Dijkstra.AdjacencyList = {} - this.getEdges().forEach((edge) => { - const sourceId = edge.getSourceCellId() - const targetId = edge.getTargetCellId() - if (sourceId && targetId) { - if (!adjacencyList[sourceId]) { - adjacencyList[sourceId] = [] - } - if (!adjacencyList[targetId]) { - adjacencyList[targetId] = [] - } - - adjacencyList[sourceId].push(targetId) - if (!options.directed) { - adjacencyList[targetId].push(sourceId) - } - } - }) - - const sourceId = typeof source === 'string' ? source : source.id - const previous = Dijkstra.run(adjacencyList, sourceId, options.weight) - - const path = [] - let targetId = typeof target === 'string' ? target : target.id - if (previous[targetId]) { - path.push(targetId) - } - - while ((targetId = previous[targetId])) { - path.unshift(targetId) - } - return path - } - - // #endregion - - // #region transform - - /** - * Translate all cells in the graph by `tx` and `ty` pixels. - */ - translate(tx: number, ty: number, options: Cell.TranslateOptions) { - this.getCells() - .filter((cell) => !cell.hasParent()) - .forEach((cell) => cell.translate(tx, ty, options)) - - return this - } - - resize(width: number, height: number, options: Cell.SetOptions) { - return this.resizeCells(width, height, this.getCells(), options) - } - - resizeCells( - width: number, - height: number, - cells: Cell[], - options: Cell.SetOptions = {}, - ) { - const bbox = this.getCellsBBox(cells) - if (bbox) { - const sx = Math.max(width / bbox.width, 0) - const sy = Math.max(height / bbox.height, 0) - const origin = bbox.getOrigin() - cells.forEach((cell) => cell.scale(sx, sy, origin, options)) - } - - return this - } - - // #endregion - - // #region serialize/deserialize - - toJSON(options: Model.ToJSONOptions = {}) { - return Model.toJSON(this.getCells(), options) - } - - parseJSON(data: Model.FromJSONData) { - return Model.fromJSON(data) - } - - fromJSON(data: Model.FromJSONData, options: Model.FromJSONOptions = {}) { - const cells = this.parseJSON(data) - this.resetCells(cells, options) - return this - } - - // #endregion - - // #region batch - - startBatch(name: Model.BatchName, data: KeyValue = {}) { - this.batches[name] = (this.batches[name] || 0) + 1 - this.notify('batch:start', { name, data }) - return this - } - - stopBatch(name: Model.BatchName, data: KeyValue = {}) { - this.batches[name] = (this.batches[name] || 0) - 1 - this.notify('batch:stop', { name, data }) - return this - } - - batchUpdate(name: Model.BatchName, execute: () => T, data: KeyValue = {}) { - this.startBatch(name, data) - const result = execute() - this.stopBatch(name, data) - return result - } - - hasActiveBatch( - name: Model.BatchName | Model.BatchName[] = Object.keys( - this.batches, - ) as Model.BatchName[], - ) { - const names = Array.isArray(name) ? name : [name] - return names.some((batch) => this.batches[batch] > 0) - } - - // #endregion -} - -export namespace Model { - export const toStringTag = `X6.${Model.name}` - - export function isModel(instance: any): instance is Model { - if (instance == null) { - return false - } - - if (instance instanceof Model) { - return true - } - - const tag = instance[Symbol.toStringTag] - const model = instance as Model - - if ( - (tag == null || tag === toStringTag) && - typeof model.addNode === 'function' && - typeof model.addEdge === 'function' && - model.collection != null - ) { - return true - } - - return false - } -} -export namespace Model { - export interface SetOptions extends Collection.SetOptions {} - export interface AddOptions extends Collection.AddOptions {} - export interface RemoveOptions extends Collection.RemoveOptions {} - export interface FromJSONOptions extends Collection.SetOptions {} - - export type FromJSONData = - | (Node.Metadata | Edge.Metadata)[] - | (Partial> & { - nodes?: Node.Metadata[] - edges?: Edge.Metadata[] - }) - export type ToJSONData = { - cells: Cell.Properties[] - } - - export interface GetCellsInAreaOptions { - strict?: boolean - } - - export interface SearchOptions extends GetNeighborsOptions { - breadthFirst?: boolean - } - - export type SearchIterator = ( - this: Model, - cell: Cell, - distance: number, - ) => any - - export interface GetNeighborsOptions { - deep?: boolean - incoming?: boolean - outgoing?: boolean - indirect?: boolean - } - - export interface GetConnectedEdgesOptions extends GetNeighborsOptions { - enclosed?: boolean - } - - export interface GetSubgraphOptions { - deep?: boolean - } - - export interface GetShortestPathOptions { - directed?: boolean - weight?: Dijkstra.Weight - } - - export interface GetPredecessorsOptions extends Cell.GetDescendantsOptions { - distance?: number | number[] | ((distance: number) => boolean) - } -} - -export namespace Model { - export interface EventArgs - extends Collection.CellEventArgs, - Collection.NodeEventArgs, - Collection.EdgeEventArgs { - 'batch:start': { - name: BatchName | string - data: KeyValue - } - 'batch:stop': { - name: BatchName | string - data: KeyValue - } - - sorted: null - reseted: { - current: Cell[] - previous: Cell[] - options: Collection.SetOptions - } - updated: { - added: Cell[] - merged: Cell[] - removed: Cell[] - options: Collection.SetOptions - } - } - - export type BatchName = - | 'update' - | 'add' - | 'remove' - | 'clear' - | 'to-back' - | 'to-front' - | 'scale' - | 'resize' - | 'rotate' - | 'translate' - | 'mouse' - | 'layout' - | 'add-edge' - | 'fit-embeds' - | 'dnd' - | 'halo' - | 'cut' - | 'paste' - | 'knob' - | 'add-vertex' - | 'move-anchor' - | 'move-vertex' - | 'move-segment' - | 'move-arrowhead' - | 'move-selection' -} - -export namespace Model { - export interface ToJSONOptions extends Cell.ToJSONOptions {} - - export function toJSON(cells: Cell[], options: ToJSONOptions = {}) { - return { - cells: cells.map((cell) => cell.toJSON(options)), - } - } - - export function fromJSON(data: FromJSONData) { - const cells: Cell.Metadata[] = [] - if (Array.isArray(data)) { - cells.push(...data) - } else { - if (data.cells) { - cells.push(...data.cells) - } - - if (data.nodes) { - data.nodes.forEach((node) => { - if (node.shape == null) { - node.shape = 'rect' - } - cells.push(node) - }) - } - - if (data.edges) { - data.edges.forEach((edge) => { - if (edge.shape == null) { - edge.shape = 'edge' - } - cells.push(edge) - }) - } - } - - return cells.map((cell) => { - const type = cell.shape - if (type) { - if (Node.registry.exist(type)) { - return Node.create(cell) - } - if (Edge.registry.exist(type)) { - return Edge.create(cell) - } - } - throw new Error( - 'The `shape` should be specified when creating a node/edge instance', - ) - }) - } -} diff --git a/packages/x6-next/src/model/node.ts b/packages/x6-next/src/model/node.ts deleted file mode 100644 index 7b7741ed8c4..00000000000 --- a/packages/x6-next/src/model/node.ts +++ /dev/null @@ -1,1197 +0,0 @@ -import { Point, Rectangle, Angle } from '@antv/x6-geometry' -import { - StringExt, - ObjectExt, - NumberExt, - Registry, - Size, - KeyValue, - Interp, -} from '@antv/x6-common' -import { DeepPartial, Omit } from 'utility-types' -import { Markup } from '../view/markup' -import { Cell } from './cell' -import { Edge } from './edge' -import { Store } from './store' -import { ShareRegistry } from './registry' -import { PortManager } from './port' -import { Animation } from './animation' - -export class Node< - Properties extends Node.Properties = Node.Properties, -> extends Cell { - protected static defaults: Node.Defaults = { - angle: 0, - position: { x: 0, y: 0 }, - size: { width: 1, height: 1 }, - } - protected readonly store: Store - protected port: PortManager - - protected get [Symbol.toStringTag]() { - return Node.toStringTag - } - - constructor(metadata: Node.Metadata = {}) { - super(metadata) - this.initPorts() - } - - protected preprocess( - metadata: Node.Metadata, - ignoreIdCheck?: boolean, - ): Properties { - const { x, y, width, height, ...others } = metadata - - if (x != null || y != null) { - const position = others.position - others.position = { - ...position, - x: x != null ? x : position ? position.x : 0, - y: y != null ? y : position ? position.y : 0, - } - } - - if (width != null || height != null) { - const size = others.size - others.size = { - ...size, - width: width != null ? width : size ? size.width : 0, - height: height != null ? height : size ? size.height : 0, - } - } - - return super.preprocess(others, ignoreIdCheck) - } - - isNode(): this is Node { - return true - } - - // #region size - - size(): Size - size(size: Size, options?: Node.ResizeOptions): this - size(width: number, height: number, options?: Node.ResizeOptions): this - size( - width?: number | Size, - height?: number | Node.ResizeOptions, - options?: Node.ResizeOptions, - ) { - if (width === undefined) { - return this.getSize() - } - - if (typeof width === 'number') { - return this.setSize(width, height as number, options) - } - - return this.setSize(width, height as Node.ResizeOptions) - } - - getSize() { - const size = this.store.get('size') - return size ? { ...size } : { width: 1, height: 1 } - } - - setSize(size: Size, options?: Node.ResizeOptions): this - setSize(width: number, height: number, options?: Node.ResizeOptions): this - setSize( - width: number | Size, - height?: number | Node.ResizeOptions, - options?: Node.ResizeOptions, - ) { - if (typeof width === 'object') { - this.resize(width.width, width.height, height as Node.ResizeOptions) - } else { - this.resize(width, height as number, options) - } - - return this - } - - resize(width: number, height: number, options: Node.ResizeOptions = {}) { - this.startBatch('resize', options) - const direction = options.direction - - if (direction) { - const currentSize = this.getSize() - switch (direction) { - case 'left': - case 'right': - // Don't change height when resizing horizontally. - height = currentSize.height // eslint-disable-line - break - case 'top': - case 'bottom': - // Don't change width when resizing vertically. - width = currentSize.width // eslint-disable-line - break - default: - break - } - - const map: { [direction: string]: number } = { - right: 0, - 'top-right': 0, - top: 1, - 'top-left': 1, - left: 2, - 'bottom-left': 2, - bottom: 3, - 'bottom-right': 3, - } - - let quadrant = map[direction] - const angle = Angle.normalize(this.getAngle() || 0) - if (options.absolute) { - // We are taking the node's rotation into account - quadrant += Math.floor((angle + 45) / 90) - quadrant %= 4 - } - - // This is a rectangle in size of the un-rotated node. - const bbox = this.getBBox() - - // Pick the corner point on the node, which meant to stay on its - // place before and after the rotation. - let fixedPoint: Point - if (quadrant === 0) { - fixedPoint = bbox.getBottomLeft() - } else if (quadrant === 1) { - fixedPoint = bbox.getCorner() - } else if (quadrant === 2) { - fixedPoint = bbox.getTopRight() - } else { - fixedPoint = bbox.getOrigin() - } - - // Find an image of the previous indent point. This is the position, - // where is the point actually located on the screen. - const imageFixedPoint = fixedPoint - .clone() - .rotate(-angle, bbox.getCenter()) - - // Every point on the element rotates around a circle with the centre of - // rotation in the middle of the element while the whole element is being - // rotated. That means that the distance from a point in the corner of - // the element (supposed its always rect) to the center of the element - // doesn't change during the rotation and therefore it equals to a - // distance on un-rotated element. - // We can find the distance as DISTANCE = (ELEMENTWIDTH/2)^2 + (ELEMENTHEIGHT/2)^2)^0.5. - const radius = Math.sqrt(width * width + height * height) / 2 - - // Now we are looking for an angle between x-axis and the line starting - // at image of fixed point and ending at the center of the element. - // We call this angle `alpha`. - - // The image of a fixed point is located in n-th quadrant. For each - // quadrant passed going anti-clockwise we have to add 90 degrees. - // Note that the first quadrant has index 0. - // - // 3 | 2 - // --c-- Quadrant positions around the element's center `c` - // 0 | 1 - // - let alpha = (quadrant * Math.PI) / 2 - - // Add an angle between the beginning of the current quadrant (line - // parallel with x-axis or y-axis going through the center of the - // element) and line crossing the indent of the fixed point and the - // center of the element. This is the angle we need but on the - // un-rotated element. - alpha += Math.atan(quadrant % 2 === 0 ? height / width : width / height) - - // Lastly we have to deduct the original angle the element was rotated - // by and that's it. - alpha -= Angle.toRad(angle) - - // With this angle and distance we can easily calculate the centre of - // the un-rotated element. - // Note that fromPolar constructor accepts an angle in radians. - const center = Point.fromPolar(radius, alpha, imageFixedPoint) - - // The top left corner on the un-rotated element has to be half a width - // on the left and half a height to the top from the center. This will - // be the origin of rectangle we were looking for. - const origin = center.clone().translate(width / -2, height / -2) - - this.store.set('size', { width, height }, options) - this.setPosition(origin.x, origin.y, options) - } else { - this.store.set('size', { width, height }, options) - } - - this.stopBatch('resize', options) - - return this - } - - scale( - sx: number, - sy: number, - origin?: Point.PointLike | null, - options: Node.SetOptions = {}, - ) { - const scaledBBox = this.getBBox().scale( - sx, - sy, - origin == null ? undefined : origin, - ) - - this.startBatch('scale', options) - this.setPosition(scaledBBox.x, scaledBBox.y, options) - this.resize(scaledBBox.width, scaledBBox.height, options) - this.stopBatch('scale') - return this - } - - // #endregion - - // #region position - - position(x: number, y: number, options?: Node.SetPositionOptions): this - position(options?: Node.GetPositionOptions): Point.PointLike - position( - arg0?: number | Node.GetPositionOptions, - arg1?: number, - arg2?: Node.SetPositionOptions, - ) { - if (typeof arg0 === 'number') { - return this.setPosition(arg0, arg1 as number, arg2) - } - return this.getPosition(arg0) - } - - getPosition(options: Node.GetPositionOptions = {}): Point.PointLike { - if (options.relative) { - const parent = this.getParent() - if (parent != null && parent.isNode()) { - const currentPosition = this.getPosition() - const parentPosition = parent.getPosition() - - return { - x: currentPosition.x - parentPosition.x, - y: currentPosition.y - parentPosition.y, - } - } - } - - const pos = this.store.get('position') - return pos ? { ...pos } : { x: 0, y: 0 } - } - - setPosition( - p: Point | Point.PointLike, - options?: Node.SetPositionOptions, - ): this - setPosition(x: number, y: number, options?: Node.SetPositionOptions): this - setPosition( - arg0: number | Point | Point.PointLike, - arg1?: number | Node.SetPositionOptions, - arg2: Node.SetPositionOptions = {}, - ) { - let x: number - let y: number - let options: Node.SetPositionOptions - - if (typeof arg0 === 'object') { - x = arg0.x - y = arg0.y - options = (arg1 as Node.SetPositionOptions) || {} - } else { - x = arg0 - y = arg1 as number - options = arg2 || {} - } - - if (options.relative) { - const parent = this.getParent() as Node - if (parent != null && parent.isNode()) { - const parentPosition = parent.getPosition() - x += parentPosition.x - y += parentPosition.y - } - } - - if (options.deep) { - const currentPosition = this.getPosition() - this.translate(x - currentPosition.x, y - currentPosition.y, options) - } else { - this.store.set('position', { x, y }, options) - } - - return this - } - - translate(tx = 0, ty = 0, options: Node.TranslateOptions = {}) { - if (tx === 0 && ty === 0) { - return this - } - - // Pass the initiator of the translation. - options.translateBy = options.translateBy || this.id - - const position = this.getPosition() - - if (options.restrict != null && options.translateBy === this.id) { - // We are restricting the translation for the element itself only. We get - // the bounding box of the element including all its embeds. - // All embeds have to be translated the exact same way as the element. - const bbox = this.getBBox({ deep: true }) - const ra = options.restrict - // - - - - - - - - - - - - -> ra.x + ra.width - // - - - -> position.x | - // -> bbox.x - // ▓▓▓▓▓▓▓ | - // ░░░░░░░▓▓▓▓▓▓▓ - // ░░░░░░░░░ | - // ▓▓▓▓▓▓▓▓░░░░░░░ - // ▓▓▓▓▓▓▓▓ | - // <-dx-> | restricted area right border - // <-width-> | ░ translated element - // <- - bbox.width - -> ▓ embedded element - const dx = position.x - bbox.x - const dy = position.y - bbox.y - // Find the maximal/minimal coordinates that the element can be translated - // while complies the restrictions. - const x = Math.max( - ra.x + dx, - Math.min(ra.x + ra.width + dx - bbox.width, position.x + tx), - ) - const y = Math.max( - ra.y + dy, - Math.min(ra.y + ra.height + dy - bbox.height, position.y + ty), - ) - - // recalculate the translation taking the restrictions into account. - tx = x - position.x // eslint-disable-line - ty = y - position.y // eslint-disable-line - } - - const translatedPosition = { - x: position.x + tx, - y: position.y + ty, - } - - // To find out by how much an element was translated in event - // 'change:position' handlers. - options.tx = tx - options.ty = ty - - if (options.transition) { - if (typeof options.transition !== 'object') { - options.transition = {} - } - - this.transition('position', translatedPosition, { - ...options.transition, - interp: Interp.object, - }) - this.eachChild((child) => { - const excluded = options.exclude?.includes(child) - if (!excluded) { - child.translate(tx, ty, options) - } - }) - } else { - this.startBatch('translate', options) - this.store.set('position', translatedPosition, options) - this.eachChild((child) => { - const excluded = options.exclude?.includes(child) - if (!excluded) { - child.translate(tx, ty, options) - } - }) - this.stopBatch('translate', options) - } - - return this - } - - // #endregion - - // #region angle - - angle(): number - angle(val: number, options?: Node.RotateOptions): this - angle(val?: number, options?: Node.RotateOptions) { - if (val == null) { - return this.getAngle() - } - return this.rotate(val, options) - } - - getAngle() { - return this.store.get('angle', 0) - } - - rotate(angle: number, options: Node.RotateOptions = {}) { - const currentAngle = this.getAngle() - if (options.center) { - const size = this.getSize() - const position = this.getPosition() - const center = this.getBBox().getCenter() - center.rotate(currentAngle - angle, options.center) - const dx = center.x - size.width / 2 - position.x - const dy = center.y - size.height / 2 - position.y - this.startBatch('rotate', { angle, options }) - this.setPosition(position.x + dx, position.y + dy, options) - this.rotate(angle, { ...options, center: null }) - this.stopBatch('rotate') - } else { - this.store.set( - 'angle', - options.absolute ? angle : (currentAngle + angle) % 360, - options, - ) - } - - return this - } - - // #endregion - - // #region common - - getBBox(options: { deep?: boolean } = {}) { - if (options.deep) { - const cells = this.getDescendants({ deep: true, breadthFirst: true }) - cells.push(this) - return Cell.getCellsBBox(cells)! - } - - return Rectangle.fromPositionAndSize(this.getPosition(), this.getSize()) - } - - getConnectionPoint(edge: Edge, type: Edge.TerminalType) { - const bbox = this.getBBox() - const center = bbox.getCenter() - const terminal = edge.getTerminal(type) as Edge.TerminalCellData - if (terminal == null) { - return center - } - - const portId = terminal.port - if (!portId || !this.hasPort(portId)) { - return center - } - - const port = this.getPort(portId) - if (!port || !port.group) { - return center - } - - const layouts = this.getPortsPosition(port.group) - const position = layouts[portId].position - const portCenter = Point.create(position).translate(bbox.getOrigin()) - - const angle = this.getAngle() - if (angle) { - portCenter.rotate(-angle, center) - } - - return portCenter - } - - /** - * Sets cell's size and position based on the children bbox and given padding. - */ - fit(options: Node.FitEmbedsOptions = {}) { - const children = this.getChildren() || [] - const embeds = children.filter((cell) => cell.isNode()) as Node[] - if (embeds.length === 0) { - return this - } - - this.startBatch('fit-embeds', options) - - if (options.deep) { - embeds.forEach((cell) => cell.fit(options)) - } - - let { x, y, width, height } = Cell.getCellsBBox(embeds)! - const padding = NumberExt.normalizeSides(options.padding) - - x -= padding.left - y -= padding.top - width += padding.left + padding.right - height += padding.bottom + padding.top - - this.store.set( - { - position: { x, y }, - size: { width, height }, - }, - options, - ) - - this.stopBatch('fit-embeds') - - return this - } - - // #endregion - - // #region ports - - get portContainerMarkup() { - return this.getPortContainerMarkup() - } - - set portContainerMarkup(markup: Markup) { - this.setPortContainerMarkup(markup) - } - - getDefaultPortContainerMarkup() { - return ( - this.store.get('defaultPortContainerMarkup') || - Markup.getPortContainerMarkup() - ) - } - - getPortContainerMarkup() { - return ( - this.store.get('portContainerMarkup') || - this.getDefaultPortContainerMarkup() - ) - } - - setPortContainerMarkup(markup?: Markup, options: Node.SetOptions = {}) { - this.store.set('portContainerMarkup', Markup.clone(markup), options) - return this - } - - get portMarkup() { - return this.getPortMarkup() - } - - set portMarkup(markup: Markup) { - this.setPortMarkup(markup) - } - - getDefaultPortMarkup() { - return this.store.get('defaultPortMarkup') || Markup.getPortMarkup() - } - - getPortMarkup() { - return this.store.get('portMarkup') || this.getDefaultPortMarkup() - } - - setPortMarkup(markup?: Markup, options: Node.SetOptions = {}) { - this.store.set('portMarkup', Markup.clone(markup), options) - return this - } - - get portLabelMarkup() { - return this.getPortLabelMarkup() - } - - set portLabelMarkup(markup: Markup) { - this.setPortLabelMarkup(markup) - } - - getDefaultPortLabelMarkup() { - return ( - this.store.get('defaultPortLabelMarkup') || Markup.getPortLabelMarkup() - ) - } - - getPortLabelMarkup() { - return this.store.get('portLabelMarkup') || this.getDefaultPortLabelMarkup() - } - - setPortLabelMarkup(markup?: Markup, options: Node.SetOptions = {}) { - this.store.set('portLabelMarkup', Markup.clone(markup), options) - return this - } - - get ports() { - const res = this.store.get('ports', { items: [] }) - if (res.items == null) { - res.items = [] - } - return res - } - - getPorts() { - return ObjectExt.cloneDeep(this.ports.items) - } - - getPortsByGroup(groupName: string) { - return this.getPorts().filter((port) => port.group === groupName) - } - - getPort(portId: string) { - return ObjectExt.cloneDeep( - this.ports.items.find((port) => port.id && port.id === portId), - ) - } - - getPortAt(index: number) { - return this.ports.items[index] || null - } - - hasPorts() { - return this.ports.items.length > 0 - } - - hasPort(portId: string) { - return this.getPortIndex(portId) !== -1 - } - - getPortIndex(port: PortManager.PortMetadata | string) { - const portId = typeof port === 'string' ? port : port.id - return portId != null - ? this.ports.items.findIndex((item) => item.id === portId) - : -1 - } - - getPortsPosition(groupName: string) { - const size = this.getSize() - const layouts = this.port.getPortsLayoutByGroup( - groupName, - new Rectangle(0, 0, size.width, size.height), - ) - - return layouts.reduce< - KeyValue<{ - position: Point.PointLike - angle: number - }> - >((memo, item) => { - const layout = item.portLayout - memo[item.portId] = { - position: { ...layout.position }, - angle: layout.angle || 0, - } - return memo - }, {}) - } - - getPortProp(portId: string): PortManager.PortMetadata - getPortProp(portId: string, path: string | string[]): T - getPortProp(portId: string, path?: string | string[]) { - return this.getPropByPath(this.prefixPortPath(portId, path)) - } - - setPortProp( - portId: string, - path: string | string[], - value: any, - options?: Node.SetOptions, - ): this - setPortProp( - portId: string, - value: DeepPartial, - options?: Node.SetOptions, - ): this - setPortProp( - portId: string, - arg1: string | string[] | DeepPartial, - arg2: any | Node.SetOptions, - arg3?: Node.SetOptions, - ) { - if (typeof arg1 === 'string' || Array.isArray(arg1)) { - const path = this.prefixPortPath(portId, arg1) - const value = arg2 - return this.setPropByPath(path, value, arg3) - } - - const path = this.prefixPortPath(portId) - const value = arg1 as DeepPartial - return this.setPropByPath(path, value, arg2 as Node.SetOptions) - } - - removePortProp(portId: string, options?: Node.SetOptions): this - removePortProp( - portId: string, - path: string | string[], - options?: Node.SetOptions, - ): this - removePortProp( - portId: string, - path?: string | string[] | Node.SetOptions, - options?: Node.SetOptions, - ) { - if (typeof path === 'string' || Array.isArray(path)) { - return this.removePropByPath(this.prefixPortPath(portId, path), options) - } - return this.removePropByPath(this.prefixPortPath(portId), path) - } - - portProp(portId: string): PortManager.PortMetadata - portProp(portId: string, path: string | string[]): T - portProp( - portId: string, - path: string | string[], - value: any, - options?: Node.SetOptions, - ): this - portProp( - portId: string, - value: DeepPartial, - options?: Node.SetOptions, - ): this - portProp( - portId: string, - path?: string | string[] | DeepPartial, - value?: any | Node.SetOptions, - options?: Node.SetOptions, - ) { - if (path == null) { - return this.getPortProp(portId) - } - if (typeof path === 'string' || Array.isArray(path)) { - if (arguments.length === 2) { - return this.getPortProp(portId, path) - } - if (value == null) { - return this.removePortProp(portId, path, options) - } - return this.setPortProp( - portId, - path, - value as DeepPartial, - options, - ) - } - return this.setPortProp( - portId, - path as DeepPartial, - value as Node.SetOptions, - ) - } - - protected prefixPortPath(portId: string, path?: string | string[]) { - const index = this.getPortIndex(portId) - if (index === -1) { - throw new Error(`Unable to find port with id: "${portId}"`) - } - - if (path == null || path === '') { - return ['ports', 'items', `${index}`] - } - - if (Array.isArray(path)) { - return ['ports', 'items', `${index}`, ...path] - } - - return `ports/items/${index}/${path}` - } - - addPort(port: PortManager.PortMetadata, options?: Node.SetOptions) { - const ports = [...this.ports.items] - ports.push(port) - this.setPropByPath('ports/items', ports, options) - return this - } - - addPorts(ports: PortManager.PortMetadata[], options?: Node.SetOptions) { - this.setPropByPath('ports/items', [...this.ports.items, ...ports], options) - return this - } - - insertPort( - index: number, - port: PortManager.PortMetadata, - options?: Node.SetOptions, - ) { - const ports = [...this.ports.items] - ports.splice(index, 0, port) - this.setPropByPath('ports/items', ports, options) - return this - } - - removePort( - port: PortManager.PortMetadata | string, - options: Node.SetOptions = {}, - ) { - return this.removePortAt(this.getPortIndex(port), options) - } - - removePortAt(index: number, options: Node.SetOptions = {}) { - if (index >= 0) { - const ports = [...this.ports.items] - ports.splice(index, 1) - options.rewrite = true - this.setPropByPath('ports/items', ports, options) - } - return this - } - - removePorts(options?: Node.SetOptions): this - removePorts( - portsForRemoval: (PortManager.PortMetadata | string)[], - options?: Node.SetOptions, - ): this - removePorts( - portsForRemoval?: (PortManager.PortMetadata | string)[] | Node.SetOptions, - opt?: Node.SetOptions, - ) { - let options - - if (Array.isArray(portsForRemoval)) { - options = opt || {} - if (portsForRemoval.length) { - options.rewrite = true - const currentPorts = [...this.ports.items] - const remainingPorts = currentPorts.filter( - (cp) => - !portsForRemoval.some((p) => { - const id = typeof p === 'string' ? p : p.id - return cp.id === id - }), - ) - this.setPropByPath('ports/items', remainingPorts, options) - } - } else { - options = portsForRemoval || {} - options.rewrite = true - this.setPropByPath('ports/items', [], options) - } - - return this - } - - getParsedPorts() { - return this.port.getPorts() - } - - getParsedGroups() { - return this.port.groups - } - - getPortsLayoutByGroup(groupName: string | undefined, bbox: Rectangle) { - return this.port.getPortsLayoutByGroup(groupName, bbox) - } - - protected initPorts() { - this.updatePortData() - this.on('change:ports', () => { - this.processRemovedPort() - this.updatePortData() - }) - } - - protected processRemovedPort() { - const current = this.ports - const currentItemsMap: { [id: string]: boolean } = {} - - current.items.forEach((item) => { - if (item.id) { - currentItemsMap[item.id] = true - } - }) - - const removed: { [id: string]: boolean } = {} - const previous = this.store.getPrevious('ports') || { - items: [], - } - - previous.items.forEach((item) => { - if (item.id && !currentItemsMap[item.id]) { - removed[item.id] = true - } - }) - - const model = this.model - if (model && !ObjectExt.isEmpty(removed)) { - const incomings = model.getConnectedEdges(this, { incoming: true }) - incomings.forEach((edge) => { - const portId = edge.getTargetPortId() - if (portId && removed[portId]) { - edge.remove() - } - }) - const outgoings = model.getConnectedEdges(this, { outgoing: true }) - outgoings.forEach((edge) => { - const portId = edge.getSourcePortId() - if (portId && removed[portId]) { - edge.remove() - } - }) - } - } - - protected validatePorts() { - const ids: { [id: string]: boolean } = {} - const errors: string[] = [] - this.ports.items.forEach((p) => { - if (typeof p !== 'object') { - errors.push(`Invalid port ${p}.`) - } - - if (p.id == null) { - p.id = this.generatePortId() - } - - if (ids[p.id]) { - errors.push('Duplicitied port id.') - } - - ids[p.id] = true - }) - - return errors - } - - protected generatePortId() { - return StringExt.uuid() - } - - protected updatePortData() { - const err = this.validatePorts() - - if (err.length > 0) { - this.store.set( - 'ports', - this.store.getPrevious('ports'), - ) - throw new Error(err.join(' ')) - } - - const prev = this.port ? this.port.getPorts() : null - this.port = new PortManager(this.ports) - const curr = this.port.getPorts() - - const added = prev - ? curr.filter((item) => { - if (!prev.find((prevPort) => prevPort.id === item.id)) { - return item - } - return null - }) - : [...curr] - - const removed = prev - ? prev.filter((item) => { - if (!curr.find((curPort) => curPort.id === item.id)) { - return item - } - return null - }) - : [] - - if (added.length > 0) { - this.notify('ports:added', { added, cell: this, node: this }) - } - - if (removed.length > 0) { - this.notify('ports:removed', { removed, cell: this, node: this }) - } - } - - // #endregion -} - -export namespace Node { - interface Common extends Cell.Common { - size?: { width: number; height: number } - position?: { x: number; y: number } - angle?: number - ports?: Partial | PortManager.PortMetadata[] - portContainerMarkup?: Markup - portMarkup?: Markup - portLabelMarkup?: Markup - defaultPortMarkup?: Markup - defaultPortLabelMarkup?: Markup - defaultPortContainerMarkup?: Markup - } - - interface Boundary { - x?: number - y?: number - width?: number - height?: number - } - - export interface Defaults extends Common, Cell.Defaults {} - - export interface Metadata extends Common, Cell.Metadata, Boundary {} - - export interface Properties - extends Common, - Omit, - Cell.Properties {} - - export interface Config - extends Defaults, - Boundary, - Cell.Config {} -} - -export namespace Node { - export interface SetOptions extends Cell.SetOptions {} - - export interface GetPositionOptions { - relative?: boolean - } - - export interface SetPositionOptions extends SetOptions { - deep?: boolean - relative?: boolean - } - - export interface TranslateOptions extends Cell.TranslateOptions { - transition?: boolean | Animation.StartOptions - restrict?: Rectangle.RectangleLike | null - exclude?: Cell[] - } - - export interface RotateOptions extends SetOptions { - absolute?: boolean - center?: Point.PointLike | null - } - - export type ResizeDirection = - | 'left' - | 'top' - | 'right' - | 'bottom' - | 'top-left' - | 'top-right' - | 'bottom-left' - | 'bottom-right' - - export interface ResizeOptions extends SetOptions { - absolute?: boolean - direction?: ResizeDirection - } - - export interface FitEmbedsOptions extends SetOptions { - deep?: boolean - padding?: NumberExt.SideOptions - } -} - -export namespace Node { - export const toStringTag = `X6.${Node.name}` - - export function isNode(instance: any): instance is Node { - if (instance == null) { - return false - } - - if (instance instanceof Node) { - return true - } - - const tag = instance[Symbol.toStringTag] - const node = instance as Node - - if ( - (tag == null || tag === toStringTag) && - typeof node.isNode === 'function' && - typeof node.isEdge === 'function' && - typeof node.prop === 'function' && - typeof node.attr === 'function' && - typeof node.size === 'function' && - typeof node.position === 'function' - ) { - return true - } - - return false - } -} - -export namespace Node { - Node.config({ - propHooks({ ports, ...metadata }) { - if (ports) { - metadata.ports = Array.isArray(ports) ? { items: ports } : ports - } - return metadata - }, - }) -} - -export namespace Node { - export const registry = Registry.create< - Definition, - never, - Config & { inherit?: string | Definition } - >({ - type: 'node', - process(shape, options) { - if (ShareRegistry.exist(shape, true)) { - throw new Error( - `Node with name '${shape}' was registered by anthor Edge`, - ) - } - - if (typeof options === 'function') { - options.config({ shape }) - return options - } - - let parent = Node - const { inherit, ...config } = options - if (inherit) { - if (typeof inherit === 'string') { - const base = this.get(inherit) - if (base == null) { - this.onNotFound(inherit, 'inherited') - } else { - parent = base - } - } else { - parent = inherit - } - } - - if (config.constructorName == null) { - config.constructorName = shape - } - - const ctor: Definition = parent.define.call(parent, config) - ctor.config({ shape }) - return ctor as any - }, - }) - - ShareRegistry.setNodeRegistry(registry) -} - -export namespace Node { - type NodeClass = typeof Node - - export interface Definition extends NodeClass { - new (metadata: T): Node - } - - let counter = 0 - function getClassName(name?: string) { - if (name) { - return StringExt.pascalCase(name) - } - counter += 1 - return `CustomNode${counter}` - } - - export function define(config: Config) { - const { constructorName, overwrite, ...others } = config - const ctor = ObjectExt.createClass( - getClassName(constructorName || others.shape), - this as NodeClass, - ) - - ctor.config(others) - - if (others.shape) { - registry.register(others.shape, ctor, overwrite) - } - - return ctor - } - - export function create(options: Metadata) { - const shape = options.shape || 'rect' - const Ctor = registry.get(shape) - if (Ctor) { - return new Ctor(options) - } - return registry.onNotFound(shape) - } -} diff --git a/packages/x6-next/src/model/port.ts b/packages/x6-next/src/model/port.ts deleted file mode 100644 index 01d8b4954e8..00000000000 --- a/packages/x6-next/src/model/port.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { JSONObject, ObjectExt, Size, KeyValue } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { Markup } from '../view' -import { Attr, PortLayout, PortLabelLayout } from '../registry' - -export class PortManager { - ports: PortManager.Port[] - groups: { [name: string]: PortManager.Group } - - constructor(data: PortManager.Metadata) { - this.ports = [] - this.groups = {} - this.init(ObjectExt.cloneDeep(data)) - } - - getPorts() { - return this.ports - } - - getGroup(groupName?: string | null) { - return groupName != null ? this.groups[groupName] : null - } - - getPortsByGroup(groupName?: string): PortManager.Port[] { - return this.ports.filter( - (p) => p.group === groupName || (p.group == null && groupName == null), - ) - } - - getPortsLayoutByGroup(groupName: string | undefined, elemBBox: Rectangle) { - const ports = this.getPortsByGroup(groupName) - const group = groupName ? this.getGroup(groupName) : null - const groupPosition = group ? group.position : null - const groupPositionName = groupPosition ? groupPosition.name : null - - let layoutFn: PortLayout.Definition - - if (groupPositionName != null) { - const fn = PortLayout.registry.get(groupPositionName) - if (fn == null) { - return PortLayout.registry.onNotFound(groupPositionName) - } - layoutFn = fn - } else { - layoutFn = PortLayout.presets.left - } - - const portsArgs = ports.map( - (port) => (port && port.position && port.position.args) || {}, - ) - const groupArgs = (groupPosition && groupPosition.args) || {} - const layouts = layoutFn(portsArgs, elemBBox, groupArgs) - return layouts.map((portLayout, index) => { - const port = ports[index] - return { - portLayout, - portId: port.id!, - portSize: port.size, - portAttrs: port.attrs, - labelSize: port.label.size, - labelLayout: this.getPortLabelLayout( - port, - Point.create(portLayout.position), - elemBBox, - ), - } - }) - } - - protected init(data: PortManager.Metadata) { - const { groups, items } = data - - if (groups != null) { - Object.keys(groups).forEach((key) => { - this.groups[key] = this.parseGroup(groups[key]) - }) - } - - if (Array.isArray(items)) { - items.forEach((item) => { - this.ports.push(this.parsePort(item)) - }) - } - } - - protected parseGroup(group: PortManager.GroupMetadata) { - return { - ...group, - label: this.getLabel(group, true), - position: this.getPortPosition(group.position, true), - } as PortManager.Group - } - - protected parsePort(port: PortManager.PortMetadata) { - const result = { ...port } as PortManager.Port - const group = this.getGroup(port.group) || ({} as PortManager.Group) - - result.markup = result.markup || group.markup - result.attrs = ObjectExt.merge({}, group.attrs, result.attrs) - result.position = this.createPosition(group, result) - result.label = ObjectExt.merge({}, group.label, this.getLabel(result)) - result.zIndex = this.getZIndex(group, result) - result.size = { ...group.size, ...result.size } as Size - - return result - } - - protected getZIndex( - group: PortManager.Group, - port: PortManager.PortMetadata, - ) { - if (typeof port.zIndex === 'number') { - return port.zIndex - } - - if (typeof group.zIndex === 'number' || group.zIndex === 'auto') { - return group.zIndex - } - - return 'auto' - } - - protected createPosition( - group: PortManager.Group, - port: PortManager.PortMetadata, - ) { - return ObjectExt.merge( - { - name: 'left', - args: {}, - }, - group.position, - { args: port.args }, - ) as PortManager.PortPosition - } - - protected getPortPosition( - position?: PortManager.PortPositionMetadata, - setDefault = false, - ): PortManager.PortPosition { - if (position == null) { - if (setDefault) { - return { name: 'left', args: {} } - } - } else { - if (typeof position === 'string') { - return { - name: position, - args: {}, - } - } - - if (Array.isArray(position)) { - return { - name: 'absolute', - args: { x: position[0], y: position[1] }, - } - } - - if (typeof position === 'object') { - return position - } - } - - return { args: {} } - } - - protected getPortLabelPosition( - position?: PortManager.PortLabelPositionMetadata, - setDefault = false, - ): PortManager.PortLabelPosition { - if (position == null) { - if (setDefault) { - return { name: 'left', args: {} } - } - } else { - if (typeof position === 'string') { - return { - name: position, - args: {}, - } - } - - if (typeof position === 'object') { - return position - } - } - - return { args: {} } - } - - protected getLabel(item: PortManager.GroupMetadata, setDefaults = false) { - const label = item.label || {} - label.position = this.getPortLabelPosition(label.position, setDefaults) - return label as PortManager.Label - } - - protected getPortLabelLayout( - port: PortManager.Port, - portPosition: Point, - elemBBox: Rectangle, - ) { - const name = port.label.position.name || 'left' - const args = port.label.position.args || {} - const layoutFn = - PortLabelLayout.registry.get(name) || PortLabelLayout.presets.left - if (layoutFn) { - return layoutFn(portPosition, elemBBox, args) - } - - return null - } -} - -export namespace PortManager { - export interface Metadata { - groups?: { [name: string]: GroupMetadata } - items: PortMetadata[] - } - - export type PortPosition = - | Partial - | Partial - - export type PortPositionMetadata = - | PortLayout.NativeNames - | Exclude - | Point.PointData // absolute layout - | PortPosition - - export type PortLabelPosition = - | Partial - | Partial - - export type PortLabelPositionMetadata = - | PortLabelLayout.NativeNames - | Exclude - | PortLabelPosition - - export interface LabelMetadata { - markup?: Markup - size?: Size - position?: PortLabelPositionMetadata - } - - export interface Label { - markup: string - size?: Size - position: PortLabelPosition - } - - interface Common { - markup: Markup - attrs: Attr.CellAttrs - zIndex: number | 'auto' - size?: Size - } - - export interface GroupMetadata extends Partial, KeyValue { - label?: LabelMetadata - position?: PortPositionMetadata - } - - export interface Group extends Partial { - label: Label - position: PortPosition - } - - interface PortBase { - group?: string - /** - * Arguments for the port layout function. - */ - args?: JSONObject - } - - export interface PortMetadata extends Partial, PortBase, KeyValue { - id?: string - label?: LabelMetadata - } - - export interface Port extends Group, PortBase { - id: string - } - - export interface LayoutResult { - portId: string - portAttrs?: Attr.CellAttrs - portSize?: Size - portLayout: PortLayout.Result - labelSize?: Size - labelLayout: PortLabelLayout.Result | null - } -} diff --git a/packages/x6-next/src/model/registry.ts b/packages/x6-next/src/model/registry.ts deleted file mode 100644 index f9b19f7c4f0..00000000000 --- a/packages/x6-next/src/model/registry.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Registry } from '@antv/x6-common' - -export namespace ShareRegistry { - let edgeRegistry: Registry - let nodeRegistry: Registry - - export function exist(name: string, isNode: boolean) { - return isNode - ? edgeRegistry != null && edgeRegistry.exist(name) - : nodeRegistry != null && nodeRegistry.exist(name) - } - - export function setEdgeRegistry(registry: any) { - edgeRegistry = registry - } - - export function setNodeRegistry(registry: any) { - nodeRegistry = registry - } -} diff --git a/packages/x6-next/src/model/store.ts b/packages/x6-next/src/model/store.ts deleted file mode 100644 index 6a34b5e53ac..00000000000 --- a/packages/x6-next/src/model/store.ts +++ /dev/null @@ -1,342 +0,0 @@ -import { ObjectExt, KeyValue, Basecoat } from '@antv/x6-common' -import { Assign, NonUndefined } from 'utility-types' - -export class Store extends Basecoat> { - protected data: D - protected previous: D - protected changed: Partial - protected pending = false - protected changing = false - protected pendingOptions: Store.MutateOptions | null - - constructor(data: Partial = {}) { - super() - this.data = {} as D - this.mutate(ObjectExt.cloneDeep(data)) - this.changed = {} - } - - protected mutate( - data: Partial, - options: Store.MutateOptions = {}, - ) { - const unset = options.unset === true - const silent = options.silent === true - const changes: K[] = [] - const changing = this.changing - - this.changing = true - - if (!changing) { - this.previous = ObjectExt.cloneDeep(this.data) - this.changed = {} - } - - const current = this.data - const previous = this.previous - const changed = this.changed - - Object.keys(data).forEach((k) => { - const key = k as K - const newValue = data[key] - if (!ObjectExt.isEqual(current[key], newValue)) { - changes.push(key) - } - - if (!ObjectExt.isEqual(previous[key], newValue)) { - changed[key] = newValue - } else { - delete changed[key] - } - - if (unset) { - delete current[key] - } else { - current[key] = newValue as any - } - }) - - if (!silent && changes.length > 0) { - this.pending = true - this.pendingOptions = options - changes.forEach((key) => { - this.emit('change:*', { - key, - options, - store: this, - current: current[key], - previous: previous[key], - }) - }) - } - - if (changing) { - return this - } - - if (!silent) { - // Changes can be recursively nested within `"change"` events. - while (this.pending) { - this.pending = false - this.emit('changed', { - current, - previous, - store: this, - options: this.pendingOptions!, - }) - } - } - - this.pending = false - this.changing = false - this.pendingOptions = null - - return this - } - - get(): D - get(key: K): D[K] - get(key: K, defaultValue: D[K]): NonUndefined - get(key: string): T - get(key: string, defaultValue: T): T - get(key?: K, defaultValue?: D[K]): D | D[K] | undefined { - if (key == null) { - return this.data - } - - const ret = this.data[key] - return ret == null ? defaultValue : ret - } - - getPrevious(key: keyof D) { - if (this.previous) { - const ret = this.previous[key] - return ret == null ? undefined : (ret as any as T) - } - - return undefined - } - - set( - key: K, - value: D[K] | null | undefined | void, - options?: Store.SetOptions, - ): this - set(key: string, value: any, options?: Store.SetOptions): this - set(data: D, options?: Store.SetOptions): this - set( - key: K | Partial, - value?: D[K] | null | undefined | void | Store.SetOptions, - options?: Store.SetOptions, - ): this { - if (key != null) { - if (typeof key === 'object') { - this.mutate(key, value as Store.SetOptions) - } else { - this.mutate({ [key]: value } as Partial, options) - } - } - - return this - } - - remove(key: K | K[], options?: Store.SetOptions): this - remove(options?: Store.SetOptions): this - remove( - key: K | K[] | Store.SetOptions, - options?: Store.SetOptions, - ) { - const empty = undefined - const subset: Partial = {} - let opts: Store.SetOptions | undefined - - if (typeof key === 'string') { - subset[key] = empty - opts = options - } else if (Array.isArray(key)) { - key.forEach((k) => (subset[k] = empty)) - opts = options - } else { - // eslint-disable-next-line - for (const key in this.data) { - subset[key] = empty - } - opts = key as Store.SetOptions - } - - this.mutate(subset, { ...opts, unset: true }) - return this - } - - getByPath(path: string | string[]) { - return ObjectExt.getByPath(this.data, path, '/') as T - } - - setByPath( - path: string | string[], - value: any, - options: Store.SetByPathOptions = {}, - ) { - const delim = '/' - const pathArray = Array.isArray(path) ? [...path] : path.split(delim) - const pathString = Array.isArray(path) ? path.join(delim) : path - - const property = pathArray[0] as K - const pathArrayLength = pathArray.length - - options.propertyPath = pathString - options.propertyValue = value - options.propertyPathArray = pathArray - - if (pathArrayLength === 1) { - this.set(property, value, options) - } else { - const update: KeyValue = {} - let diver = update - let nextKey = property as string - - // Initialize the nested object. Subobjects are either arrays or objects. - // An empty array is created if the sub-key is an integer. Otherwise, an - // empty object is created. - for (let i = 1; i < pathArrayLength; i += 1) { - const key = pathArray[i] - const isArrayIndex = Number.isFinite(Number(key)) - diver = diver[nextKey] = isArrayIndex ? [] : {} - nextKey = key - } - - // Fills update with the `value` on `path`. - ObjectExt.setByPath(update, pathArray, value, delim) - - const data = ObjectExt.cloneDeep(this.data) - - // If rewrite mode enabled, we replace value referenced by path with the - // new one (we don't merge). - if (options.rewrite) { - ObjectExt.unsetByPath(data, path, delim) - } - - const merged = ObjectExt.merge(data, update) - this.set(property, merged[property], options) - } - - return this - } - - removeByPath( - path: string | string[], - options?: Store.SetOptions, - ) { - const keys = Array.isArray(path) ? path : path.split('/') - const key = keys[0] as K - if (keys.length === 1) { - this.remove(key, options) - } else { - const paths = keys.slice(1) - const prop = ObjectExt.cloneDeep(this.get(key)) - if (prop) { - ObjectExt.unsetByPath(prop, paths) - } - - this.set(key, prop as D[K], options) - } - - return this - } - - hasChanged(): boolean - hasChanged(key: K | null): boolean - hasChanged(key: string | null): boolean - hasChanged(key?: K | null) { - if (key == null) { - return Object.keys(this.changed).length > 0 - } - - return key in this.changed - } - - /** - * Returns an object containing all the data that have changed, - * or `null` if there are no changes. Useful for determining what - * parts of a view need to be updated. - */ - getChanges(diff?: Partial) { - if (diff == null) { - return this.hasChanged() ? ObjectExt.cloneDeep(this.changed) : null - } - - const old = this.changing ? this.previous : this.data - const changed: Partial = {} - let hasChanged - // eslint-disable-next-line - for (const key in diff) { - const val = diff[key] - if (!ObjectExt.isEqual(old[key], val)) { - changed[key] = val - hasChanged = true - } - } - return hasChanged ? ObjectExt.cloneDeep(changed) : null - } - - /** - * Returns a copy of the store's `data` object. - */ - toJSON() { - return ObjectExt.cloneDeep(this.data) - } - - clone() { - const constructor = this.constructor as any - return new constructor(this.data) as T - } - - @Basecoat.dispose() - dispose() { - this.off() - this.data = {} as D - this.previous = {} as D - this.changed = {} - this.pending = false - this.changing = false - this.pendingOptions = null - this.trigger('disposed', { store: this }) - } -} - -export namespace Store { - export interface SetOptions extends KeyValue { - silent?: boolean - } - - export interface MutateOptions extends SetOptions { - unset?: boolean - } - - export interface SetByPathOptions extends SetOptions { - rewrite?: boolean - } - - type CommonArgs = { store: Store } - - export interface EventArgs { - 'change:*': Assign< - { - key: K - current: D[K] - previous: D[K] - options: MutateOptions - }, - CommonArgs - > - changed: Assign< - { - current: D - previous: D - options: MutateOptions - }, - CommonArgs - > - disposed: CommonArgs - } -} diff --git a/packages/x6-next/src/registry/attr/align.ts b/packages/x6-next/src/registry/attr/align.ts deleted file mode 100644 index db20fdb306a..00000000000 --- a/packages/x6-next/src/registry/attr/align.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { Point } from '@antv/x6-geometry' -import { Attr } from './index' - -// `x-align` when set to `middle` causes centering of the subelement around its new x coordinate. -// `x-align` when set to `right` uses the x coordinate as referenced to the right of the bbox. -export const xAlign: Attr.Definition = { - offset: offsetWrapper('x', 'width', 'right'), -} - -// `y-align` when set to `middle` causes centering of the subelement around its new y coordinate. -// `y-align` when set to `bottom` uses the y coordinate as referenced to the bottom of the bbox. -export const yAlign: Attr.Definition = { - offset: offsetWrapper('y', 'height', 'bottom'), -} - -export const resetOffset: Attr.Definition = { - offset(val, { refBBox }) { - return val ? { x: -refBBox.x, y: -refBBox.y } : { x: 0, y: 0 } - }, -} - -function offsetWrapper( - axis: 'x' | 'y', - dimension: 'width' | 'height', - corner: 'right' | 'bottom', -): Attr.OffsetFunction { - return (value, { refBBox }) => { - const point = new Point() - let delta - if (value === 'middle') { - delta = refBBox[dimension] / 2 - } else if (value === corner) { - delta = refBBox[dimension] - } else if (typeof value === 'number' && Number.isFinite(value)) { - delta = value > -1 && value < 1 ? -refBBox[dimension] * value : -value - } else if (NumberExt.isPercentage(value)) { - delta = (refBBox[dimension] * parseFloat(value)) / 100 - } else { - delta = 0 - } - point[axis] = -(refBBox[axis] + delta) - return point - } -} diff --git a/packages/x6-next/src/registry/attr/connection.ts b/packages/x6-next/src/registry/attr/connection.ts deleted file mode 100644 index 7318b608ca8..00000000000 --- a/packages/x6-next/src/registry/attr/connection.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { EdgeView } from '../../view' -import { Attr } from './index' - -const isEdgeView: Attr.QualifyFucntion = (val, { view }) => { - return view.cell.isEdge() -} - -export const connection: Attr.Definition = { - qualify: isEdgeView, - set(val, args) { - const view = args.view as EdgeView - const stubs = ((val as any).stubs || 0) as number - let d - if (Number.isFinite(stubs) && stubs !== 0) { - let offset - if (stubs < 0) { - const len = view.getConnectionLength() || 0 - offset = (len + stubs) / 2 - } else { - offset = stubs - } - - const path = view.getConnection() - if (path) { - const sourceParts = path.divideAtLength(offset) - const targetParts = path.divideAtLength(-offset) - if (sourceParts && targetParts) { - d = `${sourceParts[0].serialize()} ${targetParts[1].serialize()}` - } - } - } - - return { d: d || view.getConnectionPathData() } - }, -} - -export const atConnectionLengthKeepGradient: Attr.Definition = { - qualify: isEdgeView, - set: atConnectionWrapper('getTangentAtLength', { rotate: true }), -} - -export const atConnectionLengthIgnoreGradient: Attr.Definition = { - qualify: isEdgeView, - set: atConnectionWrapper('getTangentAtLength', { rotate: false }), -} - -export const atConnectionRatioKeepGradient: Attr.Definition = { - qualify: isEdgeView, - set: atConnectionWrapper('getTangentAtRatio', { rotate: true }), -} - -export const atConnectionRatioIgnoreGradient: Attr.Definition = { - qualify: isEdgeView, - set: atConnectionWrapper('getTangentAtRatio', { rotate: false }), -} - -// aliases -// ------- -export const atConnectionLength = atConnectionLengthKeepGradient -export const atConnectionRatio = atConnectionRatioKeepGradient - -// utils -// ----- - -function atConnectionWrapper( - method: 'getTangentAtLength' | 'getTangentAtRatio', - options: { rotate: boolean }, -): Attr.SetFunction { - const zeroVector = { x: 1, y: 0 } - - return (value, args) => { - let p - let angle - - const view = args.view as EdgeView - const tangent = view[method](Number(value)) - if (tangent) { - angle = options.rotate ? tangent.vector().vectorAngle(zeroVector) : 0 - p = tangent.start - } else { - p = (view as any).path.start - angle = 0 - } - - if (angle === 0) { - return { transform: `translate(${p.x},${p.y}')` } - } - - return { - transform: `translate(${p.x},${p.y}') rotate(${angle})`, - } - } -} diff --git a/packages/x6-next/src/registry/attr/fill.ts b/packages/x6-next/src/registry/attr/fill.ts deleted file mode 100644 index 5a8a13f3657..00000000000 --- a/packages/x6-next/src/registry/attr/fill.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ObjectExt } from '@antv/x6-common' -import { Attr } from './index' - -export const fill: Attr.Definition = { - qualify: ObjectExt.isPlainObject, - set(fill, { view }) { - return `url(#${view.graph.defineGradient(fill as any)})` - }, -} diff --git a/packages/x6-next/src/registry/attr/filter.ts b/packages/x6-next/src/registry/attr/filter.ts deleted file mode 100644 index c966e1d20d7..00000000000 --- a/packages/x6-next/src/registry/attr/filter.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ObjectExt } from '@antv/x6-common' -import { Attr } from './index' - -export const filter: Attr.Definition = { - qualify: ObjectExt.isPlainObject, - set(filter, { view }) { - return `url(#${view.graph.defineFilter(filter as any)})` - }, -} diff --git a/packages/x6-next/src/registry/attr/html.ts b/packages/x6-next/src/registry/attr/html.ts deleted file mode 100644 index bbf9e973a51..00000000000 --- a/packages/x6-next/src/registry/attr/html.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Attr } from './index' - -export const html: Attr.Definition = { - set(html, { elem }) { - elem.innerHTML = `${html}` - }, -} diff --git a/packages/x6-next/src/registry/attr/index.ts b/packages/x6-next/src/registry/attr/index.ts deleted file mode 100644 index 9f149966d17..00000000000 --- a/packages/x6-next/src/registry/attr/index.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Rectangle, Point } from '@antv/x6-geometry' -import { JSONObject, FunctionExt, Registry } from '@antv/x6-common' -import { Cell } from '../../model' -import { CellView } from '../../view' -import { raw } from './raw' -import * as attrs from './main' - -export namespace Attr { - export type SimpleAttrValue = null | undefined | string | number - - export type SimpleAttrs = { [name: string]: SimpleAttrValue } - - export type ComplexAttrValue = - | null - | undefined - | boolean - | string - | number - | JSONObject - - export type ComplexAttrs = { [name: string]: ComplexAttrValue } - - export type CellAttrs = { [selector: string]: ComplexAttrs } -} - -export namespace Attr { - export interface QualifyOptions { - elem: Element - attrs: ComplexAttrs - cell: Cell - view: CellView - } - - export type QualifyFucntion = ( - this: CellView, - val: ComplexAttrValue, - options: QualifyOptions, - ) => boolean - - export interface Options extends QualifyOptions { - refBBox: Rectangle - } - - export type SetFunction = ( - this: CellView, - val: ComplexAttrValue, - options: Options, - ) => SimpleAttrValue | SimpleAttrs | void - - export type OffsetFunction = ( - this: CellView, - val: ComplexAttrValue, - options: Options, - ) => Point.PointLike - - export type PositionFunction = ( - this: CellView, - val: ComplexAttrValue, - options: Options, - ) => Point.PointLike | undefined | null - - export interface Qualify { - qualify?: QualifyFucntion - } - - export interface SetDefinition extends Qualify { - set: SetFunction - } - - export interface OffsetDefinition extends Qualify { - offset: OffsetFunction - } - - export interface PositionDefinition extends Qualify { - /** - * Returns a point from the reference bounding box. - */ - position: PositionFunction - } - - export type Definition = - | string - | Qualify - | SetDefinition - | OffsetDefinition - | PositionDefinition - - export type Definitions = { [attrName: string]: Definition } - - export type GetDefinition = (name: string) => Definition | null | undefined -} - -export namespace Attr { - export function isValidDefinition( - this: CellView, - def: Definition | undefined | null, - val: ComplexAttrValue, - options: QualifyOptions, - ): def is Definition { - if (def != null) { - if (typeof def === 'string') { - return true - } - - if ( - typeof def.qualify !== 'function' || - FunctionExt.call(def.qualify, this, val, options) - ) { - return true - } - } - - return false - } -} - -export namespace Attr { - export type Presets = typeof Attr['presets'] - export type NativeNames = keyof Presets -} - -export namespace Attr { - export const presets: Definitions = { - ...raw, - ...attrs, - } - - export const registry = Registry.create({ - type: 'attribute definition', - }) - - registry.register(Attr.presets, true) -} diff --git a/packages/x6-next/src/registry/attr/main.ts b/packages/x6-next/src/registry/attr/main.ts deleted file mode 100644 index c0b99cf708f..00000000000 --- a/packages/x6-next/src/registry/attr/main.ts +++ /dev/null @@ -1,12 +0,0 @@ -export * from './ref' -export * from './fill' -export * from './stroke' -export * from './text' -export * from './title' -export * from './align' -export * from './style' -export * from './html' -export * from './filter' -export * from './port' -export * from './marker' -export * from './connection' diff --git a/packages/x6-next/src/registry/attr/marker.ts b/packages/x6-next/src/registry/attr/marker.ts deleted file mode 100644 index 21e86c33ab0..00000000000 --- a/packages/x6-next/src/registry/attr/marker.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { ObjectExt, JSONObject, KeyValue } from '@antv/x6-common' -import { CellView } from '../../view' -import { Marker } from '../marker' -import { Attr } from './index' - -function qualify(value: any) { - return typeof value === 'string' || ObjectExt.isPlainObject(value) -} - -export const sourceMarker: Attr.Definition = { - qualify, - set(marker: string | JSONObject, { view, attrs }) { - return createMarker('marker-start', marker, view, attrs) - }, -} - -export const targetMarker: Attr.Definition = { - qualify, - set(marker: string | JSONObject, { view, attrs }) { - return createMarker('marker-end', marker, view, attrs, { - transform: 'rotate(180)', - }) - }, -} - -export const vertexMarker: Attr.Definition = { - qualify, - set(marker: string | JSONObject, { view, attrs }) { - return createMarker('marker-mid', marker, view, attrs) - }, -} - -function createMarker( - type: 'marker-start' | 'marker-end' | 'marker-mid', - marker: string | JSONObject, - view: CellView, - attrs: Attr.ComplexAttrs, - manual: Attr.SimpleAttrs = {}, -) { - const def = typeof marker === 'string' ? { name: marker } : marker - const { name, args, ...others } = def - let preset = others - - if (name && typeof name === 'string') { - const fn = Marker.registry.get(name) - if (fn) { - preset = fn({ ...others, ...(args as KeyValue) }) - } else { - return Marker.registry.onNotFound(name) - } - } - - const options: any = { - ...normalizeAttr(attrs, type), - ...manual, - ...preset, - } - - return { - [type]: `url(#${view.graph.defineMarker(options)})`, - } -} - -function normalizeAttr( - attr: Attr.ComplexAttrs, - type: 'marker-start' | 'marker-end' | 'marker-mid', -) { - const result: Attr.SimpleAttrs = {} - - // The context 'fill' is disregared here. The usual case is to use the - // marker with a connection(for which 'fill' attribute is set to 'none'). - const stroke = attr.stroke - if (typeof stroke === 'string') { - result.stroke = stroke - result.fill = stroke - } - - // Again the context 'fill-opacity' is ignored. - let strokeOpacity = attr.strokeOpacity - if (strokeOpacity == null) { - strokeOpacity = attr['stroke-opacity'] - } - - if (strokeOpacity == null) { - strokeOpacity = attr.opacity - } - - if (strokeOpacity != null) { - result['stroke-opacity'] = strokeOpacity as number - result['fill-opacity'] = strokeOpacity as number - } - - if (type !== 'marker-mid') { - const strokeWidth = parseFloat( - (attr.strokeWidth || attr['stroke-width']) as string, - ) - if (Number.isFinite(strokeWidth) && strokeWidth > 1) { - const offset = Math.ceil(strokeWidth / 2) - result.refX = type === 'marker-start' ? offset : -offset - } - } - - return result -} diff --git a/packages/x6-next/src/registry/attr/port.ts b/packages/x6-next/src/registry/attr/port.ts deleted file mode 100644 index 41b47b1fb93..00000000000 --- a/packages/x6-next/src/registry/attr/port.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Attr } from './index' - -export const port: Attr.Definition = { - set(port) { - if (port != null && typeof port === 'object' && port.id) { - return port.id as string - } - return port as string - }, -} diff --git a/packages/x6-next/src/registry/attr/raw.ts b/packages/x6-next/src/registry/attr/raw.ts deleted file mode 100644 index dbb6e67773d..00000000000 --- a/packages/x6-next/src/registry/attr/raw.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Attr } from './index' - -export const raw: Attr.Definitions = { - xlinkHref: 'xlink:href', - xlinkShow: 'xlink:show', - xlinkRole: 'xlink:role', - xlinkType: 'xlink:type', - xlinkArcrole: 'xlink:arcrole', - xlinkTitle: 'xlink:title', - xlinkActuate: 'xlink:actuate', - xmlSpace: 'xml:space', - xmlBase: 'xml:base', - xmlLang: 'xml:lang', - preserveAspectRatio: 'preserveAspectRatio', - requiredExtension: 'requiredExtension', - requiredFeatures: 'requiredFeatures', - systemLanguage: 'systemLanguage', - externalResourcesRequired: 'externalResourceRequired', -} diff --git a/packages/x6-next/src/registry/attr/ref.ts b/packages/x6-next/src/registry/attr/ref.ts deleted file mode 100644 index ed38c0186e5..00000000000 --- a/packages/x6-next/src/registry/attr/ref.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { Point, Path, Polyline, Rectangle } from '@antv/x6-geometry' -import { NumberExt, FunctionExt, Dom } from '@antv/x6-common' -import { Attr } from './index' - -export const ref: Attr.Definition = { - // We do not set `ref` attribute directly on an element. - // The attribute itself does not qualify for relative positioning. -} - -// if `refX` is in [0, 1] then `refX` is a fraction of bounding box width -// if `refX` is < 0 then `refX`'s absolute values is the right coordinate of the bounding box -// otherwise, `refX` is the left coordinate of the bounding box - -export const refX: Attr.Definition = { - position: positionWrapper('x', 'width', 'origin'), -} - -export const refY: Attr.Definition = { - position: positionWrapper('y', 'height', 'origin'), -} - -// `ref-dx` and `ref-dy` define the offset of the subelement relative to the right and/or bottom -// coordinate of the reference element. - -export const refDx: Attr.Definition = { - position: positionWrapper('x', 'width', 'corner'), -} - -export const refDy: Attr.Definition = { - position: positionWrapper('y', 'height', 'corner'), -} - -// 'ref-width'/'ref-height' defines the width/height of the subelement relatively to -// the reference element size -// val in 0..1 ref-width = 0.75 sets the width to 75% of the ref. el. width -// val < 0 || val > 1 ref-height = -20 sets the height to the ref. el. height shorter by 20 -export const refWidth: Attr.Definition = { - set: setWrapper('width', 'width'), -} - -export const refHeight: Attr.Definition = { - set: setWrapper('height', 'height'), -} - -export const refRx: Attr.Definition = { - set: setWrapper('rx', 'width'), -} - -export const refRy: Attr.Definition = { - set: setWrapper('ry', 'height'), -} - -export const refRInscribed: Attr.Definition = { - set: ((attrName): Attr.SetFunction => { - const widthFn = setWrapper(attrName, 'width') - const heightFn = setWrapper(attrName, 'height') - return function (value, options) { - const refBBox = options.refBBox - const fn = refBBox.height > refBBox.width ? widthFn : heightFn - return FunctionExt.call(fn, this, value, options) - } - })('r'), -} - -export const refRCircumscribed: Attr.Definition = { - set(val, { refBBox }) { - let value = parseFloat(val as string) - const percentage = NumberExt.isPercentage(val) - if (percentage) { - value /= 100 - } - - const diagonalLength = Math.sqrt( - refBBox.height * refBBox.height + refBBox.width * refBBox.width, - ) - - let rValue - if (Number.isFinite(value)) { - if (percentage || (value >= 0 && value <= 1)) { - rValue = value * diagonalLength - } else { - rValue = Math.max(value + diagonalLength, 0) - } - } - - return { r: rValue } as Attr.SimpleAttrs - }, -} - -export const refCx: Attr.Definition = { - set: setWrapper('cx', 'width'), -} - -export const refCy: Attr.Definition = { - set: setWrapper('cy', 'height'), -} - -export const refDResetOffset: Attr.Definition = { - set: dWrapper({ resetOffset: true }), -} - -export const refDKeepOffset: Attr.Definition = { - set: dWrapper({ resetOffset: false }), -} - -export const refPointsResetOffset: Attr.Definition = { - set: pointsWrapper({ resetOffset: true }), -} - -export const refPointsKeepOffset: Attr.Definition = { - set: pointsWrapper({ resetOffset: false }), -} - -// aliases -// ------- -export const refR = refRInscribed -export const refD = refDResetOffset -export const refPoints = refPointsResetOffset -// Allows to combine both absolute and relative positioning -// refX: 50%, refX2: 20 -export const refX2 = refX -export const refY2 = refY -export const refWidth2 = refWidth -export const refHeight2 = refHeight - -// utils -// ----- - -function positionWrapper( - axis: 'x' | 'y', - dimension: 'width' | 'height', - origin: 'origin' | 'corner', -): Attr.PositionFunction { - return (val, { refBBox }) => { - if (val == null) { - return null - } - - let value = parseFloat(val as string) - const percentage = NumberExt.isPercentage(val) - if (percentage) { - value /= 100 - } - - let delta - if (Number.isFinite(value)) { - const refOrigin = refBBox[origin] - if (percentage || (value > 0 && value < 1)) { - delta = refOrigin[axis] + refBBox[dimension] * value - } else { - delta = refOrigin[axis] + value - } - } - - const point = new Point() - point[axis] = delta || 0 - return point - } -} - -function setWrapper( - attrName: string, - dimension: 'width' | 'height', -): Attr.SetFunction { - return function (val, { refBBox }) { - let value = parseFloat(val as string) - const percentage = NumberExt.isPercentage(val) - if (percentage) { - value /= 100 - } - - const attrs: Attr.SimpleAttrs = {} - - if (Number.isFinite(value)) { - const attrValue = - percentage || (value >= 0 && value <= 1) - ? value * refBBox[dimension] - : Math.max(value + refBBox[dimension], 0) - attrs[attrName] = attrValue - } - - return attrs - } -} - -function shapeWrapper( - shapeConstructor: (value: Attr.ComplexAttrValue) => any, - options: { resetOffset: boolean }, -): (value: Attr.ComplexAttrValue, options: Attr.Options) => T { - const cacheName = 'x6-shape' - const resetOffset = options && options.resetOffset - - return function (value, { elem, refBBox }) { - let cache = Dom.data(elem, cacheName) - if (!cache || cache.value !== value) { - // only recalculate if value has changed - const cachedShape = shapeConstructor(value) - cache = { - value, - shape: cachedShape, - shapeBBox: cachedShape.bbox(), - } - Dom.data(elem, cacheName, cache) - } - - const shape = cache.shape.clone() - const shapeBBox = cache.shapeBBox.clone() as Rectangle - const shapeOrigin = shapeBBox.getOrigin() - const refOrigin = refBBox.getOrigin() - - shapeBBox.x = refOrigin.x - shapeBBox.y = refOrigin.y - - const fitScale = refBBox.getMaxScaleToFit(shapeBBox, refOrigin) - // `maxRectScaleToFit` can give Infinity if width or height is 0 - const sx = shapeBBox.width === 0 || refBBox.width === 0 ? 1 : fitScale.sx - const sy = shapeBBox.height === 0 || refBBox.height === 0 ? 1 : fitScale.sy - - shape.scale(sx, sy, shapeOrigin) - if (resetOffset) { - shape.translate(-shapeOrigin.x, -shapeOrigin.y) - } - - return shape - } -} - -// `d` attribute for SVGPaths -function dWrapper(options: { resetOffset: boolean }): Attr.SetFunction { - function pathConstructor(value: string) { - return Path.parse(value) - } - - const shape = shapeWrapper(pathConstructor, options) - - return (value, args) => { - const path = shape(value, args) - return { - d: path.serialize(), - } - } -} - -// `points` attribute for SVGPolylines and SVGPolygons -function pointsWrapper(options: { resetOffset: boolean }): Attr.SetFunction { - const shape = shapeWrapper((points) => new Polyline(points as any), options) - return (value, args) => { - const polyline = shape(value, args) - return { - points: polyline.serialize(), - } - } -} diff --git a/packages/x6-next/src/registry/attr/stroke.ts b/packages/x6-next/src/registry/attr/stroke.ts deleted file mode 100644 index dc366c88288..00000000000 --- a/packages/x6-next/src/registry/attr/stroke.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ObjectExt } from '@antv/x6-common' -import { EdgeView } from '../../view/edge' -import { Attr } from './index' - -export const stroke: Attr.Definition = { - qualify: ObjectExt.isPlainObject, - set(stroke: any, { view }) { - const cell = view.cell - const options = { ...stroke } - - if (cell.isEdge() && options.type === 'linearGradient') { - const edgeView = view as EdgeView - const source = edgeView.sourcePoint - const target = edgeView.targetPoint - - options.id = `gradient-${options.type}-${cell.id}` - options.attrs = { - ...options.attrs, - x1: source.x, - y1: source.y, - x2: target.x, - y2: target.y, - gradientUnits: 'userSpaceOnUse', - } - - view.graph.defs.remove(options.id) - } - - return `url(#${view.graph.defineGradient(options)})` - }, -} diff --git a/packages/x6-next/src/registry/attr/style.ts b/packages/x6-next/src/registry/attr/style.ts deleted file mode 100644 index c20e7ac440a..00000000000 --- a/packages/x6-next/src/registry/attr/style.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ObjectExt, Dom } from '@antv/x6-common' -import { Attr } from './index' - -export const style: Attr.Definition = { - qualify: ObjectExt.isPlainObject, - set(styles, { elem }) { - Dom.css(elem, styles as Record) - }, -} diff --git a/packages/x6-next/src/registry/attr/text.ts b/packages/x6-next/src/registry/attr/text.ts deleted file mode 100644 index db6b7752c0f..00000000000 --- a/packages/x6-next/src/registry/attr/text.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { - ObjectExt, - JSONObject, - NumberExt, - Dom, - FunctionExt, - Text, -} from '@antv/x6-common' -import { Attr } from './index' - -export const text: Attr.Definition = { - qualify(text, { attrs }) { - return attrs.textWrap == null || !ObjectExt.isPlainObject(attrs.textWrap) - }, - set(text, { view, elem, attrs }) { - const cacheName = 'x6-text' - const cache = Dom.data(elem, cacheName) - const json = (str: any) => { - try { - return JSON.parse(str) as T - } catch (error) { - return str - } - } - const options: Dom.TextOptions = { - x: attrs.x as string | number, - eol: attrs.eol as string, - annotations: json(attrs.annotations) as - | Text.Annotation - | Text.Annotation[], - textPath: json(attrs['text-path'] || attrs.textPath), - textVerticalAnchor: (attrs['text-vertical-anchor'] || - attrs.textVerticalAnchor) as 'middle' | 'bottom' | 'top' | number, - displayEmpty: (attrs['display-empty'] || attrs.displayEmpty) === 'true', - lineHeight: (attrs['line-height'] || attrs.lineHeight) as string, - } - - const fontSize = (attrs['font-size'] || attrs.fontSize) as string - const textHash = JSON.stringify([text, options]) - - if (fontSize) { - elem.setAttribute('font-size', fontSize) - } - - // Updates the text only if there was a change in the string - // or any of its attributes. - if (cache == null || cache !== textHash) { - // Text Along Path Selector - const textPath = options.textPath as any - if (textPath != null && typeof textPath === 'object') { - const selector = textPath.selector - if (typeof selector === 'string') { - const pathNode = view.find(selector)[0] - if (pathNode instanceof SVGPathElement) { - Dom.ensureId(pathNode) - options.textPath = { - 'xlink:href': `#${pathNode.id}`, - ...textPath, - } - } - } - } - - Dom.text(elem as SVGElement, `${text}`, options) - Dom.data(elem, cacheName, textHash) - } - }, -} - -export const textWrap: Attr.Definition = { - qualify: ObjectExt.isPlainObject, - set(val, { view, elem, attrs, refBBox }) { - const info = val as JSONObject - - // option `width` - const width = info.width || 0 - if (NumberExt.isPercentage(width)) { - refBBox.width *= parseFloat(width) / 100 - } else if (width <= 0) { - refBBox.width += width as number - } else { - refBBox.width = width as number - } - - // option `height` - const height = info.height || 0 - if (NumberExt.isPercentage(height)) { - refBBox.height *= parseFloat(height) / 100 - } else if (height <= 0) { - refBBox.height += height as number - } else { - refBBox.height = height as number - } - - // option `text` - let wrappedText - let txt = info.text - if (txt == null) { - txt = attrs.text - } - - if (txt != null) { - wrappedText = Dom.breakText( - `${txt}`, - refBBox, - { - 'font-weight': attrs['font-weight'] || attrs.fontWeight, - 'font-size': attrs['font-size'] || attrs.fontSize, - 'font-family': attrs['font-family'] || attrs.fontFamily, - lineHeight: attrs.lineHeight, - }, - { - // svgDocument: view.graph.view.svg, - ellipsis: info.ellipsis as string, - // hyphen: info.hyphen as string, - // breakWord: info.breakWord as boolean, - }, - ) - } else { - wrappedText = '' - } - - FunctionExt.call(text.set, this, wrappedText, { - view, - elem, - attrs, - refBBox, - cell: view.cell, - }) - }, -} - -const isTextInUse: Attr.QualifyFucntion = (val, { attrs }) => { - return attrs.text !== undefined -} - -export const lineHeight: Attr.Definition = { - qualify: isTextInUse, -} - -export const textVerticalAnchor: Attr.Definition = { - qualify: isTextInUse, -} - -export const textPath: Attr.Definition = { - qualify: isTextInUse, -} - -export const annotations: Attr.Definition = { - qualify: isTextInUse, -} - -export const eol: Attr.Definition = { - qualify: isTextInUse, -} - -export const displayEmpty: Attr.Definition = { - qualify: isTextInUse, -} diff --git a/packages/x6-next/src/registry/attr/title.ts b/packages/x6-next/src/registry/attr/title.ts deleted file mode 100644 index 94040573c9a..00000000000 --- a/packages/x6-next/src/registry/attr/title.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Attr } from './index' - -export const title: Attr.Definition = { - qualify(title, { elem }) { - // HTMLElement title is specified via an attribute (i.e. not an element) - return elem instanceof SVGElement - }, - set(val, { elem }) { - const cacheName = 'x6-title' - const title = `${val}` - const cache = Dom.data(elem, cacheName) - if (cache == null || cache !== title) { - Dom.data(elem, cacheName, title) - // Generally SVGTitleElement should be the first child - // element of its parent. - const firstChild = elem.firstChild as Element - if (firstChild && firstChild.tagName.toUpperCase() === 'TITLE') { - // Update an existing title - const titleElem = firstChild as SVGTitleElement - titleElem.textContent = title - } else { - // Create a new title - const titleNode = document.createElementNS( - elem.namespaceURI, - 'title', - ) as SVGTitleElement - titleNode.textContent = title - elem.insertBefore(titleNode, firstChild) - } - } - }, -} diff --git a/packages/x6-next/src/registry/background/flip-x.ts b/packages/x6-next/src/registry/background/flip-x.ts deleted file mode 100644 index d0384700e1e..00000000000 --- a/packages/x6-next/src/registry/background/flip-x.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Background } from './index' - -export const flipX: Background.Definition = function (img) { - // d b - // d b - - const canvas = document.createElement('canvas') - const width = img.width - const height = img.height - - canvas.width = width * 2 - canvas.height = height - - const ctx = canvas.getContext('2d')! - // left image - ctx.drawImage(img, 0, 0, width, height) - // flipped right image - ctx.translate(2 * width, 0) - ctx.scale(-1, 1) - ctx.drawImage(img, 0, 0, width, height) - - return canvas -} diff --git a/packages/x6-next/src/registry/background/flip-xy.ts b/packages/x6-next/src/registry/background/flip-xy.ts deleted file mode 100644 index 1cf3a4c0849..00000000000 --- a/packages/x6-next/src/registry/background/flip-xy.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Background } from './index' - -export const flipXY: Background.Definition = function (img) { - // d b - // q p - - const canvas = document.createElement('canvas') - const width = img.width - const height = img.height - - canvas.width = 2 * width - canvas.height = 2 * height - - const ctx = canvas.getContext('2d')! - // top-left image - ctx.drawImage(img, 0, 0, width, height) - // xy-flipped bottom-right image - ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height) - ctx.drawImage(img, 0, 0, width, height) - // x-flipped top-right image - ctx.setTransform(-1, 0, 0, 1, canvas.width, 0) - ctx.drawImage(img, 0, 0, width, height) - // y-flipped bottom-left image - ctx.setTransform(1, 0, 0, -1, 0, canvas.height) - ctx.drawImage(img, 0, 0, width, height) - - return canvas -} diff --git a/packages/x6-next/src/registry/background/flip-y.ts b/packages/x6-next/src/registry/background/flip-y.ts deleted file mode 100644 index 57a3417ee39..00000000000 --- a/packages/x6-next/src/registry/background/flip-y.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Background } from './index' - -export const flipY: Background.Definition = function (img) { - // d d - // q q - - const canvas = document.createElement('canvas') - const width = img.width - const height = img.height - - canvas.width = width - canvas.height = height * 2 - - const ctx = canvas.getContext('2d')! - // top image - ctx.drawImage(img, 0, 0, width, height) - // flipped bottom image - ctx.translate(0, 2 * height) - ctx.scale(1, -1) - ctx.drawImage(img, 0, 0, width, height) - - return canvas -} diff --git a/packages/x6-next/src/registry/background/index.ts b/packages/x6-next/src/registry/background/index.ts deleted file mode 100644 index cb2b3f2b35e..00000000000 --- a/packages/x6-next/src/registry/background/index.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-types */ - -import { ValuesType } from 'utility-types' -import { KeyValue, Registry } from '@antv/x6-common' -import * as patterns from './main' - -export namespace Background { - export interface Options { - color?: string - image?: string - position?: Background.BackgroundPosition<{ - x: number - y: number - }> - size?: Background.BackgroundSize<{ - width: number - height: number - }> - repeat?: Background.BackgroundRepeat - opacity?: number - } - - export interface CommonOptions extends Omit { - quality?: number - } - - export type Definition = ( - img: HTMLImageElement, - options: T, - ) => HTMLCanvasElement -} - -export namespace Background { - export type Presets = typeof Background['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[1] & { - repeat: K - } - } - - export type NativeNames = keyof Presets - - export type NativeItem = ValuesType - - export type ManaualItem = CommonOptions & - KeyValue & { - repeat: string - } -} - -export namespace Background { - export const presets: { [name: string]: Definition } = { ...patterns } - - presets['flip-x'] = patterns.flipX - presets['flip-y'] = patterns.flipY - presets['flip-xy'] = patterns.flipXY - - export const registry = Registry.create({ - type: 'background pattern', - }) - - registry.register(presets, true) -} - -export namespace Background { - type Globals = '-moz-initial' | 'inherit' | 'initial' | 'revert' | 'unset' - type BgPosition = - | TLength - | 'bottom' - | 'center' - | 'left' - | 'right' - | 'top' - | (string & {}) - type BgSize = TLength | 'auto' | 'contain' | 'cover' | (string & {}) - type RepeatStyle = - | 'no-repeat' - | 'repeat' - | 'repeat-x' - | 'repeat-y' - | 'round' - | 'space' - | (string & {}) - export type BackgroundPosition = - | Globals - | BgPosition - | (string & {}) - export type BackgroundSize = - | Globals - | BgSize - | (string & {}) - export type BackgroundRepeat = Globals | RepeatStyle | (string & {}) - export interface Padding { - left: number - top: number - right: number - bottom: number - } -} diff --git a/packages/x6-next/src/registry/background/main.ts b/packages/x6-next/src/registry/background/main.ts deleted file mode 100644 index 60f8fc6e12b..00000000000 --- a/packages/x6-next/src/registry/background/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './flip-x' -export * from './flip-y' -export * from './flip-xy' -export * from './watermark' diff --git a/packages/x6-next/src/registry/background/watermark.ts b/packages/x6-next/src/registry/background/watermark.ts deleted file mode 100644 index f51dcb8cba1..00000000000 --- a/packages/x6-next/src/registry/background/watermark.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Angle } from '@antv/x6-geometry' -import { Background } from './index' - -export interface WatermarkOptions extends Background.CommonOptions { - angle?: number -} - -export const watermark: Background.Definition = function ( - img, - options, -) { - const width = img.width - const height = img.height - const canvas = document.createElement('canvas') - - canvas.width = width * 3 - canvas.height = height * 3 - - const ctx = canvas.getContext('2d')! - const angle = options.angle != null ? -options.angle : -20 - const radians = Angle.toRad(angle) - const stepX = canvas.width / 4 - const stepY = canvas.height / 4 - - for (let i = 0; i < 4; i += 1) { - for (let j = 0; j < 4; j += 1) { - if ((i + j) % 2 > 0) { - ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY) - ctx.rotate(radians) - ctx.drawImage(img, -width / 2, -height / 2, width, height) - } - } - } - - return canvas -} diff --git a/packages/x6-next/src/registry/connection-point/anchor.ts b/packages/x6-next/src/registry/connection-point/anchor.ts deleted file mode 100644 index a2c4243620a..00000000000 --- a/packages/x6-next/src/registry/connection-point/anchor.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Line } from '@antv/x6-geometry' -import { ConnectionPoint } from './index' -import { offset } from './util' - -type Align = 'top' | 'right' | 'bottom' | 'left' - -export interface AnchorOptions extends ConnectionPoint.BaseOptions { - align?: Align - alignOffset?: number -} - -function alignLine(line: Line, type: Align, offset = 0) { - const { start, end } = line - let a - let b - let direction - let coordinate: 'x' | 'y' - - switch (type) { - case 'left': - coordinate = 'x' - a = end - b = start - direction = -1 - break - case 'right': - coordinate = 'x' - a = start - b = end - direction = 1 - break - case 'top': - coordinate = 'y' - a = end - b = start - direction = -1 - break - case 'bottom': - coordinate = 'y' - a = start - b = end - direction = 1 - break - default: - return - } - - if (start[coordinate] < end[coordinate]) { - a[coordinate] = b[coordinate] - } else { - b[coordinate] = a[coordinate] - } - - if (Number.isFinite(offset)) { - a[coordinate] += direction * offset - b[coordinate] += direction * offset - } -} - -/** - * Places the connection point at the edge's endpoint. - */ -export const anchor: ConnectionPoint.Definition = function ( - line, - view, - magnet, - options, -) { - const { alignOffset, align } = options - if (align) { - alignLine(line, align, alignOffset) - } - return offset(line.end, line.start, options.offset) -} diff --git a/packages/x6-next/src/registry/connection-point/bbox.ts b/packages/x6-next/src/registry/connection-point/bbox.ts deleted file mode 100644 index bbadb33893f..00000000000 --- a/packages/x6-next/src/registry/connection-point/bbox.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { offset, getStrokeWidth } from './util' -import { ConnectionPoint } from './index' - -export interface BBoxOptions extends ConnectionPoint.StrokedOptions {} - -/** - * Places the connection point at the intersection between the edge - * path end segment and the target node bbox. - */ -export const bbox: ConnectionPoint.Definition = function ( - line, - view, - magnet, - options, -) { - const bbox = view.getBBoxOfElement(magnet) - if (options.stroked) { - bbox.inflate(getStrokeWidth(magnet) / 2) - } - const intersections = line.intersect(bbox) - const p = - intersections && intersections.length - ? line.start.closest(intersections)! - : line.end - return offset(p, line.start, options.offset) -} diff --git a/packages/x6-next/src/registry/connection-point/boundary.ts b/packages/x6-next/src/registry/connection-point/boundary.ts deleted file mode 100644 index d0ac477c7c6..00000000000 --- a/packages/x6-next/src/registry/connection-point/boundary.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { ObjectExt, Dom } from '@antv/x6-common' -import { Path, Rectangle, Ellipse, Segment } from '@antv/x6-geometry' -import { offset, getStrokeWidth, findShapeNode } from './util' -import { ConnectionPoint } from './index' -import { Util } from '../../util' - -export interface BoundaryOptions extends ConnectionPoint.StrokedOptions { - selector?: string | string[] - insideout?: boolean - precision?: number - extrapolate?: boolean - sticky?: boolean -} - -export interface BoundaryCache { - shapeBBox?: Rectangle | null - segmentSubdivisions?: Segment[][] -} - -/** - * Places the connection point at the intersection between the - * edge path end segment and the actual shape of the target magnet. - */ -export const boundary: ConnectionPoint.Definition = function ( - line, - view, - magnet, - options, -) { - let node - let intersection - const anchor = line.end - const selector = options.selector - - if (typeof selector === 'string') { - node = view.findOne(selector) - } else if (Array.isArray(selector)) { - node = ObjectExt.getByPath(magnet, selector) - } else { - node = findShapeNode(magnet) - } - - if (!Dom.isSVGGraphicsElement(node)) { - if (node === magnet || !Dom.isSVGGraphicsElement(magnet)) { - return anchor - } - node = magnet - } - - const localShape = view.getShapeOfElement(node) - const magnetMatrix = view.getMatrixOfElement(node) - const translateMatrix = view.getRootTranslatedMatrix() - const rotateMatrix = view.getRootRotatedMatrix() - const targetMatrix = translateMatrix - .multiply(rotateMatrix) - .multiply(magnetMatrix) - const localMatrix = targetMatrix.inverse() - const localLine = Util.transformLine(line, localMatrix) - const localRef = localLine.start.clone() - const data = view.getDataOfElement(node) as BoundaryCache - - if (options.insideout === false) { - if (data.shapeBBox == null) { - data.shapeBBox = localShape.bbox() - } - const localBBox = data.shapeBBox - if (localBBox != null && localBBox.containsPoint(localRef)) { - return anchor - } - } - - if (options.extrapolate === true) { - localLine.setLength(1e6) - } - - // Caching segment subdivisions for paths - let pathOptions - if (Path.isPath(localShape)) { - const precision = options.precision || 2 - if (data.segmentSubdivisions == null) { - data.segmentSubdivisions = localShape.getSegmentSubdivisions({ - precision, - }) - } - pathOptions = { - precision, - segmentSubdivisions: data.segmentSubdivisions, - } - - intersection = localLine.intersect(localShape, pathOptions) - } else { - intersection = localLine.intersect(localShape) - } - - if (intersection) { - if (Array.isArray(intersection)) { - intersection = localRef.closest(intersection) - } - } else if (options.sticky === true) { - // No intersection, find the closest point instead - if (Rectangle.isRectangle(localShape)) { - intersection = localShape.getNearestPointToPoint(localRef) - } else if (Ellipse.isEllipse(localShape)) { - intersection = localShape.intersectsWithLineFromCenterToPoint(localRef) - } else { - intersection = localShape.closestPoint(localRef, pathOptions) - } - } - - const cp = intersection - ? Util.transformPoint(intersection, targetMatrix) - : anchor - let cpOffset = options.offset || 0 - if (options.stroked !== false) { - if (typeof cpOffset === 'object') { - cpOffset = { ...cpOffset } - if (cpOffset.x == null) { - cpOffset.x = 0 - } - cpOffset.x += getStrokeWidth(node) / 2 - } else { - cpOffset += getStrokeWidth(node) / 2 - } - } - - return offset(cp, line.start, cpOffset) -} diff --git a/packages/x6-next/src/registry/connection-point/index.ts b/packages/x6-next/src/registry/connection-point/index.ts deleted file mode 100644 index cb20632e7df..00000000000 --- a/packages/x6-next/src/registry/connection-point/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Point, Line } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { Edge } from '../../model/edge' -import { CellView } from '../../view/cell' -import * as connectionPoints from './main' - -export namespace ConnectionPoint { - export type Definition = ( - line: Line, - view: CellView, - magnet: SVGElement, - options: T, - type: Edge.TerminalType, - ) => Point - - export type CommonDefinition = Definition - - export interface BaseOptions { - /** - * Offset the connection point from the anchor by the specified - * distance along the end edge path segment. - * - * Default is `0`. - */ - offset?: number | Point.PointLike - } - - export interface StrokedOptions extends BaseOptions { - /** - * If the stroke width should be included when calculating the - * connection point. - * - * Default is `false`. - */ - stroked?: boolean - } -} - -export namespace ConnectionPoint { - export type Presets = typeof ConnectionPoint['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[3] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace ConnectionPoint { - export const presets = connectionPoints - export const registry = Registry.create({ - type: 'connection point', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-next/src/registry/connection-point/main.ts b/packages/x6-next/src/registry/connection-point/main.ts deleted file mode 100644 index f0b301c448b..00000000000 --- a/packages/x6-next/src/registry/connection-point/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './bbox' -export * from './rect' -export * from './boundary' -export * from './anchor' diff --git a/packages/x6-next/src/registry/connection-point/rect.ts b/packages/x6-next/src/registry/connection-point/rect.ts deleted file mode 100644 index 8b93163e9e3..00000000000 --- a/packages/x6-next/src/registry/connection-point/rect.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { FunctionExt } from '@antv/x6-common' -import { bbox } from './bbox' -import { offset, getStrokeWidth } from './util' -import { ConnectionPoint } from './index' - -export interface RectangleOptions extends ConnectionPoint.StrokedOptions {} - -/** - * Places the connection point at the intersection between the - * link path end segment and the element's unrotated bbox. - */ -export const rect: ConnectionPoint.Definition = function ( - line, - view, - magnet, - options, - type, -) { - const cell = view.cell - const angle = cell.isNode() ? cell.getAngle() : 0 - if (angle === 0) { - return FunctionExt.call(bbox, this, line, view, magnet, options, type) - } - - const bboxRaw = view.getUnrotatedBBoxOfElement(magnet) - if (options.stroked) { - bboxRaw.inflate(getStrokeWidth(magnet) / 2) - } - const center = bboxRaw.getCenter() - const lineRaw = line.clone().rotate(angle, center) - const intersections = lineRaw.setLength(1e6).intersect(bboxRaw) - const p = - intersections && intersections.length - ? lineRaw.start.closest(intersections)!.rotate(-angle, center) - : line.end - return offset(p, line.start, options.offset) -} diff --git a/packages/x6-next/src/registry/connection-point/util.ts b/packages/x6-next/src/registry/connection-point/util.ts deleted file mode 100644 index 8962784d3df..00000000000 --- a/packages/x6-next/src/registry/connection-point/util.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Point, Line } from '@antv/x6-geometry' - -export function offset( - p1: Point, - p2: Point, - offset?: number | Point.PointLike, -) { - let tx: number | undefined - if (typeof offset === 'object') { - if (Number.isFinite(offset.y)) { - const line = new Line(p2, p1) - const { start, end } = line.parallel(offset.y) - p2 = start // eslint-disable-line - p1 = end // eslint-disable-line - } - tx = offset.x - } else { - tx = offset - } - - if (tx == null || !Number.isFinite(tx)) { - return p1 - } - - const length = p1.distance(p2) - if (tx === 0 && length > 0) { - return p1 - } - return p1.move(p2, -Math.min(tx, length - 1)) -} - -export function getStrokeWidth(magnet: SVGElement) { - const stroke = magnet.getAttribute('stroke-width') - if (stroke === null) { - return 0 - } - return parseFloat(stroke) || 0 -} - -export function findShapeNode(magnet: Element) { - if (magnet == null) { - return null - } - - let node = magnet - do { - let tagName = node.tagName - if (typeof tagName !== 'string') return null - tagName = tagName.toUpperCase() - if (tagName === 'G') { - node = node.firstElementChild as Element - } else if (tagName === 'TITLE') { - node = node.nextElementSibling as Element - } else break - } while (node) - - return node -} diff --git a/packages/x6-next/src/registry/connection-strategy/index.ts b/packages/x6-next/src/registry/connection-strategy/index.ts deleted file mode 100644 index 21bd428de85..00000000000 --- a/packages/x6-next/src/registry/connection-strategy/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { Edge } from '../../model' -import { CellView } from '../../view' -import * as strategies from './main' -import { Graph } from '../../graph' - -export namespace ConnectionStrategy { - export type Definition = ( - this: Graph, - terminal: Edge.TerminalCellData, - cellView: CellView, - magnet: Element, - coords: Point.PointLike, - edge: Edge, - type: Edge.TerminalType, - options: KeyValue, - ) => Edge.TerminalCellData -} - -export namespace ConnectionStrategy { - export type Presets = typeof ConnectionStrategy['presets'] - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: KeyValue - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace ConnectionStrategy { - export const presets = strategies - export const registry = Registry.create({ - type: 'connection strategy', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-next/src/registry/connection-strategy/main.ts b/packages/x6-next/src/registry/connection-strategy/main.ts deleted file mode 100644 index 718ea692bc5..00000000000 --- a/packages/x6-next/src/registry/connection-strategy/main.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './noop' -export * from './pin' diff --git a/packages/x6-next/src/registry/connection-strategy/noop.ts b/packages/x6-next/src/registry/connection-strategy/noop.ts deleted file mode 100644 index c618f812c8c..00000000000 --- a/packages/x6-next/src/registry/connection-strategy/noop.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ConnectionStrategy } from './index' - -export const noop: ConnectionStrategy.Definition = (terminal) => terminal diff --git a/packages/x6-next/src/registry/connection-strategy/pin.ts b/packages/x6-next/src/registry/connection-strategy/pin.ts deleted file mode 100644 index 42eea3cdb6f..00000000000 --- a/packages/x6-next/src/registry/connection-strategy/pin.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Node, Edge } from '../../model' -import { EdgeView, NodeView } from '../../view' -import { ConnectionStrategy } from './index' - -function toPercentage(value: number, max: number) { - if (max === 0) { - return '0%' - } - - return `${Math.round((value / max) * 100)}%` -} - -function pin(relative: boolean) { - const strategy: ConnectionStrategy.Definition = ( - terminal, - view, - magnet, - coords, - ) => { - return view.isEdgeElement(magnet) - ? pinEdgeTerminal(relative, terminal, view as EdgeView, magnet, coords) - : pinNodeTerminal(relative, terminal, view as NodeView, magnet, coords) - } - - return strategy -} - -function pinNodeTerminal( - relative: boolean, - data: Edge.TerminalCellData, - view: NodeView, - magnet: Element, - coords: Point.PointLike, -) { - const node = view.cell as Node - const angle = node.getAngle() - const bbox = view.getUnrotatedBBoxOfElement(magnet as SVGElement) - const center = node.getBBox().getCenter() - const pos = Point.create(coords).rotate(angle, center) - - let dx: number | string = pos.x - bbox.x - let dy: number | string = pos.y - bbox.y - - if (relative) { - dx = toPercentage(dx, bbox.width) - dy = toPercentage(dy, bbox.height) - } - - data.anchor = { - name: 'topLeft', - args: { - dx, - dy, - rotate: true, - }, - } - - return data -} - -function pinEdgeTerminal( - relative: boolean, - end: Edge.TerminalCellData, - view: EdgeView, - magnet: Element, - coords: Point.PointLike, -) { - const connection = view.getConnection() - if (!connection) { - return end - } - - const length = connection.closestPointLength(coords) - if (relative) { - const totalLength = connection.length() - end.anchor = { - name: 'ratio', - args: { - ratio: length / totalLength, - }, - } - } else { - end.anchor = { - name: 'length', - args: { - length, - }, - } - } - - return end -} - -export const pinRelative = pin(true) -export const pinAbsolute = pin(false) diff --git a/packages/x6-next/src/registry/connector/index.ts b/packages/x6-next/src/registry/connector/index.ts deleted file mode 100644 index c0010ffa07d..00000000000 --- a/packages/x6-next/src/registry/connector/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Point, Path } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { EdgeView } from '../../view' -import * as connectors from './main' - -export namespace Connector { - export interface BaseOptions { - raw?: boolean - } - - export type Definition = ( - this: EdgeView, - sourcePoint: Point.PointLike, - targetPoint: Point.PointLike, - routePoints: Point.PointLike[], - options: T, - edgeView: EdgeView, - ) => Path | string -} - -export namespace Connector { - export type Presets = typeof Connector['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[3] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace Connector { - export const presets = connectors - export const registry = Registry.create({ - type: 'connector', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-next/src/registry/connector/jumpover.ts b/packages/x6-next/src/registry/connector/jumpover.ts deleted file mode 100644 index 1f99d465ad3..00000000000 --- a/packages/x6-next/src/registry/connector/jumpover.ts +++ /dev/null @@ -1,388 +0,0 @@ -/* eslint-disable no-underscore-dangle */ - -import { Point, Line, Path } from '@antv/x6-geometry' -import { Edge } from '../../model' -import { EdgeView } from '../../view' -import { Connector } from './index' - -// takes care of math. error for case when jump is too close to end of line -const CLOSE_PROXIMITY_PADDING = 1 -const F13 = 1 / 3 -const F23 = 2 / 3 - -function setupUpdating(view: EdgeView) { - let updateList = (view.graph as any)._jumpOverUpdateList - - // first time setup for this paper - if (updateList == null) { - updateList = (view.graph as any)._jumpOverUpdateList = [] - - /** - * Handler for a batch:stop event to force - * update of all registered links with jump over connector - */ - view.graph.on('cell:mouseup', () => { - const list = (view.graph as any)._jumpOverUpdateList - for (let i = 0; i < list.length; i += 1) { - list[i].update() - } - }) - - view.graph.on('model:reseted', () => { - updateList = (view.graph as any)._jumpOverUpdateList = [] - }) - } - - // add this link to a list so it can be updated when some other link is updated - if (updateList.indexOf(view) < 0) { - updateList.push(view) - - // watch for change of connector type or removal of link itself - // to remove the link from a list of jump over connectors - const clean = () => updateList.splice(updateList.indexOf(view), 1) - view.cell.once('change:connector', clean) - view.cell.once('removed', clean) - } -} - -function createLines( - sourcePoint: Point.PointLike, - targetPoint: Point.PointLike, - route: Point.PointLike[] = [], -) { - const points = [sourcePoint, ...route, targetPoint] - const lines: Line[] = [] - - points.forEach((point, idx) => { - const next = points[idx + 1] - if (next != null) { - lines.push(new Line(point, next)) - } - }) - - return lines -} - -function findLineIntersections(line: Line, crossCheckLines: Line[]) { - const intersections: Point[] = [] - crossCheckLines.forEach((crossCheckLine) => { - const intersection = line.intersectsWithLine(crossCheckLine) - if (intersection) { - intersections.push(intersection) - } - }) - return intersections -} - -function getDistence(p1: Point, p2: Point) { - return new Line(p1, p2).squaredLength() -} - -/** - * Split input line into multiple based on intersection points. - */ -function createJumps(line: Line, intersections: Point[], jumpSize: number) { - return intersections.reduce((memo, point, idx) => { - // skipping points that were merged with the previous line - // to make bigger arc over multiple lines that are close to each other - if (skippedPoints.includes(point)) { - return memo - } - - // always grab the last line from buffer and modify it - const lastLine = memo.pop() || line - - // calculate start and end of jump by moving by a given size of jump - const jumpStart = Point.create(point).move(lastLine.start, -jumpSize) - let jumpEnd = Point.create(point).move(lastLine.start, +jumpSize) - - // now try to look at the next intersection point - const nextPoint = intersections[idx + 1] - if (nextPoint != null) { - const distance = jumpEnd.distance(nextPoint) - if (distance <= jumpSize) { - // next point is close enough, move the jump end by this - // difference and mark the next point to be skipped - jumpEnd = nextPoint.move(lastLine.start, distance) - skippedPoints.push(nextPoint) - } - } else { - // this block is inside of `else` as an optimization so the distance is - // not calculated when we know there are no other intersection points - const endDistance = jumpStart.distance(lastLine.end) - // if the end is too close to possible jump, draw remaining line instead of a jump - if (endDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) { - memo.push(lastLine) - return memo - } - } - - const startDistance = jumpEnd.distance(lastLine.start) - if (startDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) { - // if the start of line is too close to jump, draw that line instead of a jump - memo.push(lastLine) - return memo - } - - // finally create a jump line - const jumpLine = new Line(jumpStart, jumpEnd) - // it's just simple line but with a `isJump` property - jumppedLines.push(jumpLine) - - memo.push( - new Line(lastLine.start, jumpStart), - jumpLine, - new Line(jumpEnd, lastLine.end), - ) - - return memo - }, []) -} - -function buildPath( - lines: Line[], - jumpSize: number, - jumpType: JumpType, - radius: number, -) { - const path = new Path() - let segment - - // first move to the start of a first line - segment = Path.createSegment('M', lines[0].start) - path.appendSegment(segment) - - lines.forEach((line, index) => { - if (jumppedLines.includes(line)) { - let angle - let diff - - let control1 - let control2 - - if (jumpType === 'arc') { - // approximates semicircle with 2 curves - angle = -90 - // determine rotation of arc based on difference between points - diff = line.start.diff(line.end) - // make sure the arc always points up (or right) - const xAxisRotate = diff.x < 0 || (diff.x === 0 && diff.y < 0) - if (xAxisRotate) { - angle += 180 - } - - const center = line.getCenter() - const centerLine = new Line(center, line.end).rotate(angle, center) - - let halfLine - - // first half - halfLine = new Line(line.start, center) - control1 = halfLine.pointAt(2 / 3).rotate(angle, line.start) - control2 = centerLine.pointAt(1 / 3).rotate(-angle, centerLine.end) - - segment = Path.createSegment('C', control1, control2, centerLine.end) - path.appendSegment(segment) - - // second half - halfLine = new Line(center, line.end) - - control1 = centerLine.pointAt(1 / 3).rotate(angle, centerLine.end) - control2 = halfLine.pointAt(1 / 3).rotate(-angle, line.end) - - segment = Path.createSegment('C', control1, control2, line.end) - path.appendSegment(segment) - } else if (jumpType === 'gap') { - segment = Path.createSegment('M', line.end) - path.appendSegment(segment) - } else if (jumpType === 'cubic') { - // approximates semicircle with 1 curve - angle = line.start.theta(line.end) - - const xOffset = jumpSize * 0.6 - let yOffset = jumpSize * 1.35 - - // determine rotation of arc based on difference between points - diff = line.start.diff(line.end) - // make sure the arc always points up (or right) - const xAxisRotate = diff.x < 0 || (diff.x === 0 && diff.y < 0) - if (xAxisRotate) { - yOffset *= -1 - } - - control1 = new Point( - line.start.x + xOffset, - line.start.y + yOffset, - ).rotate(angle, line.start) - control2 = new Point(line.end.x - xOffset, line.end.y + yOffset).rotate( - angle, - line.end, - ) - - segment = Path.createSegment('C', control1, control2, line.end) - path.appendSegment(segment) - } - } else { - const nextLine = lines[index + 1] - if (radius === 0 || !nextLine || jumppedLines.includes(nextLine)) { - segment = Path.createSegment('L', line.end) - path.appendSegment(segment) - } else { - buildRoundedSegment(radius, path, line.end, line.start, nextLine.end) - } - } - }) - - return path -} - -function buildRoundedSegment( - offset: number, - path: Path, - curr: Point, - prev: Point, - next: Point, -) { - const prevDistance = curr.distance(prev) / 2 - const nextDistance = curr.distance(next) / 2 - - const startMove = -Math.min(offset, prevDistance) - const endMove = -Math.min(offset, nextDistance) - - const roundedStart = curr.clone().move(prev, startMove).round() - const roundedEnd = curr.clone().move(next, endMove).round() - - const control1 = new Point( - F13 * roundedStart.x + F23 * curr.x, - F23 * curr.y + F13 * roundedStart.y, - ) - const control2 = new Point( - F13 * roundedEnd.x + F23 * curr.x, - F23 * curr.y + F13 * roundedEnd.y, - ) - - let segment - segment = Path.createSegment('L', roundedStart) - path.appendSegment(segment) - - segment = Path.createSegment('C', control1, control2, roundedEnd) - path.appendSegment(segment) -} - -export type JumpType = 'arc' | 'gap' | 'cubic' - -export interface JumpoverConnectorOptions extends Connector.BaseOptions { - size?: number - radius?: number - type?: JumpType - ignoreConnectors?: string[] -} - -let jumppedLines: Line[] -let skippedPoints: Point[] - -export const jumpover: Connector.Definition = - function (sourcePoint, targetPoint, routePoints, options = {}) { - jumppedLines = [] - skippedPoints = [] - - setupUpdating(this) - - const jumpSize = options.size || 5 - const jumpType = options.type || 'arc' - const radius = options.radius || 0 - // list of connector types not to jump over. - const ignoreConnectors = options.ignoreConnectors || ['smooth'] - - const graph = this.graph - const model = graph.model - const allLinks = model.getEdges() as Edge[] - - // there is just one link, draw it directly - if (allLinks.length === 1) { - return buildPath( - createLines(sourcePoint, targetPoint, routePoints), - jumpSize, - jumpType, - radius, - ) - } - - const edge = this.cell - const thisIndex = allLinks.indexOf(edge) - const defaultConnector = graph.options.connecting.connector || {} - - // not all links are meant to be jumped over. - const edges = allLinks.filter((link, idx) => { - const connector = link.getConnector() || (defaultConnector as any) - - // avoid jumping over links with connector type listed in `ignored connectors`. - if (ignoreConnectors.includes(connector.name)) { - return false - } - // filter out links that are above this one and have the same connector type - // otherwise there would double hoops for each intersection - if (idx > thisIndex) { - return connector.name !== 'jumpover' - } - return true - }) - - // find views for all links - const linkViews = edges.map((edge) => { - return graph.findViewByCell(edge) as EdgeView - }) - - // create lines for this link - const thisLines = createLines(sourcePoint, targetPoint, routePoints) - - // create lines for all other links - const linkLines = linkViews.map((linkView) => { - if (linkView == null) { - return [] - } - if (linkView === this) { - return thisLines - } - return createLines( - linkView.sourcePoint, - linkView.targetPoint, - linkView.routePoints, - ) - }) - - // transform lines for this link by splitting with jump lines at - // points of intersection with other links - const jumpingLines: Line[] = [] - - thisLines.forEach((line) => { - // iterate all links and grab the intersections with this line - // these are then sorted by distance so the line can be split more easily - - const intersections = edges - .reduce((memo, link, i) => { - // don't intersection with itself - if (link !== edge) { - const lineIntersections = findLineIntersections(line, linkLines[i]) - memo.push(...lineIntersections) - } - return memo - }, []) - .sort((a, b) => getDistence(line.start, a) - getDistence(line.start, b)) - - if (intersections.length > 0) { - // split the line based on found intersection points - jumpingLines.push(...createJumps(line, intersections, jumpSize)) - } else { - // without any intersection the line goes uninterrupted - jumpingLines.push(line) - } - }) - - const path = buildPath(jumpingLines, jumpSize, jumpType, radius) - - jumppedLines = [] - skippedPoints = [] - - return options.raw ? path : path.serialize() - } diff --git a/packages/x6-next/src/registry/connector/loop.ts b/packages/x6-next/src/registry/connector/loop.ts deleted file mode 100644 index c01cf6e5c71..00000000000 --- a/packages/x6-next/src/registry/connector/loop.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Path, Point } from '@antv/x6-geometry' -import { Connector } from './index' - -export interface LoopConnectorOptions extends Connector.BaseOptions { - split?: boolean | number -} - -export const loop: Connector.Definition = function ( - sourcePoint, - targetPoint, - routePoints, - options = {}, -) { - const fix = routePoints.length === 3 ? 0 : 1 - const p1 = Point.create(routePoints[0 + fix]) - const p2 = Point.create(routePoints[2 + fix]) - const center = Point.create(routePoints[1 + fix]) - - if (!Point.equals(sourcePoint, targetPoint)) { - const middle = new Point( - (sourcePoint.x + targetPoint.x) / 2, - (sourcePoint.y + targetPoint.y) / 2, - ) - const angle = middle.angleBetween( - Point.create(sourcePoint).rotate(90, middle), - center, - ) - if (angle > 1) { - p1.rotate(180 - angle, middle) - p2.rotate(180 - angle, middle) - center.rotate(180 - angle, middle) - } - } - - const pathData = ` - M ${sourcePoint.x} ${sourcePoint.y} - Q ${p1.x} ${p1.y} ${center.x} ${center.y} - Q ${p2.x} ${p2.y} ${targetPoint.x} ${targetPoint.y} - ` - - return options.raw ? Path.parse(pathData) : pathData -} diff --git a/packages/x6-next/src/registry/connector/main.ts b/packages/x6-next/src/registry/connector/main.ts deleted file mode 100644 index e8fda3c5e3c..00000000000 --- a/packages/x6-next/src/registry/connector/main.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './normal' -export * from './loop' -export * from './rounded' -export * from './smooth' -export * from './jumpover' diff --git a/packages/x6-next/src/registry/connector/normal.ts b/packages/x6-next/src/registry/connector/normal.ts deleted file mode 100644 index b21ff7163be..00000000000 --- a/packages/x6-next/src/registry/connector/normal.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Polyline, Path } from '@antv/x6-geometry' -import { Connector } from './index' - -export const normal: Connector.Definition = function ( - sourcePoint, - targetPoint, - routePoints, - options = {}, -) { - const points = [sourcePoint, ...routePoints, targetPoint] - const polyline = new Polyline(points) - const path = new Path(polyline) - return options.raw ? path : path.serialize() -} diff --git a/packages/x6-next/src/registry/connector/rounded.ts b/packages/x6-next/src/registry/connector/rounded.ts deleted file mode 100644 index aa94518a58b..00000000000 --- a/packages/x6-next/src/registry/connector/rounded.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Point, Path } from '@antv/x6-geometry' -import { Connector } from './index' - -export interface RoundedConnectorOptions extends Connector.BaseOptions { - radius?: number -} - -export const rounded: Connector.Definition = function ( - sourcePoint, - targetPoint, - routePoints, - options = {}, -) { - const path = new Path() - - path.appendSegment(Path.createSegment('M', sourcePoint)) - - const f13 = 1 / 3 - const f23 = 2 / 3 - const radius = options.radius || 10 - - let prevDistance - let nextDistance - for (let i = 0, ii = routePoints.length; i < ii; i += 1) { - const curr = Point.create(routePoints[i]) - const prev = routePoints[i - 1] || sourcePoint - const next = routePoints[i + 1] || targetPoint - - prevDistance = nextDistance || curr.distance(prev) / 2 - nextDistance = curr.distance(next) / 2 - - const startMove = -Math.min(radius, prevDistance) - const endMove = -Math.min(radius, nextDistance) - - const roundedStart = curr.clone().move(prev, startMove).round() - const roundedEnd = curr.clone().move(next, endMove).round() - - const control1 = new Point( - f13 * roundedStart.x + f23 * curr.x, - f23 * curr.y + f13 * roundedStart.y, - ) - const control2 = new Point( - f13 * roundedEnd.x + f23 * curr.x, - f23 * curr.y + f13 * roundedEnd.y, - ) - - path.appendSegment(Path.createSegment('L', roundedStart)) - path.appendSegment(Path.createSegment('C', control1, control2, roundedEnd)) - } - - path.appendSegment(Path.createSegment('L', targetPoint)) - - return options.raw ? path : path.serialize() -} diff --git a/packages/x6-next/src/registry/connector/smooth.ts b/packages/x6-next/src/registry/connector/smooth.ts deleted file mode 100644 index 0aa67df31d2..00000000000 --- a/packages/x6-next/src/registry/connector/smooth.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Curve, Path } from '@antv/x6-geometry' -import { Connector } from './index' - -export interface SmoothConnectorOptions extends Connector.BaseOptions { - direction?: 'H' | 'V' -} - -export const smooth: Connector.Definition = function ( - sourcePoint, - targetPoint, - routePoints, - options = {}, -) { - let path - let direction = options.direction - - if (routePoints && routePoints.length !== 0) { - const points = [sourcePoint, ...routePoints, targetPoint] - const curves = Curve.throughPoints(points) - path = new Path(curves) - } else { - // If we have no route, use a default cubic bezier curve, cubic bezier - // requires two control points, the control points have `x` midway - // between source and target. This produces an S-like curve. - - path = new Path() - path.appendSegment(Path.createSegment('M', sourcePoint)) - - if (!direction) { - direction = - Math.abs(sourcePoint.x - targetPoint.x) >= - Math.abs(sourcePoint.y - targetPoint.y) - ? 'H' - : 'V' - } - - if (direction === 'H') { - const controlPointX = (sourcePoint.x + targetPoint.x) / 2 - path.appendSegment( - Path.createSegment( - 'C', - controlPointX, - sourcePoint.y, - controlPointX, - targetPoint.y, - targetPoint.x, - targetPoint.y, - ), - ) - } else { - const controlPointY = (sourcePoint.y + targetPoint.y) / 2 - path.appendSegment( - Path.createSegment( - 'C', - sourcePoint.x, - controlPointY, - targetPoint.x, - controlPointY, - targetPoint.x, - targetPoint.y, - ), - ) - } - } - - return options.raw ? path : path.serialize() -} diff --git a/packages/x6-next/src/registry/edge-anchor/closest.ts b/packages/x6-next/src/registry/edge-anchor/closest.ts deleted file mode 100644 index bf03fc0e53b..00000000000 --- a/packages/x6-next/src/registry/edge-anchor/closest.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { ResolveOptions, resolve } from '../node-anchor/util' -import { EdgeAnchor } from './index' - -export interface ClosestEndpointOptions extends ResolveOptions {} - -export const getClosestPoint: EdgeAnchor.ResolvedDefinition = - function ( - view, - magnet, - refPoint, - options, // eslint-disable-line @typescript-eslint/no-unused-vars - ) { - const closestPoint = view.getClosestPoint(refPoint) - return closestPoint != null ? closestPoint : new Point() - } - -export const closest = resolve< - EdgeAnchor.ResolvedDefinition, - EdgeAnchor.Definition ->(getClosestPoint) diff --git a/packages/x6-next/src/registry/edge-anchor/index.ts b/packages/x6-next/src/registry/edge-anchor/index.ts deleted file mode 100644 index 3a9a2c6489d..00000000000 --- a/packages/x6-next/src/registry/edge-anchor/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { Edge } from '../../model/edge' -import { EdgeView } from '../../view' -import * as anchors from './main' - -export namespace EdgeAnchor { - export type Definition = ( - this: EdgeView, - view: EdgeView, - magnet: SVGElement, - ref: Point | Point.PointLike | SVGElement, - options: T, - type: Edge.TerminalType, - ) => Point - - export type CommonDefinition = Definition - - export type ResolvedDefinition = ( - this: EdgeView, - view: EdgeView, - magnet: SVGElement, - refPoint: Point, - options: T, - ) => Point -} - -export namespace EdgeAnchor { - export type Presets = typeof EdgeAnchor['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[3] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace EdgeAnchor { - export const presets = anchors - export const registry = Registry.create({ - type: 'edge endpoint', - }) - registry.register(presets, true) -} diff --git a/packages/x6-next/src/registry/edge-anchor/length.ts b/packages/x6-next/src/registry/edge-anchor/length.ts deleted file mode 100644 index 2c8df553b71..00000000000 --- a/packages/x6-next/src/registry/edge-anchor/length.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { EdgeAnchor } from './index' - -export interface LengthEndpointOptions { - length?: number -} - -export const length: EdgeAnchor.Definition = function ( - view, - magnet, - ref, - options, -) { - const length = options.length != null ? options.length : 20 - return view.getPointAtLength(length)! -} diff --git a/packages/x6-next/src/registry/edge-anchor/main.ts b/packages/x6-next/src/registry/edge-anchor/main.ts deleted file mode 100644 index b0922e0301b..00000000000 --- a/packages/x6-next/src/registry/edge-anchor/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './ratio' -export * from './length' -export * from './orth' -export { - closest, - ClosestEndpointOptions as ClosestAnchorOptions, -} from './closest' diff --git a/packages/x6-next/src/registry/edge-anchor/orth.ts b/packages/x6-next/src/registry/edge-anchor/orth.ts deleted file mode 100644 index d37c2ac8534..00000000000 --- a/packages/x6-next/src/registry/edge-anchor/orth.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Line, Point } from '@antv/x6-geometry' -import { FunctionExt } from '@antv/x6-common' -import { ResolveOptions, resolve, getPointAtEdge } from '../node-anchor/util' -import { getClosestPoint } from './closest' -import { EdgeAnchor } from './index' - -export interface OrthEndpointOptions extends ResolveOptions { - fallbackAt?: number | string -} - -const orthogonal: EdgeAnchor.ResolvedDefinition = - function (view, magnet, refPoint, options): Point { - const OFFSET = 1e6 - const path = view.getConnection()! - const segmentSubdivisions = view.getConnectionSubdivisions() - const vLine = new Line( - refPoint.clone().translate(0, OFFSET), - refPoint.clone().translate(0, -OFFSET), - ) - const hLine = new Line( - refPoint.clone().translate(OFFSET, 0), - refPoint.clone().translate(-OFFSET, 0), - ) - - const vIntersections = vLine.intersect(path, { - segmentSubdivisions, - }) - - const hIntersections = hLine.intersect(path, { - segmentSubdivisions, - }) - - const intersections = [] - if (vIntersections) { - intersections.push(...vIntersections) - } - if (hIntersections) { - intersections.push(...hIntersections) - } - - if (intersections.length > 0) { - return refPoint.closest(intersections)! - } - - if (options.fallbackAt != null) { - return getPointAtEdge(view, options.fallbackAt)! - } - - return FunctionExt.call( - getClosestPoint, - this, - view, - magnet, - refPoint, - options, - ) - } - -export const orth = resolve< - EdgeAnchor.ResolvedDefinition, - EdgeAnchor.Definition ->(orthogonal) diff --git a/packages/x6-next/src/registry/edge-anchor/ratio.ts b/packages/x6-next/src/registry/edge-anchor/ratio.ts deleted file mode 100644 index 04f4c40d0f8..00000000000 --- a/packages/x6-next/src/registry/edge-anchor/ratio.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { EdgeAnchor } from './index' - -export interface RatioEndpointOptions { - ratio?: number -} - -export const ratio: EdgeAnchor.Definition = function ( - view, - magnet, - ref, - options, -) { - let ratio = options.ratio != null ? options.ratio : 0.5 - if (ratio > 1) { - ratio /= 100 - } - return view.getPointAtRatio(ratio)! -} diff --git a/packages/x6-next/src/registry/filter/blur.ts b/packages/x6-next/src/registry/filter/blur.ts deleted file mode 100644 index 5662cec6d24..00000000000 --- a/packages/x6-next/src/registry/filter/blur.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getNumber } from './util' - -export interface BlurArgs { - /** - * Horizontal blur. Default `2` - */ - x?: number - /** - * Vertical blur. - */ - y?: number -} - -export function blur(args: BlurArgs = {}) { - const x = getNumber(args.x, 2) - const stdDeviation = - args.y != null && Number.isFinite(args.y) ? [x, args.y] : x - - return ` - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/brightness.ts b/packages/x6-next/src/registry/filter/brightness.ts deleted file mode 100644 index a1f283e0710..00000000000 --- a/packages/x6-next/src/registry/filter/brightness.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { getNumber } from './util' - -export interface BrightnessArgs { - /** - * The proportion of the conversion. - * A value of `1` leaves the input unchanged. - * A value of `0` will create an image that is completely black. - * - * Default `1`. - */ - amount?: number -} - -export function brightness(args: BrightnessArgs = {}) { - const amount = getNumber(args.amount, 1) - return ` - - - - - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/contrast.ts b/packages/x6-next/src/registry/filter/contrast.ts deleted file mode 100644 index e9a307638cd..00000000000 --- a/packages/x6-next/src/registry/filter/contrast.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { getNumber } from './util' - -export interface ContrastArgs { - /** - * The proportion of the conversion. - * A value of `1` leaves the input unchanged. - * A value of `0` will create an image that is completely black. - * - * Default `1`. - */ - amount?: number -} - -export function contrast(args: ContrastArgs = {}) { - const amount = getNumber(args.amount, 1) - const amount2 = 0.5 - amount / 2 - - return ` - - - - - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/drop-shadow.ts b/packages/x6-next/src/registry/filter/drop-shadow.ts deleted file mode 100644 index de7398297bb..00000000000 --- a/packages/x6-next/src/registry/filter/drop-shadow.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { getString, getNumber } from './util' - -export interface DropShadowArgs { - dx?: number - dy?: number - color?: string - blur?: number - opacity?: number -} - -export function dropShadow(args: DropShadowArgs = {}) { - const dx = getNumber(args.dx, 0) - const dy = getNumber(args.dy, 0) - const color = getString(args.color, 'black') - const blur = getNumber(args.blur, 4) - const opacity = getNumber(args.opacity, 1) - - return 'SVGFEDropShadowElement' in window - ? ` - - `.trim() - : ` - - - - - - - - - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/gray-scale.ts b/packages/x6-next/src/registry/filter/gray-scale.ts deleted file mode 100644 index 16c90cb00d3..00000000000 --- a/packages/x6-next/src/registry/filter/gray-scale.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getNumber } from './util' - -export interface GrayScaleArgs { - /** - * The proportion of the conversion. - * A value of `1` is completely grayscale. - * A value of `0` leaves the input unchanged. - * - * Default `1`. - */ - amount?: number -} - -export function grayScale(args: GrayScaleArgs = {}) { - const amount = getNumber(args.amount, 1) - const a = 0.2126 + 0.7874 * (1 - amount) - const b = 0.7152 - 0.7152 * (1 - amount) - const c = 0.0722 - 0.0722 * (1 - amount) - const d = 0.2126 - 0.2126 * (1 - amount) - const e = 0.7152 + 0.2848 * (1 - amount) - const f = 0.0722 - 0.0722 * (1 - amount) - const g = 0.2126 - 0.2126 * (1 - amount) - const h = 0.0722 + 0.9278 * (1 - amount) - - return ` - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/highlight.ts b/packages/x6-next/src/registry/filter/highlight.ts deleted file mode 100644 index 631566c9bdc..00000000000 --- a/packages/x6-next/src/registry/filter/highlight.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { getString, getNumber } from './util' - -export interface HighlightArgs { - /** - * Highlight color. Default `'red'`. - */ - color?: string - /** - * Highlight blur. Default `0`. - */ - blur?: number - /** - * Highlight width. Default `1`. - */ - width?: number - /** - * Highlight opacity. Default `1`. - */ - opacity?: number -} - -export function highlight(args: HighlightArgs = {}) { - const color = getString(args.color, 'red') - const blur = getNumber(args.blur, 0) - const width = getNumber(args.width, 1) - const opacity = getNumber(args.opacity, 1) - - return ` - - - - - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/hue-rotate.ts b/packages/x6-next/src/registry/filter/hue-rotate.ts deleted file mode 100644 index c7b2392d02e..00000000000 --- a/packages/x6-next/src/registry/filter/hue-rotate.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { getNumber } from './util' - -export interface HueRotateArgs { - /** - * The number of degrees around the color. - * - * Default `0`. - */ - angle?: number -} - -export function hueRotate(args: HueRotateArgs = {}) { - const angle = getNumber(args.angle, 0) - return ` - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/index.ts b/packages/x6-next/src/registry/filter/index.ts deleted file mode 100644 index 6fbad5a147c..00000000000 --- a/packages/x6-next/src/registry/filter/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NonUndefined } from 'utility-types' -import { KeyValue, Registry } from '@antv/x6-common' -import * as filters from './main' - -export namespace Filter { - export type Definition = (args: T) => string - export type CommonDefinition = Definition -} - -export namespace Filter { - export type Presets = typeof Filter['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: NonUndefined[0]> - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace Filter { - export const presets = filters - export const registry = Registry.create({ - type: 'filter', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-next/src/registry/filter/invert.ts b/packages/x6-next/src/registry/filter/invert.ts deleted file mode 100644 index c72d0e4771e..00000000000 --- a/packages/x6-next/src/registry/filter/invert.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { getNumber } from './util' - -export interface InvertArgs { - /** - * The proportion of the conversion. - * A value of `1` is completely inverted. - * A value of `0` leaves the input unchanged. - * - * Default `1`. - */ - amount?: number -} - -export function invert(args: InvertArgs = {}) { - const amount = getNumber(args.amount, 1) - const amount2 = 1 - amount - return ` - - - - - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/main.ts b/packages/x6-next/src/registry/filter/main.ts deleted file mode 100644 index 432bd634d07..00000000000 --- a/packages/x6-next/src/registry/filter/main.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './outline' -export * from './highlight' -export * from './blur' -export * from './drop-shadow' -export * from './gray-scale' -export * from './sepia' -export * from './saturate' -export * from './hue-rotate' -export * from './invert' -export * from './brightness' -export * from './contrast' diff --git a/packages/x6-next/src/registry/filter/outline.ts b/packages/x6-next/src/registry/filter/outline.ts deleted file mode 100644 index e59cd17b323..00000000000 --- a/packages/x6-next/src/registry/filter/outline.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { getString, getNumber } from './util' - -export interface OutlineArgs { - /** - * Outline color. Default `'blue'`. - */ - color?: string - /** - * Outline width. Default `1` - */ - width?: number - /** - * Gap between outline and the element. Default `2` - */ - margin?: number - /** - * Outline opacity. Default `1` - */ - opacity?: number -} - -export function outline(args: OutlineArgs = {}) { - const color = getString(args.color, 'blue') - const width = getNumber(args.width, 1) - const margin = getNumber(args.margin, 2) - const opacity = getNumber(args.opacity, 1) - - const innerRadius = margin - const outerRadius = margin + width - - return ` - - - - - - - - - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/saturate.ts b/packages/x6-next/src/registry/filter/saturate.ts deleted file mode 100644 index 60ce4397aff..00000000000 --- a/packages/x6-next/src/registry/filter/saturate.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { getNumber } from './util' - -export interface SaturateArgs { - /** - * The proportion of the conversion. - * A value of `1` is completely un-saturated. - * A value of `0` leaves the input unchanged. - * - * Default `1`. - */ - amount?: number -} - -export function saturate(args: SaturateArgs = {}) { - const amount = getNumber(args.amount, 1) - return ` - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/sepia.ts b/packages/x6-next/src/registry/filter/sepia.ts deleted file mode 100644 index ef2f9554395..00000000000 --- a/packages/x6-next/src/registry/filter/sepia.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getNumber } from './util' - -export interface SepiaArgs { - /** - * The proportion of the conversion. - * A value of `1` is completely sepia. - * A value of `0` leaves the input unchanged. - * - * Default `1`. - */ - amount?: number -} -export function sepia(args: SepiaArgs = {}) { - const amount = getNumber(args.amount, 1) - const a = 0.393 + 0.607 * (1 - amount) - const b = 0.769 - 0.769 * (1 - amount) - const c = 0.189 - 0.189 * (1 - amount) - const d = 0.349 - 0.349 * (1 - amount) - const e = 0.686 + 0.314 * (1 - amount) - const f = 0.168 - 0.168 * (1 - amount) - const g = 0.272 - 0.272 * (1 - amount) - const h = 0.534 - 0.534 * (1 - amount) - const i = 0.131 + 0.869 * (1 - amount) - - return ` - - - - `.trim() -} diff --git a/packages/x6-next/src/registry/filter/util.ts b/packages/x6-next/src/registry/filter/util.ts deleted file mode 100644 index f55e32e76c9..00000000000 --- a/packages/x6-next/src/registry/filter/util.ts +++ /dev/null @@ -1,13 +0,0 @@ -export function getString( - value: string | null | undefined, - defaultValue: string, -) { - return value != null ? value : defaultValue -} - -export function getNumber( - num: number | null | undefined, - defaultValue: number, -) { - return num != null && Number.isFinite(num) ? num : defaultValue -} diff --git a/packages/x6-next/src/registry/grid/dot.ts b/packages/x6-next/src/registry/grid/dot.ts deleted file mode 100644 index 192b165db16..00000000000 --- a/packages/x6-next/src/registry/grid/dot.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Grid } from './index' - -export interface DotOptions extends Grid.Options {} - -export const dot: Grid.Definition = { - color: '#aaaaaa', - thickness: 1, - markup: 'rect', - update(elem, options) { - const width = options.thickness * options.sx - const height = options.thickness * options.sy - Dom.attr(elem, { - width, - height, - rx: width, - ry: height, - fill: options.color, - }) - }, -} diff --git a/packages/x6-next/src/registry/grid/double-mesh.ts b/packages/x6-next/src/registry/grid/double-mesh.ts deleted file mode 100644 index 4df5b85c5c0..00000000000 --- a/packages/x6-next/src/registry/grid/double-mesh.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Grid } from './index' - -export interface DoubleMeshOptions extends Grid.Options { - factor?: number -} - -export const doubleMesh: Grid.Definition[] = [ - { - color: 'rgba(224,224,224,1)', - thickness: 1, - markup: 'path', - update(elem, options) { - let d - const width = options.width - const height = options.height - const thickness = options.thickness - - if (width - thickness >= 0 && height - thickness >= 0) { - d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ') - } else { - d = 'M 0 0 0 0' - } - - Dom.attr(elem, { - d, - stroke: options.color, - 'stroke-width': options.thickness, - }) - }, - }, - { - color: 'rgba(224,224,224,0.2)', - thickness: 3, - factor: 4, - markup: 'path', - update(elem, options) { - let d - const factor = options.factor || 1 - const width = options.width * factor - const height = options.height * factor - const thickness = options.thickness - - if (width - thickness >= 0 && height - thickness >= 0) { - d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ') - } else { - d = 'M 0 0 0 0' - } - - // update wrapper size - options.width = width - options.height = height - - Dom.attr(elem, { - d, - stroke: options.color, - 'stroke-width': options.thickness, - }) - }, - }, -] diff --git a/packages/x6-next/src/registry/grid/fixed-dot.ts b/packages/x6-next/src/registry/grid/fixed-dot.ts deleted file mode 100644 index 98a8b48812b..00000000000 --- a/packages/x6-next/src/registry/grid/fixed-dot.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Grid } from './index' - -export interface FixedDotOptions extends Grid.Options {} - -export const fixedDot: Grid.Definition = { - color: '#aaaaaa', - thickness: 1, - markup: 'rect', - update(elem, options) { - const size = - options.sx <= 1 ? options.thickness * options.sx : options.thickness - Dom.attr(elem, { - width: size, - height: size, - rx: size, - ry: size, - fill: options.color, - }) - }, -} diff --git a/packages/x6-next/src/registry/grid/index.ts b/packages/x6-next/src/registry/grid/index.ts deleted file mode 100644 index 1390f8340e2..00000000000 --- a/packages/x6-next/src/registry/grid/index.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Dom, Vector, Registry, KeyValue } from '@antv/x6-common' -import * as patterns from './main' - -export class Grid { - root: Element - patterns: { [id: string]: Element } - - constructor() { - this.patterns = {} - this.root = Vector.create( - Dom.createSvgDocument(), - { - width: '100%', - height: '100%', - }, - [Dom.createSvgElement('defs')], - ).node - } - - add(id: string, elem: Element) { - const firstChild = this.root.childNodes[0] - if (firstChild) { - firstChild.appendChild(elem) - } - - this.patterns[id] = elem - - Vector.create('rect', { - width: '100%', - height: '100%', - fill: `url(#${id})`, - }).appendTo(this.root) - } - - get(id: string) { - return this.patterns[id] - } - - has(id: string) { - return this.patterns[id] != null - } -} - -export namespace Grid { - export interface Options { - color: string - thickness: number - } - - interface BaseDefinition extends Options { - markup: string - update: ( - elem: Element, - options: { - sx: number - sy: number - ox: number - oy: number - width: number - height: number - } & T, - ) => void - } - - export type Definition = T & BaseDefinition - export type CommonDefinition = - | Definition - | Definition[] -} - -export namespace Grid { - export const presets = patterns - export const registry = Registry.create({ - type: 'grid', - }) - - registry.register(presets, true) -} - -export namespace Grid { - export type Presets = typeof Grid['presets'] - - export type OptionsMap = { - dot: patterns.DotOptions - fixedDot: patterns.FixedDotOptions - mesh: patterns.MeshOptions - doubleMesh: patterns.DoubleMeshOptions[] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - type: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - type: Exclude - args?: KeyValue - } -} diff --git a/packages/x6-next/src/registry/grid/main.ts b/packages/x6-next/src/registry/grid/main.ts deleted file mode 100644 index 5fa3e4ea5ad..00000000000 --- a/packages/x6-next/src/registry/grid/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './dot' -export * from './fixed-dot' -export * from './mesh' -export * from './double-mesh' diff --git a/packages/x6-next/src/registry/grid/mesh.ts b/packages/x6-next/src/registry/grid/mesh.ts deleted file mode 100644 index 60b2e84fae6..00000000000 --- a/packages/x6-next/src/registry/grid/mesh.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Grid } from './index' - -export interface MeshOptions extends Grid.Options {} - -export const mesh: Grid.Definition = { - color: 'rgba(224,224,224,1)', - thickness: 1, - markup: 'path', - update(elem, options) { - let d - const width = options.width - const height = options.height - const thickness = options.thickness - - if (width - thickness >= 0 && height - thickness >= 0) { - d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ') - } else { - d = 'M 0 0 0 0' - } - - Dom.attr(elem, { - d, - stroke: options.color, - 'stroke-width': options.thickness, - }) - }, -} diff --git a/packages/x6-next/src/registry/highlighter/class.ts b/packages/x6-next/src/registry/highlighter/class.ts deleted file mode 100644 index 1b14d0843a8..00000000000 --- a/packages/x6-next/src/registry/highlighter/class.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Config } from '../../config' -import { Highlighter } from './index' - -export interface ClassHighlighterOptions { - className?: string -} - -const defaultClassName = Config.prefix('highlighted') - -export const className: Highlighter.Definition = { - highlight(cellView, magnet, options) { - const cls = (options && options.className) || defaultClassName - Dom.addClass(magnet, cls) - }, - unhighlight(cellView, magnet, options) { - const cls = (options && options.className) || defaultClassName - Dom.removeClass(magnet, cls) - }, -} diff --git a/packages/x6-next/src/registry/highlighter/index.ts b/packages/x6-next/src/registry/highlighter/index.ts deleted file mode 100644 index 48a250fd5e0..00000000000 --- a/packages/x6-next/src/registry/highlighter/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Registry, KeyValue } from '@antv/x6-common' -import { CellView } from '../../view' -import * as highlighters from './main' - -export namespace Highlighter { - export interface Definition { - highlight: (cellView: CellView, magnet: Element, options: T) => void - unhighlight: (cellView: CellView, magnet: Element, options: T) => void - } - - export type CommonDefinition = Highlighter.Definition -} - -export namespace Highlighter { - export function check( - name: string, - highlighter: Highlighter.CommonDefinition, - ) { - if (typeof highlighter.highlight !== 'function') { - throw new Error( - `Highlighter '${name}' is missing required \`highlight()\` method`, - ) - } - - if (typeof highlighter.unhighlight !== 'function') { - throw new Error( - `Highlighter '${name}' is missing required \`unhighlight()\` method`, - ) - } - } -} - -export namespace Highlighter { - export type Presets = typeof Highlighter['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[2] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace Highlighter { - export const presets = highlighters - export const registry = Registry.create({ - type: 'highlighter', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-next/src/registry/highlighter/main.ts b/packages/x6-next/src/registry/highlighter/main.ts deleted file mode 100644 index eec2d1029e8..00000000000 --- a/packages/x6-next/src/registry/highlighter/main.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './class' -export * from './opacity' -export * from './stroke' diff --git a/packages/x6-next/src/registry/highlighter/opacity.ts b/packages/x6-next/src/registry/highlighter/opacity.ts deleted file mode 100644 index bbf3bafd9f3..00000000000 --- a/packages/x6-next/src/registry/highlighter/opacity.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Config } from '../../config' -import { Highlighter } from './index' - -export interface OpacityHighlighterOptions {} - -const className = Config.prefix('highlight-opacity') - -export const opacity: Highlighter.Definition = { - highlight(cellView, magnet) { - Dom.addClass(magnet, className) - }, - - unhighlight(cellView, magnetEl) { - Dom.removeClass(magnetEl, className) - }, -} diff --git a/packages/x6-next/src/registry/highlighter/stroke.ts b/packages/x6-next/src/registry/highlighter/stroke.ts deleted file mode 100644 index 899b50059aa..00000000000 --- a/packages/x6-next/src/registry/highlighter/stroke.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { ObjectExt, Dom, Vector } from '@antv/x6-common' -import { Attr } from '../attr' -import { Config } from '../../config' -import { EdgeView } from '../../view' -import { Highlighter } from './index' -import { Util } from '../../util' - -export interface StrokeHighlighterOptions { - padding?: number - rx?: number - ry?: number - attrs?: Attr.SimpleAttrs -} - -const defaultOptions: StrokeHighlighterOptions = { - padding: 3, - rx: 0, - ry: 0, - attrs: { - 'stroke-width': 3, - stroke: '#FEB663', - }, -} - -export const stroke: Highlighter.Definition = { - highlight(cellView, magnet, options) { - const id = Private.getHighlighterId(magnet, options) - if (Private.hasCache(id)) { - return - } - - // eslint-disable-next-line - options = ObjectExt.defaultsDeep({}, options, defaultOptions) - - const magnetVel = Vector.create(magnet as SVGElement) - let pathData - let magnetBBox - - try { - pathData = magnetVel.toPathData() - } catch (error) { - // Failed to get path data from magnet element. - // Draw a rectangle around the entire cell view instead. - magnetBBox = Util.bbox(magnetVel.node, true) - pathData = Dom.rectToPathData({ ...options, ...magnetBBox }) - } - - const path = Dom.createSvgElement('path') - Dom.attr(path, { - d: pathData, - 'pointer-events': 'none', - 'vector-effect': 'non-scaling-stroke', - fill: 'none', - ...(options.attrs ? Dom.kebablizeAttrs(options.attrs) : null), - }) - - // const highlightVel = v.create('path').attr() - - if (cellView.isEdgeElement(magnet)) { - Dom.attr(path, 'd', (cellView as EdgeView).getConnectionPathData()) - } else { - let highlightMatrix = magnetVel.getTransformToElement( - cellView.container as SVGElement, - ) - - // Add padding to the highlight element. - const padding = options.padding - if (padding) { - if (magnetBBox == null) { - magnetBBox = Util.bbox(magnetVel.node, true) - } - - const cx = magnetBBox.x + magnetBBox.width / 2 - const cy = magnetBBox.y + magnetBBox.height / 2 - - magnetBBox = Util.transformRectangle(magnetBBox, highlightMatrix) - - const width = Math.max(magnetBBox.width, 1) - const height = Math.max(magnetBBox.height, 1) - const sx = (width + padding) / width - const sy = (height + padding) / height - - const paddingMatrix = Dom.createSVGMatrix({ - a: sx, - b: 0, - c: 0, - d: sy, - e: cx - sx * cx, - f: cy - sy * cy, - }) - - highlightMatrix = highlightMatrix.multiply(paddingMatrix) - } - - Dom.transform(path, highlightMatrix) - } - - Dom.addClass(path, Config.prefix('highlight-stroke')) - - const cell = cellView.cell - const removeHandler = () => Private.removeHighlighter(id) - - cell.on('removed', removeHandler) - if (cell.model) { - cell.model.on('reseted', removeHandler) - } - - cellView.container.appendChild(path) - Private.setCache(id, path) - }, - - unhighlight(cellView, magnet, opt) { - Private.removeHighlighter(Private.getHighlighterId(magnet, opt)) - }, -} - -namespace Private { - export function getHighlighterId( - magnet: Element, - options: StrokeHighlighterOptions, - ) { - Dom.ensureId(magnet) - return magnet.id + JSON.stringify(options) - } - - const cache: { [id: string]: Element } = {} - - export function setCache(id: string, elem: Element) { - cache[id] = elem - } - - export function hasCache(id: string) { - return cache[id] != null - } - - export function removeHighlighter(id: string) { - const elem = cache[id] - if (elem) { - Dom.remove(elem) - delete cache[id] - } - } -} diff --git a/packages/x6-next/src/registry/index.ts b/packages/x6-next/src/registry/index.ts deleted file mode 100644 index c0b5e38d62b..00000000000 --- a/packages/x6-next/src/registry/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export * from './grid' -export * from './background' -export * from './filter' -export * from './attr' -export * from './highlighter' -export * from './port-layout' -export * from './port-label-layout' -export * from './tool' -export * from './marker' -export * from './node-anchor' -export * from './edge-anchor' -export * from './connection-point' -export * from './router' -export * from './connector' diff --git a/packages/x6-next/src/registry/marker/async.ts b/packages/x6-next/src/registry/marker/async.ts deleted file mode 100644 index 3f38cb77616..00000000000 --- a/packages/x6-next/src/registry/marker/async.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Path } from '@antv/x6-geometry' -import { KeyValue } from '@antv/x6-common' -import { normalize } from './util' -import { Marker } from './index' - -export interface AsyncMarkerOptions extends KeyValue { - width?: number - height?: number - offset?: number - open?: boolean - flip?: boolean -} - -export const async: Marker.Factory = ({ - width, - height, - offset, - open, - flip, - ...attrs -}) => { - let h = height || 6 - const w = width || 10 - const opened = open === true - const fliped = flip === true - const result: Marker.Result = { ...attrs, tagName: 'path' } - - if (fliped) { - h = -h - } - - const path = new Path() - - path.moveTo(0, h).lineTo(w, 0) - - if (!opened) { - path.lineTo(w, h) - path.close() - } else { - result.fill = 'none' - } - - result.d = normalize(path.serialize(), { - x: offset || -w / 2, - y: h / 2, - }) - - return result -} diff --git a/packages/x6-next/src/registry/marker/circle.ts b/packages/x6-next/src/registry/marker/circle.ts deleted file mode 100644 index 5ab7fa22ce1..00000000000 --- a/packages/x6-next/src/registry/marker/circle.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Path } from '@antv/x6-geometry' -import { Attr } from '../attr' -import { normalize } from './util' -import { Marker } from './index' - -export interface CircleMarkerOptions extends Attr.SimpleAttrs { - r?: number -} - -export interface CirclePlusMarkerOptions extends CircleMarkerOptions {} - -export const circle: Marker.Factory = ({ - r, - ...attrs -}) => { - const radius = r || 5 - return { - cx: radius, - ...attrs, - tagName: 'circle', - r: radius, - } -} - -export const circlePlus: Marker.Factory = ({ - r, - ...attrs -}) => { - const radius = r || 5 - const path = new Path() - - path.moveTo(radius, 0).lineTo(radius, radius * 2) - path.moveTo(0, radius).lineTo(radius * 2, radius) - - return { - children: [ - { - ...circle({ r: radius }), - fill: 'none', - }, - { - ...attrs, - tagName: 'path', - d: normalize(path.serialize(), -radius), - }, - ] as any, - } -} diff --git a/packages/x6-next/src/registry/marker/classic.ts b/packages/x6-next/src/registry/marker/classic.ts deleted file mode 100644 index 045ca2c6e3f..00000000000 --- a/packages/x6-next/src/registry/marker/classic.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Path } from '@antv/x6-geometry' -import { NumberExt, KeyValue } from '@antv/x6-common' -import { normalize } from './util' -import { Marker } from './index' - -interface Common { - size?: number - width?: number - height?: number - offset?: number -} - -export interface BlockMarkerOptions extends Common, KeyValue { - open?: boolean -} - -export interface ClassicMarkerOptions extends Common, KeyValue { - factor?: number -} - -export const block: Marker.Factory = ({ - size, - width, - height, - offset, - open, - ...attrs -}) => { - return createClassicMarker( - { size, width, height, offset }, - open === true, - true, - undefined, - attrs, - ) -} - -export const classic: Marker.Factory = ({ - size, - width, - height, - offset, - factor, - ...attrs -}) => { - return createClassicMarker( - { size, width, height, offset }, - false, - false, - factor, - attrs, - ) -} - -function createClassicMarker( - options: Common, - open: boolean, - full: boolean, - factor: number = 3 / 4, - attrs: KeyValue = {}, -) { - const size = options.size || 10 - const width = options.width || size - const height = options.height || size - const path = new Path() - const localAttrs: { fill?: string } = {} - - if (open) { - path - .moveTo(width, 0) - .lineTo(0, height / 2) - .lineTo(width, height) - localAttrs.fill = 'none' - } else { - path.moveTo(0, height / 2) - path.lineTo(width, 0) - - if (!full) { - const f = NumberExt.clamp(factor, 0, 1) - path.lineTo(width * f, height / 2) - } - - path.lineTo(width, height) - path.close() - } - - return { - ...localAttrs, - ...attrs, - tagName: 'path', - d: normalize(path.serialize(), { - x: options.offset != null ? options.offset : -width / 2, - }), - } -} diff --git a/packages/x6-next/src/registry/marker/cross.ts b/packages/x6-next/src/registry/marker/cross.ts deleted file mode 100644 index 4da6dc8a95a..00000000000 --- a/packages/x6-next/src/registry/marker/cross.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Path } from '@antv/x6-geometry' -import { Attr } from '../attr' -import { normalize } from './util' -import { Marker } from './index' - -export interface CrossMarkerOptions extends Attr.SimpleAttrs { - size?: number - width?: number - height?: number - offset?: number -} - -export const cross: Marker.Factory = ({ - size, - width, - height, - offset, - ...attrs -}) => { - const s = size || 10 - const w = width || s - const h = height || s - - const path = new Path() - path.moveTo(0, 0).lineTo(w, h).moveTo(0, h).lineTo(w, 0) - - return { - ...attrs, - tagName: 'path', - fill: 'none', - d: normalize(path.serialize(), offset || -w / 2), - } -} diff --git a/packages/x6-next/src/registry/marker/diamond.ts b/packages/x6-next/src/registry/marker/diamond.ts deleted file mode 100644 index 0077e8c5a5b..00000000000 --- a/packages/x6-next/src/registry/marker/diamond.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Path } from '@antv/x6-geometry' -import { Attr } from '../attr' -import { normalize } from './util' -import { Marker } from './index' - -export interface DiamondMarkerOptions extends Attr.SimpleAttrs { - size?: number - width?: number - height?: number - offset?: number -} - -export const diamond: Marker.Factory = ({ - size, - width, - height, - offset, - ...attrs -}) => { - const s = size || 10 - const w = width || s - const h = height || s - - const path = new Path() - path - .moveTo(0, h / 2) - .lineTo(w / 2, 0) - .lineTo(w, h / 2) - .lineTo(w / 2, h) - .close() - - return { - ...attrs, - tagName: 'path', - d: normalize(path.serialize(), offset == null ? -w / 2 : offset), - } -} diff --git a/packages/x6-next/src/registry/marker/ellipse.ts b/packages/x6-next/src/registry/marker/ellipse.ts deleted file mode 100644 index d367d536198..00000000000 --- a/packages/x6-next/src/registry/marker/ellipse.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Attr } from '../attr' -import { Marker } from './index' - -export interface EllipseMarkerOptions extends Attr.SimpleAttrs { - rx?: number - ry?: number -} - -export const ellipse: Marker.Factory = ({ - rx, - ry, - ...attrs -}) => { - const radiusX = rx || 5 - const radiusy = ry || 5 - return { - cx: radiusX, - ...attrs, - tagName: 'ellipse', - rx: radiusX, - ry: radiusy, - } -} diff --git a/packages/x6-next/src/registry/marker/index.ts b/packages/x6-next/src/registry/marker/index.ts deleted file mode 100644 index c3735becad4..00000000000 --- a/packages/x6-next/src/registry/marker/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Registry, KeyValue } from '@antv/x6-common' -import { Attr } from '../attr' -import * as markers from './main' -import { normalize as normalizeMarker } from './util' - -export namespace Marker { - export type Factory = (options: T) => Result - - export interface BaseResult extends Attr.SimpleAttrs { - tagName?: string - } - - export type Result = BaseResult & { - id?: string - refX?: number - refY?: number - // @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker - markerUnits?: string - markerOrient?: 'auto' | 'auto-start-reverse' | number - children?: BaseResult[] - } -} - -export namespace Marker { - export type Presets = typeof Marker['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[0] - } - - export type NativeNames = keyof OptionsMap - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace Marker { - export const presets = markers - export const registry = Registry.create({ - type: 'marker', - }) - registry.register(presets, true) -} - -export namespace Marker { - export const normalize = normalizeMarker -} diff --git a/packages/x6-next/src/registry/marker/main.ts b/packages/x6-next/src/registry/marker/main.ts deleted file mode 100644 index 3c91d12a0b4..00000000000 --- a/packages/x6-next/src/registry/marker/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './classic' -export * from './diamond' -export * from './path' -export * from './cross' -export * from './async' -export * from './circle' -export * from './ellipse' diff --git a/packages/x6-next/src/registry/marker/path.ts b/packages/x6-next/src/registry/marker/path.ts deleted file mode 100644 index 626b203d833..00000000000 --- a/packages/x6-next/src/registry/marker/path.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Attr } from '../attr' -import { normalize } from './util' -import { Marker } from './index' - -export interface PathMarkerOptions extends Attr.SimpleAttrs { - d: string - offsetX?: number - offsetY?: number -} - -export const path: Marker.Factory = ({ - d, - offsetX, - offsetY, - ...attrs -}) => { - return { - ...attrs, - tagName: 'path', - d: normalize(d, offsetX, offsetY), - } -} diff --git a/packages/x6-next/src/registry/marker/util.ts b/packages/x6-next/src/registry/marker/util.ts deleted file mode 100644 index 5146dabd7ae..00000000000 --- a/packages/x6-next/src/registry/marker/util.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Path } from '@antv/x6-geometry' - -/** - * Normalizes marker's path data by translate the center - * of an arbitrary path at <0 + offset,0>. - */ -export function normalize(d: string, offset: { x?: number; y?: number }): string -export function normalize(d: string, offsetX?: number, offsetY?: number): string -export function normalize( - d: string, - offset1?: number | { x?: number; y?: number }, - offset2?: number, -) { - let offsetX: number | undefined - let offsetY: number | undefined - if (typeof offset1 === 'object') { - offsetX = offset1.x - offsetY = offset1.y - } else { - offsetX = offset1 - offsetY = offset2 - } - - const path = Path.parse(d) - const bbox = path.bbox() - if (bbox) { - let ty = -bbox.height / 2 - bbox.y - let tx = -bbox.width / 2 - bbox.x - if (typeof offsetX === 'number') { - tx -= offsetX - } - if (typeof offsetY === 'number') { - ty -= offsetY - } - - path.translate(tx, ty) - } - - return path.serialize() -} diff --git a/packages/x6-next/src/registry/node-anchor/bbox.ts b/packages/x6-next/src/registry/node-anchor/bbox.ts deleted file mode 100644 index b344a7c3a09..00000000000 --- a/packages/x6-next/src/registry/node-anchor/bbox.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { NodeAnchor } from './index' - -export interface BBoxEndpointOptions { - dx?: number | string - dy?: number | string - /** - * Should the anchor bbox rotate with the terminal view. - * - * Default is `false`, meaning that the unrotated bbox is used. - */ - rotate?: boolean -} - -export const center = createBBoxAnchor('center') -export const top = createBBoxAnchor('topCenter') -export const bottom = createBBoxAnchor('bottomCenter') -export const left = createBBoxAnchor('leftMiddle') -export const right = createBBoxAnchor('rightMiddle') -export const topLeft = createBBoxAnchor('topLeft') -export const topRight = createBBoxAnchor('topRight') -export const bottomLeft = createBBoxAnchor('bottomLeft') -export const bottomRight = createBBoxAnchor('bottomRight') - -function createBBoxAnchor( - method: - | 'center' - | 'topCenter' - | 'bottomCenter' - | 'leftMiddle' - | 'rightMiddle' - | 'topLeft' - | 'topRight' - | 'bottomLeft' - | 'bottomRight', -): NodeAnchor.Definition { - return function (view, magnet, ref, options: BBoxEndpointOptions = {}) { - const bbox = options.rotate - ? view.getUnrotatedBBoxOfElement(magnet) - : view.getBBoxOfElement(magnet) - const result = bbox[method] - - result.x += NumberExt.normalizePercentage(options.dx, bbox.width) - result.y += NumberExt.normalizePercentage(options.dy, bbox.height) - - const cell = view.cell - return options.rotate - ? result.rotate(-cell.getAngle(), cell.getBBox().getCenter()) - : result - } -} diff --git a/packages/x6-next/src/registry/node-anchor/index.ts b/packages/x6-next/src/registry/node-anchor/index.ts deleted file mode 100644 index 6a60f69eb42..00000000000 --- a/packages/x6-next/src/registry/node-anchor/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { Edge } from '../../model' -import { EdgeView, NodeView } from '../../view' -import * as anchors from './main' - -export namespace NodeAnchor { - export type Definition = ( - this: EdgeView, - /** - * The NodeView to which we are connecting. - */ - nodeView: NodeView, - /** - * The SVGElement in our graph that contains the magnet - * (element/subelement/port) to which we are connecting. - */ - magnet: SVGElement, - /** - * A reference to another component of the edge path that may be - * necessary to find this anchor point. If we are calling this method - * for a source anchor, it is the first vertex, or if there are no - * vertices the target anchor. If we are calling this method for a target - * anchor, it is the last vertex, or if there are no vertices the source - * anchor... - */ - ref: Point | Point.PointLike | SVGElement, - args: T, - type: Edge.TerminalType, - ) => Point - - export type CommonDefinition = Definition - - export type ResolvedDefinition = ( - this: EdgeView, - view: NodeView, - magnet: SVGElement, - refPoint: Point, - args: T, - ) => Point -} - -export namespace NodeAnchor { - export type Presets = typeof NodeAnchor['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[3] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace NodeAnchor { - export const presets = anchors - export const registry = Registry.create({ - type: 'node endpoint', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-next/src/registry/node-anchor/main.ts b/packages/x6-next/src/registry/node-anchor/main.ts deleted file mode 100644 index 2ecd6814879..00000000000 --- a/packages/x6-next/src/registry/node-anchor/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './bbox' -export * from './orth' -export * from './node-center' -export * from './middle-side' diff --git a/packages/x6-next/src/registry/node-anchor/middle-side.ts b/packages/x6-next/src/registry/node-anchor/middle-side.ts deleted file mode 100644 index 02e4bf74f78..00000000000 --- a/packages/x6-next/src/registry/node-anchor/middle-side.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { ResolveOptions, resolve } from './util' -import { NodeAnchor } from './index' - -export interface MiddleSideEndpointOptions extends ResolveOptions { - rotate?: boolean - padding?: number - direction?: 'H' | 'V' -} - -const middleSide: NodeAnchor.ResolvedDefinition = - function (view, magnet, refPoint, options) { - let bbox - let angle = 0 - let center - - const node = view.cell - if (options.rotate) { - bbox = view.getUnrotatedBBoxOfElement(magnet) - center = node.getBBox().getCenter() - angle = node.getAngle() - } else { - bbox = view.getBBoxOfElement(magnet) - } - - const padding = options.padding - if (padding != null && Number.isFinite(padding)) { - bbox.inflate(padding) - } - - if (options.rotate) { - refPoint.rotate(angle, center) - } - - const side = bbox.getNearestSideToPoint(refPoint) - let result: Point - switch (side) { - case 'left': - result = bbox.getLeftMiddle() - break - case 'right': - result = bbox.getRightMiddle() - break - case 'top': - result = bbox.getTopCenter() - break - case 'bottom': - result = bbox.getBottomCenter() - break - default: - break - } - - const direction = options.direction - if (direction === 'H') { - if (side === 'top' || side === 'bottom') { - if (refPoint.x <= bbox.x + bbox.width) { - result = bbox.getLeftMiddle() - } else { - result = bbox.getRightMiddle() - } - } - } else if (direction === 'V') { - if (refPoint.y <= bbox.y + bbox.height) { - result = bbox.getTopCenter() - } else { - result = bbox.getBottomCenter() - } - } - - return options.rotate ? result!.rotate(-angle, center) : result! - } - -/** - * Places the anchor of the edge in the middle of the side of view bbox - * closest to the other endpoint. - */ -export const midSide = resolve< - NodeAnchor.ResolvedDefinition, - NodeAnchor.Definition ->(middleSide) diff --git a/packages/x6-next/src/registry/node-anchor/node-center.ts b/packages/x6-next/src/registry/node-anchor/node-center.ts deleted file mode 100644 index 18adde73515..00000000000 --- a/packages/x6-next/src/registry/node-anchor/node-center.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NodeAnchor } from './index' - -export interface NodeCenterEndpointOptions { - dx?: number - dy?: number -} - -/** - * Places the anchor of the edge at center of the node bbox. - */ -export const nodeCenter: NodeAnchor.Definition = - function (view, magnet, ref, options, endType) { - const result = view.cell.getConnectionPoint(this.cell, endType) - if (options.dx || options.dy) { - result.translate(options.dx || 0, options.dy || 0) - } - return result - } diff --git a/packages/x6-next/src/registry/node-anchor/orth.ts b/packages/x6-next/src/registry/node-anchor/orth.ts deleted file mode 100644 index 37bd9cecd81..00000000000 --- a/packages/x6-next/src/registry/node-anchor/orth.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Angle } from '@antv/x6-geometry' -import { ResolveOptions, resolve } from './util' -import { NodeAnchor } from './index' - -export interface OrthEndpointOptions extends ResolveOptions { - padding: number -} - -const orthogonal: NodeAnchor.ResolvedDefinition = - function (view, magnet, refPoint, options) { - const angle = view.cell.getAngle() - const bbox = view.getBBoxOfElement(magnet) - const result = bbox.getCenter() - const topLeft = bbox.getTopLeft() - const bottomRight = bbox.getBottomRight() - - let padding = options.padding - if (!Number.isFinite(padding)) { - padding = 0 - } - - if ( - topLeft.y + padding <= refPoint.y && - refPoint.y <= bottomRight.y - padding - ) { - const dy = refPoint.y - result.y - result.x += - angle === 0 || angle === 180 - ? 0 - : (dy * 1) / Math.tan(Angle.toRad(angle)) - result.y += dy - } else if ( - topLeft.x + padding <= refPoint.x && - refPoint.x <= bottomRight.x - padding - ) { - const dx = refPoint.x - result.x - result.y += - angle === 90 || angle === 270 ? 0 : dx * Math.tan(Angle.toRad(angle)) - result.x += dx - } - - return result - } - -/** - * Tries to place the anchor of the edge inside the view bbox so that the - * edge is made orthogonal. The anchor is placed along two line segments - * inside the view bbox (between the centers of the top and bottom side and - * between the centers of the left and right sides). If it is not possible - * to place the anchor so that the edge would be orthogonal, the anchor is - * placed at the center of the view bbox instead. - */ -export const orth = resolve< - NodeAnchor.ResolvedDefinition, - NodeAnchor.Definition ->(orthogonal) diff --git a/packages/x6-next/src/registry/node-anchor/util.ts b/packages/x6-next/src/registry/node-anchor/util.ts deleted file mode 100644 index 5c913e55a81..00000000000 --- a/packages/x6-next/src/registry/node-anchor/util.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { Point } from '@antv/x6-geometry' -import { EdgeView } from '../../view' - -export interface ResolveOptions { - fixedAt?: number | string -} - -// eslint-disable-next-line -export function resolve(fn: S): T { - return function ( - this: EdgeView, - view: EdgeView, - magnet: SVGElement, - ref: any, - options: ResolveOptions, - ) { - if (ref instanceof Element) { - const refView = this.graph.findViewByElem(ref) - let refPoint - if (refView) { - if (refView.isEdgeElement(ref)) { - const distance = options.fixedAt != null ? options.fixedAt : '50%' - refPoint = getPointAtEdge(refView as EdgeView, distance) - } else { - refPoint = refView.getBBoxOfElement(ref).getCenter() - } - } else { - refPoint = new Point() - } - return fn.call(this, view, magnet, refPoint, options) - } - return fn.apply(this, arguments) // eslint-disable-line - } as any as T -} - -export function getPointAtEdge(edgeView: EdgeView, value: string | number) { - const isPercentage = NumberExt.isPercentage(value) - const num = typeof value === 'string' ? parseFloat(value) : value - if (isPercentage) { - return edgeView.getPointAtRatio(num / 100) - } - return edgeView.getPointAtLength(num) -} diff --git a/packages/x6-next/src/registry/port-label-layout/index.ts b/packages/x6-next/src/registry/port-label-layout/index.ts deleted file mode 100644 index c43230e5e9e..00000000000 --- a/packages/x6-next/src/registry/port-label-layout/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Point, Rectangle } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { Attr } from '../attr' -import * as layouts from './main' - -export namespace PortLabelLayout { - export interface Result { - position: Point.PointLike - angle: number - attrs: Attr.CellAttrs - } - - export type Definition = ( - portPosition: Point, - elemBBox: Rectangle, - args: T, - ) => Result - - export type CommonDefinition = Definition - - export interface CommonOptions { - x?: number - y?: number - angle?: number - attrs?: Attr.CellAttrs - } -} - -export namespace PortLabelLayout { - export type Presets = typeof PortLabelLayout['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[2] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace PortLabelLayout { - export const presets = layouts - export const registry = Registry.create({ - type: 'port label layout', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-next/src/registry/port-label-layout/inout.ts b/packages/x6-next/src/registry/port-label-layout/inout.ts deleted file mode 100644 index 866f8757e53..00000000000 --- a/packages/x6-next/src/registry/port-label-layout/inout.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { Point, Rectangle } from '@antv/x6-geometry' -import { PortLabelLayout } from './index' -import { toResult } from './util' - -export interface InOutArgs extends PortLabelLayout.CommonOptions { - offset?: number -} - -export const outside: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => outsideLayout(portPosition, elemBBox, false, args) - -export const outsideOriented: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => outsideLayout(portPosition, elemBBox, true, args) - -export const inside: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => insideLayout(portPosition, elemBBox, false, args) - -export const insideOriented: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => insideLayout(portPosition, elemBBox, true, args) - -function outsideLayout( - portPosition: Point, - elemBBox: Rectangle, - autoOrient: boolean, - args: InOutArgs, -) { - const offset = args.offset != null ? args.offset : 15 - const angle = elemBBox.getCenter().theta(portPosition) - const bboxAngles = getBBoxAngles(elemBBox) - - let y - let tx - let ty - let textAnchor - let orientAngle = 0 - - if (angle < bboxAngles[1] || angle > bboxAngles[2]) { - y = '.3em' - tx = offset - ty = 0 - textAnchor = 'start' - } else if (angle < bboxAngles[0]) { - y = '0' - tx = 0 - ty = -offset - if (autoOrient) { - orientAngle = -90 - textAnchor = 'start' - } else { - textAnchor = 'middle' - } - } else if (angle < bboxAngles[3]) { - y = '.3em' - tx = -offset - ty = 0 - textAnchor = 'end' - } else { - y = '.6em' - tx = 0 - ty = offset - if (autoOrient) { - orientAngle = 90 - textAnchor = 'start' - } else { - textAnchor = 'middle' - } - } - - return toResult( - { - position: { - x: Math.round(tx), - y: Math.round(ty), - }, - angle: orientAngle, - attrs: { - '.': { - y, - 'text-anchor': textAnchor, - }, - }, - }, - args, - ) -} - -function insideLayout( - portPosition: Point, - elemBBox: Rectangle, - autoOrient: boolean, - args: InOutArgs, -) { - const offset = args.offset != null ? args.offset : 15 - const angle = elemBBox.getCenter().theta(portPosition) - const bboxAngles = getBBoxAngles(elemBBox) - - let y - let tx - let ty - let textAnchor - let orientAngle = 0 - - if (angle < bboxAngles[1] || angle > bboxAngles[2]) { - y = '.3em' - tx = -offset - ty = 0 - textAnchor = 'end' - } else if (angle < bboxAngles[0]) { - y = '.6em' - tx = 0 - ty = offset - if (autoOrient) { - orientAngle = 90 - textAnchor = 'start' - } else { - textAnchor = 'middle' - } - } else if (angle < bboxAngles[3]) { - y = '.3em' - tx = offset - ty = 0 - textAnchor = 'start' - } else { - y = '0em' - tx = 0 - ty = -offset - if (autoOrient) { - orientAngle = -90 - textAnchor = 'start' - } else { - textAnchor = 'middle' - } - } - - return toResult( - { - position: { - x: Math.round(tx), - y: Math.round(ty), - }, - angle: orientAngle, - attrs: { - '.': { - y, - 'text-anchor': textAnchor, - }, - }, - }, - args, - ) -} - -function getBBoxAngles(elemBBox: Rectangle) { - const center = elemBBox.getCenter() - - const tl = center.theta(elemBBox.getTopLeft()) - const bl = center.theta(elemBBox.getBottomLeft()) - const br = center.theta(elemBBox.getBottomRight()) - const tr = center.theta(elemBBox.getTopRight()) - - return [tl, tr, br, bl] -} diff --git a/packages/x6-next/src/registry/port-label-layout/main.ts b/packages/x6-next/src/registry/port-label-layout/main.ts deleted file mode 100644 index dc587ffe371..00000000000 --- a/packages/x6-next/src/registry/port-label-layout/main.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './side' -export * from './inout' -export * from './radial' diff --git a/packages/x6-next/src/registry/port-label-layout/radial.ts b/packages/x6-next/src/registry/port-label-layout/radial.ts deleted file mode 100644 index 9d1557763dd..00000000000 --- a/packages/x6-next/src/registry/port-label-layout/radial.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { PortLabelLayout } from './index' -import { toResult } from './util' - -export interface RadialArgs extends PortLabelLayout.CommonOptions { - offset?: number -} - -export const radial: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => radialLayout(portPosition.diff(elemBBox.getCenter()), false, args) - -export const radialOriented: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => radialLayout(portPosition.diff(elemBBox.getCenter()), true, args) - -function radialLayout( - portCenterOffset: Point, - autoOrient: boolean, - args: RadialArgs, -) { - const offset = args.offset != null ? args.offset : 20 - const origin = new Point(0, 0) - const angle = -portCenterOffset.theta(origin) - const pos = portCenterOffset - .clone() - .move(origin, offset) - .diff(portCenterOffset) - .round() - - let y = '.3em' - let textAnchor - let orientAngle = angle - - if ((angle + 90) % 180 === 0) { - textAnchor = autoOrient ? 'end' : 'middle' - if (!autoOrient && angle === -270) { - y = '0em' - } - } else if (angle > -270 && angle < -90) { - textAnchor = 'start' - orientAngle = angle - 180 - } else { - textAnchor = 'end' - } - - return toResult( - { - position: pos.round().toJSON(), - angle: autoOrient ? orientAngle : 0, - attrs: { - '.': { - y, - 'text-anchor': textAnchor, - }, - }, - }, - args, - ) -} diff --git a/packages/x6-next/src/registry/port-label-layout/side.ts b/packages/x6-next/src/registry/port-label-layout/side.ts deleted file mode 100644 index 8d5a583b581..00000000000 --- a/packages/x6-next/src/registry/port-label-layout/side.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { PortLabelLayout } from './index' -import { toResult } from './util' - -export interface SideArgs extends PortLabelLayout.CommonOptions {} - -export const manual: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => toResult({ position: elemBBox.getTopLeft() }, args) - -export const left: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => - toResult( - { - position: { x: -15, y: 0 }, - attrs: { '.': { y: '.3em', 'text-anchor': 'end' } }, - }, - args, - ) - -export const right: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => - toResult( - { - position: { x: 15, y: 0 }, - attrs: { '.': { y: '.3em', 'text-anchor': 'start' } }, - }, - args, - ) - -export const top: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => - toResult( - { - position: { x: 0, y: -15 }, - attrs: { '.': { 'text-anchor': 'middle' } }, - }, - args, - ) - -export const bottom: PortLabelLayout.Definition = ( - portPosition, - elemBBox, - args, -) => - toResult( - { - position: { x: 0, y: 15 }, - attrs: { '.': { y: '.6em', 'text-anchor': 'middle' } }, - }, - args, - ) diff --git a/packages/x6-next/src/registry/port-label-layout/util.ts b/packages/x6-next/src/registry/port-label-layout/util.ts deleted file mode 100644 index d5b5a0666d7..00000000000 --- a/packages/x6-next/src/registry/port-label-layout/util.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ObjectExt } from '@antv/x6-common' -import { PortLabelLayout } from './index' - -const defaults: PortLabelLayout.Result = { - position: { x: 0, y: 0 }, - angle: 0, - attrs: { - '.': { - y: '0', - 'text-anchor': 'start', - }, - }, -} - -export function toResult( - preset: Partial, - args?: Partial, -): PortLabelLayout.Result { - const { x, y, angle, attrs } = args || {} - return ObjectExt.defaultsDeep( - {}, - { angle, attrs, position: { x, y } }, - preset, - defaults, - ) -} diff --git a/packages/x6-next/src/registry/port-layout/absolute.ts b/packages/x6-next/src/registry/port-layout/absolute.ts deleted file mode 100644 index 00decc7f385..00000000000 --- a/packages/x6-next/src/registry/port-layout/absolute.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PortLayout } from './index' -import { normalizePoint, toResult } from './util' - -export interface AbsoluteArgs { - x?: string | number - y?: string | number - angle?: number -} - -export const absolute: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, -) => { - return portsPositionArgs.map(({ x, y, angle }) => - toResult(normalizePoint(elemBBox, { x, y }), angle || 0), - ) -} diff --git a/packages/x6-next/src/registry/port-layout/ellipse.ts b/packages/x6-next/src/registry/port-layout/ellipse.ts deleted file mode 100644 index 7a66b6203ae..00000000000 --- a/packages/x6-next/src/registry/port-layout/ellipse.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Rectangle, Ellipse } from '@antv/x6-geometry' -import { PortLayout } from './index' -import { toResult } from './util' - -export interface EllipseArgs extends PortLayout.CommonArgs { - start?: number - step?: number - compensateRotate?: boolean - /** - * delta radius - */ - dr?: number -} - -export const ellipse: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - const startAngle = groupPositionArgs.start || 0 - const stepAngle = groupPositionArgs.step || 20 - - return ellipseLayout( - portsPositionArgs, - elemBBox, - startAngle, - (index, count) => (index + 0.5 - count / 2) * stepAngle, - ) -} - -export const ellipseSpread: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - const startAngle = groupPositionArgs.start || 0 - const stepAngle = groupPositionArgs.step || 360 / portsPositionArgs.length - - return ellipseLayout(portsPositionArgs, elemBBox, startAngle, (index) => { - return index * stepAngle - }) -} - -function ellipseLayout( - portsPositionArgs: EllipseArgs[], - elemBBox: Rectangle, - startAngle: number, - stepFn: (index: number, count: number) => number, -) { - const center = elemBBox.getCenter() - const start = elemBBox.getTopCenter() - const ratio = elemBBox.width / elemBBox.height - const ellipse = Ellipse.fromRect(elemBBox) - const count = portsPositionArgs.length - - return portsPositionArgs.map((item, index) => { - const angle = startAngle + stepFn(index, count) - const p = start.clone().rotate(-angle, center).scale(ratio, 1, center) - - const theta = item.compensateRotate ? -ellipse.tangentTheta(p) : 0 - - if (item.dx || item.dy) { - p.translate(item.dx || 0, item.dy || 0) - } - - if (item.dr) { - p.move(center, item.dr) - } - - return toResult(p.round(), theta, item) - }) -} diff --git a/packages/x6-next/src/registry/port-layout/index.ts b/packages/x6-next/src/registry/port-layout/index.ts deleted file mode 100644 index 80a70b5ec09..00000000000 --- a/packages/x6-next/src/registry/port-layout/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Rectangle, Point } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import * as layouts from './main' - -export namespace PortLayout { - export const presets = layouts - export const registry = Registry.create({ - type: 'port layout', - }) - - registry.register(presets, true) -} - -export namespace PortLayout { - export interface Result { - position: Point.PointLike - angle?: number - } - - export interface CommonArgs { - x?: number - y?: number - dx?: number - dy?: number - } - - export type Definition = ( - portsPositionArgs: T[], - elemBBox: Rectangle, - groupPositionArgs: T, - ) => Result[] - - export type CommonDefinition = Definition -} - -export namespace PortLayout { - export type Presets = typeof PortLayout['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[2] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: CommonArgs - } -} diff --git a/packages/x6-next/src/registry/port-layout/line.ts b/packages/x6-next/src/registry/port-layout/line.ts deleted file mode 100644 index aaaedd05e8b..00000000000 --- a/packages/x6-next/src/registry/port-layout/line.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Point, Line } from '@antv/x6-geometry' -import { normalizePoint, toResult } from './util' -import { PortLayout } from './index' - -export interface SideArgs extends PortLayout.CommonArgs { - strict?: boolean -} - -export interface LineArgs extends SideArgs { - start?: Point.PointLike - end?: Point.PointLike -} - -export const line: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - const start = normalizePoint( - elemBBox, - groupPositionArgs.start || elemBBox.getOrigin(), - ) - const end = normalizePoint( - elemBBox, - groupPositionArgs.end || elemBBox.getCorner(), - ) - - return lineLayout(portsPositionArgs, start, end, groupPositionArgs) -} - -export const left: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - return lineLayout( - portsPositionArgs, - elemBBox.getTopLeft(), - elemBBox.getBottomLeft(), - groupPositionArgs, - ) -} - -export const right: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - return lineLayout( - portsPositionArgs, - elemBBox.getTopRight(), - elemBBox.getBottomRight(), - groupPositionArgs, - ) -} - -export const top: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - return lineLayout( - portsPositionArgs, - elemBBox.getTopLeft(), - elemBBox.getTopRight(), - groupPositionArgs, - ) -} - -export const bottom: PortLayout.Definition = ( - portsPositionArgs, - elemBBox, - groupPositionArgs, -) => { - return lineLayout( - portsPositionArgs, - elemBBox.getBottomLeft(), - elemBBox.getBottomRight(), - groupPositionArgs, - ) -} - -function lineLayout( - portsPositionArgs: SideArgs[], - p1: Point, - p2: Point, - groupPositionArgs: SideArgs, -) { - const line = new Line(p1, p2) - const length = portsPositionArgs.length - return portsPositionArgs.map(({ strict, ...offset }, index) => { - const ratio = - strict || groupPositionArgs.strict - ? (index + 1) / (length + 1) - : (index + 0.5) / length - - const p = line.pointAt(ratio) - if (offset.dx || offset.dy) { - p.translate(offset.dx || 0, offset.dy || 0) - } - - return toResult(p.round(), 0, offset) - }) -} diff --git a/packages/x6-next/src/registry/port-layout/main.ts b/packages/x6-next/src/registry/port-layout/main.ts deleted file mode 100644 index 82e79b305b7..00000000000 --- a/packages/x6-next/src/registry/port-layout/main.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './absolute' -export * from './ellipse' -export * from './line' diff --git a/packages/x6-next/src/registry/port-layout/util.ts b/packages/x6-next/src/registry/port-layout/util.ts deleted file mode 100644 index a51d50f3f1f..00000000000 --- a/packages/x6-next/src/registry/port-layout/util.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { PortLayout } from './index' - -export function normalizePoint( - bbox: Rectangle, - args: { - x?: string | number - y?: string | number - } = {}, -) { - return new Point( - NumberExt.normalizePercentage(args.x, bbox.width), - NumberExt.normalizePercentage(args.y, bbox.height), - ) -} - -export function toResult( - point: Point, - angle?: number, - rawArgs?: T, -): PortLayout.Result { - return { - angle, - position: point.toJSON(), - ...rawArgs, - } -} diff --git a/packages/x6-next/src/registry/router/er.ts b/packages/x6-next/src/registry/router/er.ts deleted file mode 100644 index 24e402313df..00000000000 --- a/packages/x6-next/src/registry/router/er.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { Router } from './index' - -export interface ErRouterOptions { - min?: number - offset?: number | 'center' - direction?: 'T' | 'B' | 'L' | 'R' | 'H' | 'V' -} - -export const er: Router.Definition = function ( - vertices, - options, - edgeView, -) { - const offsetRaw = options.offset || 32 - const min = options.min == null ? 16 : options.min - - let offset = 0 - let direction = options.direction - - const sourceBBox = edgeView.sourceBBox - const targetBBox = edgeView.targetBBox - const sourcePoint = sourceBBox.getCenter() - const targetPoint = targetBBox.getCenter() - - if (typeof offsetRaw === 'number') { - offset = offsetRaw - } - - if (direction == null) { - let dx = targetBBox.left - sourceBBox.right - let dy = targetBBox.top - sourceBBox.bottom - - if (dx >= 0 && dy >= 0) { - direction = dx >= dy ? 'L' : 'T' - } else if (dx <= 0 && dy >= 0) { - dx = sourceBBox.left - targetBBox.right - if (dx >= 0) { - direction = dx >= dy ? 'R' : 'T' - } else { - direction = 'T' - } - } else if (dx >= 0 && dy <= 0) { - dy = sourceBBox.top - targetBBox.bottom - if (dy >= 0) { - direction = dx >= dy ? 'L' : 'B' - } else { - direction = 'L' - } - } else { - dx = sourceBBox.left - targetBBox.right - dy = sourceBBox.top - targetBBox.bottom - if (dx >= 0 && dy >= 0) { - direction = dx >= dy ? 'R' : 'B' - } else if (dx <= 0 && dy >= 0) { - direction = 'B' - } else if (dx >= 0 && dy <= 0) { - direction = 'R' - } else { - direction = Math.abs(dx) > Math.abs(dy) ? 'R' : 'B' - } - } - } - - if (direction === 'H') { - direction = targetPoint.x - sourcePoint.x >= 0 ? 'L' : 'R' - } else if (direction === 'V') { - direction = targetPoint.y - sourcePoint.y >= 0 ? 'T' : 'B' - } - - if (offsetRaw === 'center') { - if (direction === 'L') { - offset = (targetBBox.left - sourceBBox.right) / 2 - } else if (direction === 'R') { - offset = (sourceBBox.left - targetBBox.right) / 2 - } else if (direction === 'T') { - offset = (targetBBox.top - sourceBBox.bottom) / 2 - } else if (direction === 'B') { - offset = (sourceBBox.top - targetBBox.bottom) / 2 - } - } - - let coord: 'x' | 'y' - let dim: 'width' | 'height' - let factor - const horizontal = direction === 'L' || direction === 'R' - - if (horizontal) { - if (targetPoint.y === sourcePoint.y) { - return [...vertices] - } - - factor = direction === 'L' ? 1 : -1 - coord = 'x' - dim = 'width' - } else { - if (targetPoint.x === sourcePoint.x) { - return [...vertices] - } - - factor = direction === 'T' ? 1 : -1 - coord = 'y' - dim = 'height' - } - - const source = sourcePoint.clone() - const target = targetPoint.clone() - - source[coord] += factor * (sourceBBox[dim] / 2 + offset) - target[coord] -= factor * (targetBBox[dim] / 2 + offset) - - if (horizontal) { - const sourceX = source.x - const targetX = target.x - const sourceDelta = sourceBBox.width / 2 + min - const targetDelta = targetBBox.width / 2 + min - if (targetPoint.x > sourcePoint.x) { - if (targetX <= sourceX) { - source.x = Math.max(targetX, sourcePoint.x + sourceDelta) - target.x = Math.min(sourceX, targetPoint.x - targetDelta) - } - } else if (targetX >= sourceX) { - source.x = Math.min(targetX, sourcePoint.x - sourceDelta) - target.x = Math.max(sourceX, targetPoint.x + targetDelta) - } - } else { - const sourceY = source.y - const targetY = target.y - const sourceDelta = sourceBBox.height / 2 + min - const targetDelta = targetBBox.height / 2 + min - if (targetPoint.y > sourcePoint.y) { - if (targetY <= sourceY) { - source.y = Math.max(targetY, sourcePoint.y + sourceDelta) - target.y = Math.min(sourceY, targetPoint.y - targetDelta) - } - } else if (targetY >= sourceY) { - source.y = Math.min(targetY, sourcePoint.y - sourceDelta) - target.y = Math.max(sourceY, targetPoint.y + targetDelta) - } - } - - return [source.toJSON(), ...vertices, target.toJSON()] -} diff --git a/packages/x6-next/src/registry/router/index.ts b/packages/x6-next/src/registry/router/index.ts deleted file mode 100644 index 4b5956092f1..00000000000 --- a/packages/x6-next/src/registry/router/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Registry, KeyValue } from '@antv/x6-common' -import { EdgeView } from '../../view' -import * as routers from './main' - -export namespace Router { - export type Definition = ( - this: EdgeView, - vertices: Point.PointLike[], - options: T, - edgeView: EdgeView, - ) => Point.PointLike[] - export type CommonDefinition = Definition -} - -export namespace Router { - export type Presets = typeof Router['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: Parameters[1] - } - - export type NativeNames = keyof OptionsMap - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: KeyValue - } -} - -export namespace Router { - export const presets = routers - export const registry = Registry.create({ - type: 'router', - }) - - registry.register(presets, true) -} diff --git a/packages/x6-next/src/registry/router/loop.ts b/packages/x6-next/src/registry/router/loop.ts deleted file mode 100644 index 0c4dc41ca6a..00000000000 --- a/packages/x6-next/src/registry/router/loop.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { Angle, Point, Line } from '@antv/x6-geometry' -import { Router } from './index' - -export interface LoopRouterOptions { - width?: number - height?: number - angle?: 'auto' | number - merge?: boolean | number -} - -function rollup(points: Point.PointLike[], merge?: boolean | number) { - if (merge != null && merge !== false) { - const amount = typeof merge === 'boolean' ? 0 : merge - if (amount > 0) { - const center1 = Point.create(points[1]).move(points[2], amount) - const center2 = Point.create(points[1]).move(points[0], amount) - return [center1.toJSON(), ...points, center2.toJSON()] - } - { - const center = points[1] - return [{ ...center }, ...points, { ...center }] - } - } - return points -} - -export const loop: Router.Definition = function ( - vertices, - options, - edgeView, -) { - const width = options.width || 50 - const height = options.height || 80 - const halfHeight = height / 2 - const angle = options.angle || 'auto' - - const sourceAnchor = edgeView.sourceAnchor - const targetAnchor = edgeView.targetAnchor - const sourceBBox = edgeView.sourceBBox - const targetBBox = edgeView.targetBBox - - if (sourceAnchor.equals(targetAnchor)) { - const getVertices = (angle: number) => { - const rad = Angle.toRad(angle) - const sin = Math.sin(rad) - const cos = Math.cos(rad) - - const center = new Point( - sourceAnchor.x + cos * width, - sourceAnchor.y + sin * width, - ) - const ref = new Point( - center.x - cos * halfHeight, - center.y - sin * halfHeight, - ) - const p1 = ref.clone().rotate(-90, center) - const p2 = ref.clone().rotate(90, center) - - return [p1.toJSON(), center.toJSON(), p2.toJSON()] - } - - const validate = (end: Point.PointLike) => { - const start = sourceAnchor.clone().move(end, -1) - const line = new Line(start, end) - return ( - !sourceBBox.containsPoint(end) && !sourceBBox.intersectsWithLine(line) - ) - } - - const angles = [0, 90, 180, 270, 45, 135, 225, 315] - - if (typeof angle === 'number') { - return rollup(getVertices(angle), options.merge) - } - - const center = sourceBBox.getCenter() - if (center.equals(sourceAnchor)) { - return rollup(getVertices(0), options.merge) - } - - const deg = center.angleBetween( - sourceAnchor, - center.clone().translate(1, 0), - ) - let ret = getVertices(deg) - if (validate(ret[1])) { - return rollup(ret, options.merge) - } - - // return the best vertices - for (let i = 1, l = angles.length; i < l; i += 1) { - ret = getVertices(deg + angles[i]) - if (validate(ret[1])) { - return rollup(ret, options.merge) - } - } - return rollup(ret, options.merge) - } - { - const line = new Line(sourceAnchor, targetAnchor) - let parallel = line.parallel(-width) - let center = parallel.getCenter() - let p1 = parallel.start.clone().move(parallel.end, halfHeight) - let p2 = parallel.end.clone().move(parallel.start, halfHeight) - - const ref = line.parallel(-1) - const line1 = new Line(ref.start, center) - const line2 = new Line(ref.end, center) - - if ( - sourceBBox.containsPoint(center) || - targetBBox.containsPoint(center) || - sourceBBox.intersectsWithLine(line1) || - sourceBBox.intersectsWithLine(line2) || - targetBBox.intersectsWithLine(line1) || - targetBBox.intersectsWithLine(line2) - ) { - parallel = line.parallel(width) - center = parallel.getCenter() - p1 = parallel.start.clone().move(parallel.end, halfHeight) - p2 = parallel.end.clone().move(parallel.start, halfHeight) - } - - if (options.merge) { - const line = new Line(sourceAnchor, targetAnchor) - const normal = new Line(center, line.center).setLength( - Number.MAX_SAFE_INTEGER, - ) - const intersects1 = sourceBBox.intersectsWithLine(normal) - const intersects2 = targetBBox.intersectsWithLine(normal) - const intersects = intersects1 - ? Array.isArray(intersects1) - ? intersects1 - : [intersects1] - : [] - if (intersects2) { - if (Array.isArray(intersects2)) { - intersects.push(...intersects2) - } else { - intersects.push(intersects2) - } - } - const anchor = line.center.closest(intersects) - if (anchor) { - edgeView.sourceAnchor = anchor.clone() - edgeView.targetAnchor = anchor.clone() - } else { - edgeView.sourceAnchor = line.center.clone() - edgeView.targetAnchor = line.center.clone() - } - } - - return rollup([p1.toJSON(), center.toJSON(), p2.toJSON()], options.merge) - } -} diff --git a/packages/x6-next/src/registry/router/main.ts b/packages/x6-next/src/registry/router/main.ts deleted file mode 100644 index 23f81869bb3..00000000000 --- a/packages/x6-next/src/registry/router/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './normal' -export * from './oneside' -export * from './orth' -export * from './metro' -export * from './manhattan/index' -export * from './er' -export * from './loop' diff --git a/packages/x6-next/src/registry/router/manhattan/index.ts b/packages/x6-next/src/registry/router/manhattan/index.ts deleted file mode 100644 index 6641e7b47a4..00000000000 --- a/packages/x6-next/src/registry/router/manhattan/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { FunctionExt } from '@antv/x6-common' -import { Router } from '../index' -import { router } from './router' -import { defaults, ManhattanRouterOptions } from './options' - -export const manhattan: Router.Definition> = - function (vertices, options, edgeView) { - return FunctionExt.call( - router, - this, - vertices, - { ...defaults, ...options }, - edgeView, - ) - } diff --git a/packages/x6-next/src/registry/router/manhattan/obstacle-map.ts b/packages/x6-next/src/registry/router/manhattan/obstacle-map.ts deleted file mode 100644 index 2d8136ba87e..00000000000 --- a/packages/x6-next/src/registry/router/manhattan/obstacle-map.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { ArrayExt, KeyValue } from '@antv/x6-common' -import { Rectangle, Point } from '@antv/x6-geometry' -import { Cell, Edge, Model } from '../../../model' -import { ResolvedOptions } from './options' - -/** - * Helper structure to identify whether a point lies inside an obstacle. - */ -export class ObstacleMap { - options: ResolvedOptions - - /** - * How to divide the paper when creating the elements map - */ - mapGridSize: number - - map: KeyValue - - constructor(options: ResolvedOptions) { - this.options = options - this.mapGridSize = 100 - this.map = {} - } - - /** - * Builds a map of all nodes for quicker obstacle queries i.e. is a point - * contained in any obstacle? - * - * A simplified grid search. - */ - build(model: Model, edge: Edge) { - const options = this.options - // source or target node could be excluded from set of obstacles - const excludedTerminals = options.excludeTerminals.reduce( - (memo, type) => { - const terminal = edge[type] - if (terminal) { - const cell = model.getCell((terminal as Edge.TerminalCellData).cell) - if (cell) { - memo.push(cell) - } - } - - return memo - }, - [], - ) - - let excludedAncestors: string[] = [] - - const source = model.getCell(edge.getSourceCellId()) - if (source) { - excludedAncestors = ArrayExt.union( - excludedAncestors, - source.getAncestors().map((cell) => cell.id), - ) - } - - const target = model.getCell(edge.getTargetCellId()) - if (target) { - excludedAncestors = ArrayExt.union( - excludedAncestors, - target.getAncestors().map((cell) => cell.id), - ) - } - - // The graph is divided into smaller cells, where each holds information - // about which node belong to it. When we query whether a point lies - // inside an obstacle we don't need to go through all obstacles, we check - // only those in a particular cell. - const mapGridSize = this.mapGridSize - - model.getNodes().reduce((map, node) => { - const shape = node.shape - const excludeShapes = options.excludeShapes - const excType = shape ? excludeShapes.includes(shape) : false - const excTerminal = excludedTerminals.some((cell) => cell.id === node.id) - const excAncestor = excludedAncestors.includes(node.id) - const excHidden = options.excludeHiddenNodes && !node.isVisible() - const excluded = excType || excTerminal || excAncestor || excHidden - - if (!excluded) { - const bbox = node.getBBox().moveAndExpand(options.paddingBox) - const origin = bbox.getOrigin().snapToGrid(mapGridSize) - const corner = bbox.getCorner().snapToGrid(mapGridSize) - - for (let x = origin.x; x <= corner.x; x += mapGridSize) { - for (let y = origin.y; y <= corner.y; y += mapGridSize) { - const key = new Point(x, y).toString() - if (map[key] == null) { - map[key] = [] - } - map[key].push(bbox) - } - } - } - return map - }, this.map) - - return this - } - - isAccessible(point: Point) { - const key = point.clone().snapToGrid(this.mapGridSize).toString() - - const rects = this.map[key] - return rects ? rects.every((rect) => !rect.containsPoint(point)) : true - } -} diff --git a/packages/x6-next/src/registry/router/manhattan/options.ts b/packages/x6-next/src/registry/router/manhattan/options.ts deleted file mode 100644 index 6da1f8ae214..00000000000 --- a/packages/x6-next/src/registry/router/manhattan/options.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { Point, Rectangle, Angle } from '@antv/x6-geometry' -import { Edge } from '../../../model' -import { EdgeView } from '../../../view' -import { orth } from '../orth' -import { Router } from '../index' - -export type Direction = 'top' | 'right' | 'bottom' | 'left' -type Callable = T | ((this: ManhattanRouterOptions) => T) - -export interface ResolvedOptions { - /** - * The size of step to find a route (the grid of the manhattan pathfinder). - */ - step: number - - /** - * The number of route finding loops that cause the router to abort returns - * fallback route instead. - */ - maxLoopCount: number - - /** - * The number of decimal places to round floating point coordinates. - */ - precision: number - - /** - * The maximum change of direction. - */ - maxDirectionChange: number - - /** - * Should the router use perpendicular edgeView option? Does not connect - * to the anchor of node but rather a point close-by that is orthogonal. - */ - perpendicular: boolean - - /** - * Should the source and/or target not be considered as obstacles? - */ - excludeTerminals: Edge.TerminalType[] - - /** - * Should certain types of nodes not be considered as obstacles? - */ - excludeShapes: string[] - - /** - * Should certain hidden nodes not be considered as obstacles? - */ - excludeHiddenNodes: boolean - - /** - * Possible starting directions from a node. - */ - startDirections: Direction[] - - /** - * Possible ending directions to a node. - */ - endDirections: Direction[] - - /** - * Specify the directions used above and what they mean - */ - directionMap: { - top: Point.PointLike - right: Point.PointLike - bottom: Point.PointLike - left: Point.PointLike - } - - /** - * Returns the cost of an orthogonal step. - */ - cost: number - - /** - * Returns an array of directions to find next points on the route different - * from start/end directions. - */ - directions: { - cost: number - offsetX: number - offsetY: number - angle?: number - gridOffsetX?: number - gridOffsetY?: number - }[] - - /** - * A penalty received for direction change. - */ - penalties: { - [key: number]: number - } - - padding?: { - top: number - right: number - bottom: number - left: number - } - - /** - * The padding applied on the element bounding boxes. - */ - paddingBox: Rectangle.RectangleLike - - fallbackRouter: Router.Definition - - draggingRouter?: - | (( - this: EdgeView, - dragFrom: Point.PointLike, - dragTo: Point.PointLike, - options: ResolvedOptions, - ) => Point[]) - | null - - fallbackRoute?: ( - this: EdgeView, - from: Point, - to: Point, - options: ResolvedOptions, - ) => Point[] | null - - previousDirectionAngle?: number | null -} - -export type ManhattanRouterOptions = { - [Key in keyof ResolvedOptions]: Callable -} - -export const defaults: ManhattanRouterOptions = { - step: 10, - maxLoopCount: 2000, - precision: 1, - maxDirectionChange: 90, - perpendicular: true, - excludeTerminals: [], - excludeShapes: [], // ['text'] - excludeHiddenNodes: false, - startDirections: ['top', 'right', 'bottom', 'left'], - endDirections: ['top', 'right', 'bottom', 'left'], - directionMap: { - top: { x: 0, y: -1 }, - right: { x: 1, y: 0 }, - bottom: { x: 0, y: 1 }, - left: { x: -1, y: 0 }, - }, - - cost() { - const step = resolve(this.step, this) - return step - }, - - directions() { - const step = resolve(this.step, this) - const cost = resolve(this.cost, this) - - return [ - { cost, offsetX: step, offsetY: 0 }, - { cost, offsetX: -step, offsetY: 0 }, - { cost, offsetX: 0, offsetY: step }, - { cost, offsetX: 0, offsetY: -step }, - ] - }, - - penalties() { - const step = resolve(this.step, this) - return { - 0: 0, - 45: step / 2, - 90: step / 2, - } - }, - - paddingBox() { - const step = resolve(this.step, this) - return { - x: -step, - y: -step, - width: 2 * step, - height: 2 * step, - } - }, - - fallbackRouter: orth, - draggingRouter: null, -} - -export function resolve( - input: Callable, - options: ManhattanRouterOptions, -) { - if (typeof input === 'function') { - return input.call(options) - } - return input -} - -export function resolveOptions(options: ManhattanRouterOptions) { - const result = Object.keys(options).reduce( - (memo, key: keyof ResolvedOptions) => { - const ret = memo as any - if ( - key === 'fallbackRouter' || - key === 'draggingRouter' || - key === 'fallbackRoute' - ) { - ret[key] = options[key] - } else { - ret[key] = resolve(options[key], options) - } - return memo - }, - {} as ResolvedOptions, - ) - - if (result.padding) { - const sides = NumberExt.normalizeSides(result.padding) - options.paddingBox = { - x: -sides.left, - y: -sides.top, - width: sides.left + sides.right, - height: sides.top + sides.bottom, - } - } - - result.directions.forEach((direction) => { - const point1 = new Point(0, 0) - const point2 = new Point(direction.offsetX, direction.offsetY) - direction.angle = Angle.normalize(point1.theta(point2)) - }) - - return result -} diff --git a/packages/x6-next/src/registry/router/manhattan/router.ts b/packages/x6-next/src/registry/router/manhattan/router.ts deleted file mode 100644 index 69b0bcd740b..00000000000 --- a/packages/x6-next/src/registry/router/manhattan/router.ts +++ /dev/null @@ -1,351 +0,0 @@ -import { FunctionExt, KeyValue } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { EdgeView } from '../../../view' -import { Router } from '../index' -import { SortedSet } from './sorted-set' -import { ObstacleMap } from './obstacle-map' -import * as util from './util' -import { - resolveOptions, - ResolvedOptions, - ManhattanRouterOptions, -} from './options' - -/** - * Finds the route between two points (`from`, `to`). - */ -function findRoute( - edgeView: EdgeView, - from: Point | Rectangle, - to: Point | Rectangle, - map: ObstacleMap, - options: ResolvedOptions, -) { - const precision = options.precision - - let sourceEndpoint - let targetEndpoint - - if (Rectangle.isRectangle(from)) { - sourceEndpoint = util.round( - util.getSourceEndpoint(edgeView, options).clone(), - precision, - ) - } else { - sourceEndpoint = util.round(from.clone(), precision) - } - - if (Rectangle.isRectangle(to)) { - targetEndpoint = util.round( - util.getTargetEndpoint(edgeView, options).clone(), - precision, - ) - } else { - targetEndpoint = util.round(to.clone(), precision) - } - - // Get grid for this route. - const grid = util.getGrid(options.step, sourceEndpoint, targetEndpoint) - - // Get pathfinding points. - // ----------------------- - - const startPoint = sourceEndpoint - const endPoint = targetEndpoint - let startPoints - let endPoints - - if (Rectangle.isRectangle(from)) { - startPoints = util.getRectPoints( - startPoint, - from, - options.startDirections, - grid, - options, - ) - } else { - startPoints = [startPoint] - } - - if (Rectangle.isRectangle(to)) { - endPoints = util.getRectPoints( - targetEndpoint, - to, - options.endDirections, - grid, - options, - ) - } else { - endPoints = [endPoint] - } - - // take into account only accessible rect points (those not under obstacles) - startPoints = startPoints.filter((p) => map.isAccessible(p)) - endPoints = endPoints.filter((p) => map.isAccessible(p)) - - // There is an accessible route point on both sides. - if (startPoints.length > 0 && endPoints.length > 0) { - const openSet = new SortedSet() - // Keeps the actual points for given nodes of the open set. - const points: KeyValue = {} - // Keeps the point that is immediate predecessor of given element. - const parents: KeyValue = {} - // Cost from start to a point along best known path. - const costs: KeyValue = {} - - for (let i = 0, n = startPoints.length; i < n; i += 1) { - // startPoint is assumed to be aligned already - const startPoint = startPoints[i] - const key = util.getKey(startPoint) - openSet.add(key, util.getCost(startPoint, endPoints)) - points[key] = startPoint - costs[key] = 0 - } - - const previousRouteDirectionAngle = options.previousDirectionAngle - // undefined for first route - const isPathBeginning = previousRouteDirectionAngle === undefined - - // directions - let direction - let directionChange - const directions = util.getGridOffsets(grid, options) - const numDirections = directions.length - const endPointsKeys = endPoints.reduce((res, endPoint) => { - const key = util.getKey(endPoint) - res.push(key) - return res - }, []) - - // main route finding loop - const sameStartEndPoints = Point.equalPoints(startPoints, endPoints) - let loopsRemaining = options.maxLoopCount - while (!openSet.isEmpty() && loopsRemaining > 0) { - // Get the closest item and mark it CLOSED - const currentKey = openSet.pop()! - const currentPoint = points[currentKey] - const currentParent = parents[currentKey] - const currentCost = costs[currentKey] - - const isStartPoint = currentPoint.equals(startPoint) - const isRouteBeginning = currentParent == null - - let previousDirectionAngle: number | null | undefined - if (!isRouteBeginning) { - previousDirectionAngle = util.getDirectionAngle( - currentParent, - currentPoint, - numDirections, - grid, - options, - ) - } else if (!isPathBeginning) { - // a vertex on the route - previousDirectionAngle = previousRouteDirectionAngle - } else if (!isStartPoint) { - // beginning of route on the path - previousDirectionAngle = util.getDirectionAngle( - startPoint, - currentPoint, - numDirections, - grid, - options, - ) - } else { - previousDirectionAngle = null - } - - // Check if we reached any endpoint - const skipEndCheck = isRouteBeginning && sameStartEndPoints - if (!skipEndCheck && endPointsKeys.indexOf(currentKey) >= 0) { - options.previousDirectionAngle = previousDirectionAngle - return util.reconstructRoute( - parents, - points, - currentPoint, - startPoint, - endPoint, - ) - } - - // Go over all possible directions and find neighbors - for (let i = 0; i < numDirections; i += 1) { - direction = directions[i] - - const directionAngle = direction.angle! - directionChange = util.getDirectionChange( - previousDirectionAngle!, - directionAngle, - ) - - // Don't use the point changed rapidly. - if ( - !(isPathBeginning && isStartPoint) && - directionChange > options.maxDirectionChange - ) { - continue - } - - const neighborPoint = util.align( - currentPoint - .clone() - .translate(direction.gridOffsetX || 0, direction.gridOffsetY || 0), - grid, - precision, - ) - const neighborKey = util.getKey(neighborPoint) - - // Closed points were already evaluated. - if (openSet.isClose(neighborKey) || !map.isAccessible(neighborPoint)) { - continue - } - - // Neighbor is an end point. - if (endPointsKeys.indexOf(neighborKey) >= 0) { - const isEndPoint = neighborPoint.equals(endPoint) - if (!isEndPoint) { - const endDirectionAngle = util.getDirectionAngle( - neighborPoint, - endPoint, - numDirections, - grid, - options, - ) - - const endDirectionChange = util.getDirectionChange( - directionAngle, - endDirectionAngle, - ) - - if (endDirectionChange > options.maxDirectionChange) { - continue - } - } - } - - // The current direction is ok. - // ---------------------------- - - const neighborCost = direction.cost - const neighborPenalty = isStartPoint - ? 0 - : options.penalties[directionChange] - const costFromStart = currentCost + neighborCost + neighborPenalty - - // Neighbor point has not been processed yet or the cost of - // the path from start is lower than previously calculated. - if ( - !openSet.isOpen(neighborKey) || - costFromStart < costs[neighborKey] - ) { - points[neighborKey] = neighborPoint - parents[neighborKey] = currentPoint - costs[neighborKey] = costFromStart - openSet.add( - neighborKey, - costFromStart + util.getCost(neighborPoint, endPoints), - ) - } - } - - loopsRemaining -= 1 - } - } - - if (options.fallbackRoute) { - return FunctionExt.call( - options.fallbackRoute, - this, - startPoint, - endPoint, - options, - ) - } - - return null -} - -export const router: Router.Definition = function ( - vertices, - optionsRaw, - edgeView, -) { - const options = resolveOptions(optionsRaw) - const sourceBBox = util.getSourceBBox(edgeView, options) - const targetBBox = util.getTargetBBox(edgeView, options) - const sourceEndpoint = util.getSourceEndpoint(edgeView, options) - - // pathfinding - const map = new ObstacleMap(options).build( - edgeView.graph.model, - edgeView.cell, - ) - - const oldVertices = vertices.map((p) => Point.create(p)) - const newVertices: Point[] = [] - - // The origin of first route's grid, does not need snapping - let tailPoint = sourceEndpoint - - let from - let to - - for (let i = 0, len = oldVertices.length; i <= len; i += 1) { - let partialRoute: Point[] | null = null - - from = to || sourceBBox - to = oldVertices[i] - - // This is the last iteration - if (to == null) { - to = targetBBox - - // If the target is a point, we should use dragging route - // instead of main routing method if it has been provided. - const edge = edgeView.cell - const isEndingAtPoint = - edge.getSourceCellId() == null || edge.getTargetCellId() == null - - if (isEndingAtPoint && typeof options.draggingRouter === 'function') { - const dragFrom = from === sourceBBox ? sourceEndpoint : from - const dragTo = to.getOrigin() - partialRoute = FunctionExt.call( - options.draggingRouter, - edgeView, - dragFrom, - dragTo, - options, - ) - } - } - - // Find the partial route - if (partialRoute == null) { - partialRoute = findRoute(edgeView, from, to, map, options) - } - - // Cannot found the partial route. - if (partialRoute === null) { - return FunctionExt.call( - options.fallbackRouter, - this, - vertices, - options, - edgeView, - ) - } - - // Remove the first point if the previous partial route has - // the same point as last. - const leadPoint = partialRoute[0] - if (leadPoint && leadPoint.equals(tailPoint)) { - partialRoute.shift() - } - - // Save tailPoint for next iteration - tailPoint = partialRoute[partialRoute.length - 1] || tailPoint - newVertices.push(...partialRoute) - } - - return newVertices -} diff --git a/packages/x6-next/src/registry/router/manhattan/sorted-set.ts b/packages/x6-next/src/registry/router/manhattan/sorted-set.ts deleted file mode 100644 index aac56d0385a..00000000000 --- a/packages/x6-next/src/registry/router/manhattan/sorted-set.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ArrayExt } from '@antv/x6-common' - -const OPEN = 1 -const CLOSE = 2 - -export class SortedSet { - items: string[] - hash: { [key: string]: number } - values: { [key: string]: number } - - constructor() { - this.items = [] - this.hash = {} - this.values = {} - } - - add(item: string, value: number) { - if (this.hash[item]) { - // item removal - this.items.splice(this.items.indexOf(item), 1) - } else { - this.hash[item] = OPEN - } - - this.values[item] = value - - const index = ArrayExt.sortedIndexBy( - this.items, - item, - (key) => this.values[key], - ) - - this.items.splice(index, 0, item) - } - - pop() { - const item = this.items.shift() - if (item) { - this.hash[item] = CLOSE - } - return item - } - - isOpen(item: string) { - return this.hash[item] === OPEN - } - - isClose(item: string) { - return this.hash[item] === CLOSE - } - - isEmpty() { - return this.items.length === 0 - } -} diff --git a/packages/x6-next/src/registry/router/manhattan/util.ts b/packages/x6-next/src/registry/router/manhattan/util.ts deleted file mode 100644 index 01d6c474ae3..00000000000 --- a/packages/x6-next/src/registry/router/manhattan/util.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { Point, Line, Angle, Rectangle, Util } from '@antv/x6-geometry' -import { KeyValue } from '@antv/x6-common' -import { EdgeView } from '../../../view/edge' -import { ResolvedOptions, Direction } from './options' - -export function getSourceBBox(view: EdgeView, options: ResolvedOptions) { - const bbox = view.sourceBBox.clone() - if (options && options.paddingBox) { - return bbox.moveAndExpand(options.paddingBox) - } - - return bbox -} - -export function getTargetBBox(view: EdgeView, options: ResolvedOptions) { - const bbox = view.targetBBox.clone() - if (options && options.paddingBox) { - return bbox.moveAndExpand(options.paddingBox) - } - - return bbox -} - -export function getSourceEndpoint(view: EdgeView, options: ResolvedOptions) { - if (view.sourceAnchor) { - return view.sourceAnchor - } - - const sourceBBox = getSourceBBox(view, options) - return sourceBBox.getCenter() -} - -export function getTargetEndpoint(view: EdgeView, options: ResolvedOptions) { - if (view.targetAnchor) { - return view.targetAnchor - } - - const targetBBox = getTargetBBox(view, options) - return targetBBox.getCenter() -} - -// returns a direction index from start point to end point -// corrects for grid deformation between start and end -export function getDirectionAngle( - start: Point, - end: Point, - directionCount: number, - grid: Grid, - options: ResolvedOptions, -) { - const quadrant = 360 / directionCount - const angleTheta = start.theta(fixAngleEnd(start, end, grid, options)) - const normalizedAngle = Angle.normalize(angleTheta + quadrant / 2) - return quadrant * Math.floor(normalizedAngle / quadrant) -} - -function fixAngleEnd( - start: Point, - end: Point, - grid: Grid, - options: ResolvedOptions, -) { - const step = options.step - - const diffX = end.x - start.x - const diffY = end.y - start.y - - const gridStepsX = diffX / grid.x - const gridStepsY = diffY / grid.y - - const distanceX = gridStepsX * step - const distanceY = gridStepsY * step - - return new Point(start.x + distanceX, start.y + distanceY) -} - -/** - * Returns the change in direction between two direction angles. - */ -export function getDirectionChange(angle1: number, angle2: number) { - const change = Math.abs(angle1 - angle2) - return change > 180 ? 360 - change : change -} - -// fix direction offsets according to current grid -export function getGridOffsets(grid: Grid, options: ResolvedOptions) { - const step = options.step - - options.directions.forEach((direction) => { - direction.gridOffsetX = (direction.offsetX / step) * grid.x - direction.gridOffsetY = (direction.offsetY / step) * grid.y - }) - - return options.directions -} - -export interface Grid { - source: Point - x: number - y: number -} - -// get grid size in x and y dimensions, adapted to source and target positions -export function getGrid(step: number, source: Point, target: Point): Grid { - return { - source: source.clone(), - x: getGridDimension(target.x - source.x, step), - y: getGridDimension(target.y - source.y, step), - } -} - -function getGridDimension(diff: number, step: number) { - // return step if diff = 0 - if (!diff) { - return step - } - - const abs = Math.abs(diff) - const count = Math.round(abs / step) - - // return `abs` if less than one step apart - if (!count) { - return abs - } - - // otherwise, return corrected step - const roundedDiff = count * step - const remainder = abs - roundedDiff - const correction = remainder / count - - return step + correction -} - -function snapGrid(point: Point, grid: Grid) { - const source = grid.source - const x = Util.snapToGrid(point.x - source.x, grid.x) + source.x - const y = Util.snapToGrid(point.y - source.y, grid.y) + source.y - - return new Point(x, y) -} - -export function round(point: Point, precision: number) { - return point.round(precision) -} - -export function align(point: Point, grid: Grid, precision: number) { - return round(snapGrid(point.clone(), grid), precision) -} - -export function getKey(point: Point) { - return point.toString() -} - -export function normalizePoint(point: Point.PointLike) { - return new Point( - point.x === 0 ? 0 : Math.abs(point.x) / point.x, - point.y === 0 ? 0 : Math.abs(point.y) / point.y, - ) -} - -export function getCost(from: Point, anchors: Point[]) { - let min = Infinity - - for (let i = 0, len = anchors.length; i < len; i += 1) { - const dist = from.manhattanDistance(anchors[i]) - if (dist < min) { - min = dist - } - } - - return min -} - -// Find points around the bbox taking given directions into account -// lines are drawn from anchor in given directions, intersections recorded -// if anchor is outside bbox, only those directions that intersect get a rect point -// the anchor itself is returned as rect point (representing some directions) -// (since those directions are unobstructed by the bbox) -export function getRectPoints( - anchor: Point, - bbox: Rectangle, - directionList: Direction[], - grid: Grid, - options: ResolvedOptions, -) { - const precision = options.precision - const directionMap = options.directionMap - const centerVector = anchor.diff(bbox.getCenter()) - - const rectPoints = Object.keys(directionMap).reduce( - (res, key: Direction) => { - if (directionList.includes(key)) { - const direction = directionMap[key] - - // Create a line that is guaranteed to intersect the bbox if bbox - // is in the direction even if anchor lies outside of bbox. - const ending = new Point( - anchor.x + direction.x * (Math.abs(centerVector.x) + bbox.width), - anchor.y + direction.y * (Math.abs(centerVector.y) + bbox.height), - ) - const intersectionLine = new Line(anchor, ending) - - // Get the farther intersection, in case there are two - // (that happens if anchor lies next to bbox) - const intersections = intersectionLine.intersect(bbox) || [] - let farthestIntersectionDistance - let farthestIntersection = null - for (let i = 0; i < intersections.length; i += 1) { - const intersection = intersections[i] - const distance = anchor.squaredDistance(intersection) - if ( - farthestIntersectionDistance == null || - distance > farthestIntersectionDistance - ) { - farthestIntersectionDistance = distance - farthestIntersection = intersection - } - } - - // If an intersection was found in this direction, it is our rectPoint - if (farthestIntersection) { - let target = align(farthestIntersection, grid, precision) - // If the rectPoint lies inside the bbox, offset it by one more step - if (bbox.containsPoint(target)) { - target = align( - target.translate(direction.x * grid.x, direction.y * grid.y), - grid, - precision, - ) - } - - res.push(target) - } - } - - return res - }, - [], - ) - - // if anchor lies outside of bbox, add it to the array of points - if (!bbox.containsPoint(anchor)) { - rectPoints.push(align(anchor, grid, precision)) - } - - return rectPoints -} - -// reconstructs a route by concatenating points with their parents -export function reconstructRoute( - parents: KeyValue, - points: KeyValue, - tailPoint: Point, - from: Point, - to: Point, -) { - const route = [] - - let prevDiff = normalizePoint(to.diff(tailPoint)) - - // tailPoint is assumed to be aligned already - let currentKey = getKey(tailPoint) - let parent = parents[currentKey] - - let point - while (parent) { - // point is assumed to be aligned already - point = points[currentKey] - - const diff = normalizePoint(point.diff(parent)) - if (!diff.equals(prevDiff)) { - route.unshift(point) - prevDiff = diff - } - - // parent is assumed to be aligned already - currentKey = getKey(parent) - parent = parents[currentKey] - } - - // leadPoint is assumed to be aligned already - const leadPoint = points[currentKey] - - const fromDiff = normalizePoint(leadPoint.diff(from)) - if (!fromDiff.equals(prevDiff)) { - route.unshift(leadPoint) - } - - return route -} diff --git a/packages/x6-next/src/registry/router/metro.ts b/packages/x6-next/src/registry/router/metro.ts deleted file mode 100644 index 84f40a0ac85..00000000000 --- a/packages/x6-next/src/registry/router/metro.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { FunctionExt } from '@antv/x6-common' -import { Point, Line, Angle } from '@antv/x6-geometry' -import { ManhattanRouterOptions, resolve } from './manhattan/options' -import { manhattan } from './manhattan/index' -import { Router } from './index' - -export interface MetroRouterOptions extends ManhattanRouterOptions {} - -const defaults: Partial = { - maxDirectionChange: 45, - - // an array of directions to find next points on the route - // different from start/end directions - directions() { - const step = resolve(this.step, this) - const cost = resolve(this.cost, this) - const diagonalCost = Math.ceil(Math.sqrt((step * step) << 1)) // eslint-disable-line no-bitwise - - return [ - { cost, offsetX: step, offsetY: 0 }, - { cost: diagonalCost, offsetX: step, offsetY: step }, - { cost, offsetX: 0, offsetY: step }, - { cost: diagonalCost, offsetX: -step, offsetY: step }, - { cost, offsetX: -step, offsetY: 0 }, - { cost: diagonalCost, offsetX: -step, offsetY: -step }, - { cost, offsetX: 0, offsetY: -step }, - { cost: diagonalCost, offsetX: step, offsetY: -step }, - ] - }, - - // a simple route used in situations when main routing method fails - // (exceed max number of loop iterations, inaccessible) - fallbackRoute(from, to, options) { - // Find a route which breaks by 45 degrees ignoring all obstacles. - - const theta = from.theta(to) - - const route = [] - - let a = { x: to.x, y: from.y } - let b = { x: from.x, y: to.y } - - if (theta % 180 > 90) { - const t = a - a = b - b = t - } - - const p1 = theta % 90 < 45 ? a : b - const l1 = new Line(from, p1) - - const alpha = 90 * Math.ceil(theta / 90) - - const p2 = Point.fromPolar(l1.squaredLength(), Angle.toRad(alpha + 135), p1) - const l2 = new Line(to, p2) - - const intersectionPoint = l1.intersectsWithLine(l2) - const point = intersectionPoint || to - - const directionFrom = intersectionPoint ? point : from - - const quadrant = 360 / options.directions.length - const angleTheta = directionFrom.theta(to) - const normalizedAngle = Angle.normalize(angleTheta + quadrant / 2) - const directionAngle = quadrant * Math.floor(normalizedAngle / quadrant) - - options.previousDirectionAngle = directionAngle - - if (point) route.push(point.round()) - route.push(to) - - return route - }, -} - -export const metro: Router.Definition> = function ( - vertices, - options, - linkView, -) { - return FunctionExt.call( - manhattan, - this, - vertices, - { ...defaults, ...options }, - linkView, - ) -} diff --git a/packages/x6-next/src/registry/router/normal.ts b/packages/x6-next/src/registry/router/normal.ts deleted file mode 100644 index 21b3618ffad..00000000000 --- a/packages/x6-next/src/registry/router/normal.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Router } from './index' - -export interface NormalRouterOptions {} - -export const normal: Router.Definition = function ( - vertices, -) { - return [...vertices] -} diff --git a/packages/x6-next/src/registry/router/oneside.ts b/packages/x6-next/src/registry/router/oneside.ts deleted file mode 100644 index 97b5da5d377..00000000000 --- a/packages/x6-next/src/registry/router/oneside.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { PaddingOptions } from './util' -import { Router } from './index' - -export interface OneSideRouterOptions extends PaddingOptions { - side?: 'left' | 'top' | 'right' | 'bottom' -} - -/** - * Routes the edge always to/from a certain side - */ -export const oneSide: Router.Definition = function ( - vertices, - options, - edgeView, -) { - const side = options.side || 'bottom' - const padding = NumberExt.normalizeSides(options.padding || 40) - const sourceBBox = edgeView.sourceBBox - const targetBBox = edgeView.targetBBox - const sourcePoint = sourceBBox.getCenter() - const targetPoint = targetBBox.getCenter() - - let coord: 'x' | 'y' - let dim: 'width' | 'height' - let factor - - switch (side) { - case 'top': - factor = -1 - coord = 'y' - dim = 'height' - break - case 'left': - factor = -1 - coord = 'x' - dim = 'width' - break - case 'right': - factor = 1 - coord = 'x' - dim = 'width' - break - case 'bottom': - default: - factor = 1 - coord = 'y' - dim = 'height' - break - } - - // Move the points from the center of the element to outside of it. - sourcePoint[coord] += factor * (sourceBBox[dim] / 2 + padding[side]) - targetPoint[coord] += factor * (targetBBox[dim] / 2 + padding[side]) - - // Make edge orthogonal (at least the first and last vertex). - if (factor * (sourcePoint[coord] - targetPoint[coord]) > 0) { - targetPoint[coord] = sourcePoint[coord] - } else { - sourcePoint[coord] = targetPoint[coord] - } - - return [sourcePoint.toJSON(), ...vertices, targetPoint.toJSON()] -} diff --git a/packages/x6-next/src/registry/router/orth.ts b/packages/x6-next/src/registry/router/orth.ts deleted file mode 100644 index ea3c4354fcd..00000000000 --- a/packages/x6-next/src/registry/router/orth.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { ArrayExt } from '@antv/x6-common' -import { Point, Rectangle, Line, Angle } from '@antv/x6-geometry' -import { Router } from './index' -import * as Util from './util' - -export interface OrthRouterOptions extends Util.PaddingOptions {} - -/** - * Returns a route with orthogonal line segments. - */ -export const orth: Router.Definition = function ( - vertices, - options, - edgeView, -) { - let sourceBBox = Util.getSourceBBox(edgeView, options) - let targetBBox = Util.getTargetBBox(edgeView, options) - const sourceAnchor = Util.getSourceAnchor(edgeView, options) - const targetAnchor = Util.getTargetAnchor(edgeView, options) - - // If anchor lies outside of bbox, the bbox expands to include it - sourceBBox = sourceBBox.union(Util.getPointBBox(sourceAnchor)) - targetBBox = targetBBox.union(Util.getPointBBox(targetAnchor)) - - const points = vertices.map((p) => Point.create(p)) - points.unshift(sourceAnchor) - points.push(targetAnchor) - - // bearing of previous route segment - let bearing: Private.Bearings | null = null - const result = [] - - for (let i = 0, len = points.length - 1; i < len; i += 1) { - let route = null - - const from = points[i] - const to = points[i + 1] - const isOrthogonal = Private.getBearing(from, to) != null - - if (i === 0) { - // source - - if (i + 1 === len) { - // source -> target - - // Expand one of the nodes by 1px to detect situations when the two - // nodes are positioned next to each other with no gap in between. - if (sourceBBox.intersectsWithRect(targetBBox.clone().inflate(1))) { - route = Private.insideNode(from, to, sourceBBox, targetBBox) - } else if (!isOrthogonal) { - route = Private.nodeToNode(from, to, sourceBBox, targetBBox) - } - } else { - // source -> vertex - if (sourceBBox.containsPoint(to)) { - route = Private.insideNode( - from, - to, - sourceBBox, - Util.getPointBBox(to).moveAndExpand(Util.getPaddingBox(options)), - ) - } else if (!isOrthogonal) { - route = Private.nodeToVertex(from, to, sourceBBox) - } - } - } else if (i + 1 === len) { - // vertex -> target - - // prevent overlaps with previous line segment - const isOrthogonalLoop = - isOrthogonal && Private.getBearing(to, from) === bearing - - if (targetBBox.containsPoint(from) || isOrthogonalLoop) { - route = Private.insideNode( - from, - to, - Util.getPointBBox(from).moveAndExpand(Util.getPaddingBox(options)), - targetBBox, - bearing, - ) - } else if (!isOrthogonal) { - route = Private.vertexToNode(from, to, targetBBox, bearing) - } - } else if (!isOrthogonal) { - // vertex -> vertex - route = Private.vertexToVertex(from, to, bearing) - } - - // set bearing for next iteration - if (route) { - result.push(...route.points) - bearing = route.direction as Private.Bearings - } else { - // orthogonal route and not looped - bearing = Private.getBearing(from, to) - } - - // push `to` point to identified orthogonal vertices array - if (i + 1 < len) { - result.push(to) - } - } - - return result -} - -namespace Private { - /** - * Bearing to opposite bearing map - */ - const opposites = { - N: 'S', - S: 'N', - E: 'W', - W: 'E', - } - - /** - * Bearing to radians map - */ - const radians = { - N: (-Math.PI / 2) * 3, - S: -Math.PI / 2, - E: 0, - W: Math.PI, - } - - /** - * Returns a point `p` where lines p,p1 and p,p2 are perpendicular - * and p is not contained in the given box - */ - function freeJoin(p1: Point, p2: Point, bbox: Rectangle) { - let p = new Point(p1.x, p2.y) - if (bbox.containsPoint(p)) { - p = new Point(p2.x, p1.y) - } - - // kept for reference - // if (bbox.containsPoint(p)) { - // return null - // } - - return p - } - - /** - * Returns either width or height of a bbox based on the given bearing. - */ - export function getBBoxSize(bbox: Rectangle, bearing: Bearings) { - return bbox[bearing === 'W' || bearing === 'E' ? 'width' : 'height'] - } - - export type Bearings = ReturnType - - export function getBearing(from: Point.PointLike, to: Point.PointLike) { - if (from.x === to.x) { - return from.y > to.y ? 'N' : 'S' - } - - if (from.y === to.y) { - return from.x > to.x ? 'W' : 'E' - } - - return null - } - - export function vertexToVertex(from: Point, to: Point, bearing: Bearings) { - const p1 = new Point(from.x, to.y) - const p2 = new Point(to.x, from.y) - const d1 = getBearing(from, p1) - const d2 = getBearing(from, p2) - const opposite = bearing ? opposites[bearing] : null - - const p = - d1 === bearing || (d1 !== opposite && (d2 === opposite || d2 !== bearing)) - ? p1 - : p2 - - return { points: [p], direction: getBearing(p, to) } - } - - export function nodeToVertex(from: Point, to: Point, fromBBox: Rectangle) { - const p = freeJoin(from, to, fromBBox) - - return { points: [p], direction: getBearing(p, to) } - } - - export function vertexToNode( - from: Point, - to: Point, - toBBox: Rectangle, - bearing: Bearings, - ) { - const points = [new Point(from.x, to.y), new Point(to.x, from.y)] - const freePoints = points.filter((p) => !toBBox.containsPoint(p)) - const freeBearingPoints = freePoints.filter( - (p) => getBearing(p, from) !== bearing, - ) - - let p - - if (freeBearingPoints.length > 0) { - // Try to pick a point which bears the same direction as the previous segment. - - p = freeBearingPoints.filter((p) => getBearing(from, p) === bearing).pop() - p = p || freeBearingPoints[0] - - return { - points: [p], - direction: getBearing(p, to), - } - } - - { - // Here we found only points which are either contained in the element or they would create - // a link segment going in opposite direction from the previous one. - // We take the point inside element and move it outside the element in the direction the - // route is going. Now we can join this point with the current end (using freeJoin). - - p = ArrayExt.difference(points, freePoints)[0] - - const p2 = Point.create(to).move(p, -getBBoxSize(toBBox, bearing) / 2) - const p1 = freeJoin(p2, from, toBBox) - - return { - points: [p1, p2], - direction: getBearing(p2, to), - } - } - } - - export function nodeToNode( - from: Point, - to: Point, - fromBBox: Rectangle, - toBBox: Rectangle, - ) { - let route = nodeToVertex(to, from, toBBox) - const p1 = route.points[0] - - if (fromBBox.containsPoint(p1)) { - route = nodeToVertex(from, to, fromBBox) - const p2 = route.points[0] - - if (toBBox.containsPoint(p2)) { - const fromBorder = Point.create(from).move( - p2, - -getBBoxSize(fromBBox, getBearing(from, p2)) / 2, - ) - const toBorder = Point.create(to).move( - p1, - -getBBoxSize(toBBox, getBearing(to, p1)) / 2, - ) - - const mid = new Line(fromBorder, toBorder).getCenter() - const startRoute = nodeToVertex(from, mid, fromBBox) - const endRoute = vertexToVertex( - mid, - to, - startRoute.direction as Bearings, - ) - - route.points = [startRoute.points[0], endRoute.points[0]] - route.direction = endRoute.direction - } - } - - return route - } - - // Finds route for situations where one node is inside the other. - // Typically the route is directed outside the outer node first and - // then back towards the inner node. - export function insideNode( - from: Point, - to: Point, - fromBBox: Rectangle, - toBBox: Rectangle, - bearing?: Bearings, - ) { - const boundary = fromBBox.union(toBBox).inflate(1) - - // start from the point which is closer to the boundary - const center = boundary.getCenter() - const reversed = center.distance(to) > center.distance(from) - const start = reversed ? to : from - const end = reversed ? from : to - - let p1: Point - let p2: Point - let p3: Point - - if (bearing) { - // Points on circle with radius equals 'W + H` are always outside the rectangle - // with width W and height H if the center of that circle is the center of that rectangle. - p1 = Point.fromPolar( - boundary.width + boundary.height, - radians[bearing], - start, - ) - p1 = boundary.getNearestPointToPoint(p1).move(p1, -1) - } else { - p1 = boundary.getNearestPointToPoint(start).move(start, 1) - } - - p2 = freeJoin(p1, end, boundary) - - let points: Point[] - - if (p1.round().equals(p2.round())) { - p2 = Point.fromPolar( - boundary.width + boundary.height, - Angle.toRad(p1.theta(start)) + Math.PI / 2, - end, - ) - p2 = boundary.getNearestPointToPoint(p2).move(end, 1).round() - p3 = freeJoin(p1, p2, boundary) - points = reversed ? [p2, p3, p1] : [p1, p3, p2] - } else { - points = reversed ? [p2, p1] : [p1, p2] - } - - const direction = reversed ? getBearing(p1, to) : getBearing(p2, to) - - return { - points, - direction, - } - } -} diff --git a/packages/x6-next/src/registry/router/util.ts b/packages/x6-next/src/registry/router/util.ts deleted file mode 100644 index 91d47a70d8e..00000000000 --- a/packages/x6-next/src/registry/router/util.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { NumberExt } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' -import { EdgeView } from '../../view/edge' - -export interface PaddingOptions { - padding?: NumberExt.SideOptions -} - -export function getPointBBox(p: Point) { - return new Rectangle(p.x, p.y, 0, 0) -} - -export function getPaddingBox(options: PaddingOptions = {}) { - const sides = NumberExt.normalizeSides(options.padding || 20) - - return { - x: -sides.left, - y: -sides.top, - width: sides.left + sides.right, - height: sides.top + sides.bottom, - } -} - -export function getSourceBBox(view: EdgeView, options: PaddingOptions = {}) { - return view.sourceBBox.clone().moveAndExpand(getPaddingBox(options)) -} - -export function getTargetBBox(view: EdgeView, options: PaddingOptions = {}) { - return view.targetBBox.clone().moveAndExpand(getPaddingBox(options)) -} - -export function getSourceAnchor(view: EdgeView, options: PaddingOptions = {}) { - if (view.sourceAnchor) { - return view.sourceAnchor - } - const bbox = getSourceBBox(view, options) - return bbox.getCenter() -} - -export function getTargetAnchor(view: EdgeView, options: PaddingOptions = {}) { - if (view.targetAnchor) { - return view.targetAnchor - } - - const bbox = getTargetBBox(view, options) - return bbox.getCenter() -} diff --git a/packages/x6-next/src/registry/tool/anchor.ts b/packages/x6-next/src/registry/tool/anchor.ts deleted file mode 100644 index 716def8e564..00000000000 --- a/packages/x6-next/src/registry/tool/anchor.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { Dom, FunctionExt } from '@antv/x6-common' -import { Point } from '@antv/x6-geometry' -import { Attr } from '../attr' -import { Edge } from '../../model/edge' -import { Node } from '../../model/node' -import { EdgeView } from '../../view/edge' -import { CellView } from '../../view/cell' -import { ToolsView } from '../../view/tool' -import * as Util from './util' - -class Anchor extends ToolsView.ToolItem { - protected get type() { - return this.options.type! - } - - protected onRender() { - Dom.addClass( - this.container, - this.prefixClassName(`edge-tool-${this.type}-anchor`), - ) - - this.toggleArea(false) - this.update() - } - - update() { - const type = this.type - const edgeView = this.cellView - const terminalView = edgeView.getTerminalView(type) - if (terminalView) { - this.updateAnchor() - this.updateArea() - this.container.style.display = '' - } else { - this.container.style.display = 'none' - } - return this - } - - protected updateAnchor() { - const childNodes = this.childNodes - if (!childNodes) { - return - } - - const anchorNode = childNodes.anchor - if (!anchorNode) { - return - } - - const type = this.type - const edgeView = this.cellView - const options = this.options - const position = edgeView.getTerminalAnchor(type) - const customAnchor = edgeView.cell.prop([type, 'anchor']) - anchorNode.setAttribute( - 'transform', - `translate(${position.x}, ${position.y})`, - ) - - const anchorAttrs = customAnchor - ? options.customAnchorAttrs - : options.defaultAnchorAttrs - - if (anchorAttrs) { - Object.keys(anchorAttrs).forEach((attrName) => { - anchorNode.setAttribute(attrName, anchorAttrs[attrName] as string) - }) - } - } - - protected updateArea() { - const childNodes = this.childNodes - if (!childNodes) { - return - } - - const areaNode = childNodes.area - if (!areaNode) { - return - } - - const type = this.type - const edgeView = this.cellView - const terminalView = edgeView.getTerminalView(type) - if (terminalView) { - const terminalCell = terminalView.cell as Node - const magnet = edgeView.getTerminalMagnet(type) - let padding = this.options.areaPadding || 0 - if (!Number.isFinite(padding)) { - padding = 0 - } - - let bbox - let angle - let center - if (terminalView.isEdgeElement(magnet)) { - bbox = terminalView.getBBox() - angle = 0 - center = bbox.getCenter() - } else { - bbox = terminalView.getUnrotatedBBoxOfElement(magnet as SVGElement) - angle = terminalCell.getAngle() - center = bbox.getCenter() - if (angle) { - center.rotate(-angle, terminalCell.getBBox().getCenter()) - } - } - - bbox.inflate(padding) - - Dom.attr(areaNode, { - x: -bbox.width / 2, - y: -bbox.height / 2, - width: bbox.width, - height: bbox.height, - transform: `translate(${center.x}, ${center.y}) rotate(${angle})`, - }) - } - } - - protected toggleArea(visible?: boolean) { - if (this.childNodes) { - const elem = this.childNodes.area as HTMLElement - if (elem) { - elem.style.display = visible ? '' : 'none' - } - } - } - - protected onMouseDown(evt: Dom.MouseDownEvent) { - if (this.guard(evt)) { - return - } - evt.stopPropagation() - evt.preventDefault() - this.graph.view.undelegateEvents() - if (this.options.documentEvents) { - this.delegateDocumentEvents(this.options.documentEvents) - } - this.focus() - this.toggleArea(this.options.restrictArea) - this.cell.startBatch('move-anchor', { - ui: true, - toolId: this.cid, - }) - } - - protected resetAnchor(anchor?: Edge.TerminalCellData['anchor']) { - const type = this.type - const cell = this.cell - if (anchor) { - cell.prop([type, 'anchor'], anchor, { - rewrite: true, - ui: true, - toolId: this.cid, - }) - } else { - cell.removeProp([type, 'anchor'], { - ui: true, - toolId: this.cid, - }) - } - } - - protected onMouseMove(evt: Dom.MouseMoveEvent) { - const terminalType = this.type - const edgeView = this.cellView - const terminalView = edgeView.getTerminalView(terminalType) - if (terminalView == null) { - return - } - - const e = this.normalizeEvent(evt) - const terminalCell = terminalView.cell - const terminalMagnet = edgeView.getTerminalMagnet(terminalType)! - let coords = this.graph.coord.clientToLocalPoint(e.clientX, e.clientY) - - const snapFn = this.options.snap - if (typeof snapFn === 'function') { - const tmp = FunctionExt.call( - snapFn, - edgeView, - coords, - terminalView, - terminalMagnet, - terminalType, - edgeView, - this, - ) - coords = Point.create(tmp) - } - - if (this.options.restrictArea) { - if (terminalView.isEdgeElement(terminalMagnet)) { - const pointAtConnection = (terminalView as EdgeView).getClosestPoint( - coords, - ) - if (pointAtConnection) { - coords = pointAtConnection - } - } else { - const bbox = terminalView.getUnrotatedBBoxOfElement( - terminalMagnet as SVGElement, - ) - const angle = (terminalCell as Node).getAngle() - const origin = terminalCell.getBBox().getCenter() - const rotatedCoords = coords.clone().rotate(angle, origin) - if (!bbox.containsPoint(rotatedCoords)) { - coords = bbox - .getNearestPointToPoint(rotatedCoords) - .rotate(-angle, origin) - } - } - } - - let anchor - const anchorFn = this.options.anchor - if (typeof anchorFn === 'function') { - anchor = FunctionExt.call( - anchorFn, - edgeView, - coords, - terminalView, - terminalMagnet, - terminalType, - edgeView, - this, - ) as Edge.TerminalCellData['anchor'] - } - - this.resetAnchor(anchor) - this.update() - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected onMouseUp(evt: Dom.MouseUpEvent) { - this.graph.view.delegateEvents() - this.undelegateDocumentEvents() - this.blur() - this.toggleArea(false) - const edgeView = this.cellView - if (this.options.removeRedundancies) { - edgeView.removeRedundantLinearVertices({ ui: true, toolId: this.cid }) - } - this.cell.stopBatch('move-anchor', { ui: true, toolId: this.cid }) - } - - protected onDblClick() { - const anchor = this.options.resetAnchor - if (anchor) { - this.resetAnchor(anchor === true ? undefined : anchor) - } - this.update() - } -} - -namespace Anchor { - export interface Options extends ToolsView.ToolItem.Options { - type?: Edge.TerminalType - snapRadius?: number - areaPadding?: number - restrictArea?: boolean - resetAnchor?: boolean | Edge.TerminalCellData['anchor'] - removeRedundancies?: boolean - defaultAnchorAttrs?: Attr.SimpleAttrs - customAnchorAttrs?: Attr.SimpleAttrs - snap?: ( - this: EdgeView, - pos: Point, - terminalView: CellView, - terminalMagnet: Element | null, - terminalType: Edge.TerminalType, - edgeView: EdgeView, - toolView: Anchor, - ) => Point.PointLike - anchor?: ( - this: EdgeView, - pos: Point, - terminalView: CellView, - terminalMagnet: Element | null, - terminalType: Edge.TerminalType, - edgeView: EdgeView, - toolView: Anchor, - ) => Edge.TerminalCellData['anchor'] - } -} - -namespace Anchor { - Anchor.config({ - tagName: 'g', - markup: [ - { - tagName: 'circle', - selector: 'anchor', - attrs: { - cursor: 'pointer', - }, - }, - { - tagName: 'rect', - selector: 'area', - attrs: { - 'pointer-events': 'none', - fill: 'none', - stroke: '#33334F', - 'stroke-dasharray': '2,4', - rx: 5, - ry: 5, - }, - }, - ], - events: { - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - dblclick: 'onDblClick', - }, - documentEvents: { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - }, - customAnchorAttrs: { - 'stroke-width': 4, - stroke: '#33334F', - fill: '#FFFFFF', - r: 5, - }, - defaultAnchorAttrs: { - 'stroke-width': 2, - stroke: '#FFFFFF', - fill: '#33334F', - r: 6, - }, - areaPadding: 6, - snapRadius: 10, - resetAnchor: true, - restrictArea: true, - removeRedundancies: true, - anchor: Util.getAnchor, - snap(pos, terminalView, terminalMagnet, terminalType, edgeView, toolView) { - const snapRadius = toolView.options.snapRadius || 0 - const isSource = terminalType === 'source' - const refIndex = isSource ? 0 : -1 - const ref = - this.cell.getVertexAt(refIndex) || - this.getTerminalAnchor(isSource ? 'target' : 'source') - if (ref) { - if (Math.abs(ref.x - pos.x) < snapRadius) pos.x = ref.x - if (Math.abs(ref.y - pos.y) < snapRadius) pos.y = ref.y - } - return pos - }, - }) -} - -export const SourceAnchor = Anchor.define({ - name: 'source-anchor', - type: 'source', -}) - -export const TargetAnchor = Anchor.define({ - name: 'target-anchor', - type: 'target', -}) diff --git a/packages/x6-next/src/registry/tool/arrowhead.ts b/packages/x6-next/src/registry/tool/arrowhead.ts deleted file mode 100644 index dc93f413b43..00000000000 --- a/packages/x6-next/src/registry/tool/arrowhead.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { Dom } from '@antv/x6-common' -import { Point } from '@antv/x6-geometry' -import { Attr } from '../attr' -import { Edge } from '../../model/edge' -import { EdgeView } from '../../view/edge' -import { ToolsView } from '../../view/tool' - -class Arrowhead extends ToolsView.ToolItem { - protected get type() { - return this.options.type! - } - - protected get ratio() { - return this.options.ratio! - } - - protected init() { - if (this.options.attrs) { - const { class: className, ...attrs } = this.options.attrs - this.setAttrs(attrs, this.container) - if (className) { - Dom.addClass(this.container, className as string) - } - } - } - - protected onRender() { - Dom.addClass( - this.container, - this.prefixClassName(`edge-tool-${this.type}-arrowhead`), - ) - this.update() - } - - update() { - const ratio = this.ratio - const edgeView = this.cellView as EdgeView - const tangent = edgeView.getTangentAtRatio(ratio) - const position = tangent ? tangent.start : edgeView.getPointAtRatio(ratio) - const angle = - (tangent && tangent.vector().vectorAngle(new Point(1, 0))) || 0 - - if (!position) { - return this - } - - const matrix = Dom.createSVGMatrix() - .translate(position.x, position.y) - .rotate(angle) - - Dom.transform(this.container as SVGElement, matrix, { absolute: true }) - - return this - } - - protected onMouseDown(evt: Dom.MouseDownEvent) { - if (this.guard(evt)) { - return - } - - evt.stopPropagation() - evt.preventDefault() - - const edgeView = this.cellView as EdgeView - - if (edgeView.can('arrowheadMovable')) { - edgeView.cell.startBatch('move-arrowhead', { - ui: true, - toolId: this.cid, - }) - - const coords = this.graph.snapToGrid(evt.clientX, evt.clientY) - const data = edgeView.prepareArrowheadDragging(this.type, { - x: coords.x, - y: coords.y, - options: { - toolId: this.cid, - }, - }) - this.cellView.setEventData(evt, data) - this.delegateDocumentEvents(this.options.documentEvents!, evt.data) - edgeView.graph.view.undelegateEvents() - - this.container.style.pointerEvents = 'none' - } - - this.focus() - } - - protected onMouseMove(evt: Dom.MouseMoveEvent) { - const e = this.normalizeEvent(evt) - const coords = this.graph.snapToGrid(e.clientX, e.clientY) - this.cellView.onMouseMove(e, coords.x, coords.y) - this.update() - } - - protected onMouseUp(evt: Dom.MouseUpEvent) { - this.undelegateDocumentEvents() - const e = this.normalizeEvent(evt) - const edgeView = this.cellView - const coords = this.graph.snapToGrid(e.clientX, e.clientY) - edgeView.onMouseUp(e, coords.x, coords.y) - this.graph.view.delegateEvents() - this.blur() - this.container.style.pointerEvents = '' - edgeView.cell.stopBatch('move-arrowhead', { - ui: true, - toolId: this.cid, - }) - } -} - -namespace Arrowhead { - export interface Options extends ToolsView.ToolItem.Options { - attrs?: Attr.SimpleAttrs - type?: Edge.TerminalType - ratio?: number - } -} - -namespace Arrowhead { - Arrowhead.config({ - tagName: 'path', - isSVGElement: true, - events: { - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - }, - documentEvents: { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - }, - }) -} - -export const SourceArrowhead = Arrowhead.define({ - name: 'source-arrowhead', - type: 'source', - ratio: 0, - attrs: { - d: 'M 10 -8 -10 0 10 8 Z', - fill: '#333', - stroke: '#fff', - 'stroke-width': 2, - cursor: 'move', - }, -}) - -export const TargetArrowhead = Arrowhead.define({ - name: 'target-arrowhead', - type: 'target', - ratio: 1, - attrs: { - d: 'M -10 -8 10 0 -10 8 Z', - fill: '#333', - stroke: '#fff', - 'stroke-width': 2, - cursor: 'move', - }, -}) diff --git a/packages/x6-next/src/registry/tool/boundary.ts b/packages/x6-next/src/registry/tool/boundary.ts deleted file mode 100644 index b0e3d19ab65..00000000000 --- a/packages/x6-next/src/registry/tool/boundary.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { NumberExt, Dom } from '@antv/x6-common' -import { Attr } from '../attr' -import { NodeView } from '../../view/node' -import { EdgeView } from '../../view/edge' -import { ToolsView } from '../../view/tool' -import * as Util from './util' - -export class Boundary extends ToolsView.ToolItem< - EdgeView | NodeView, - Boundary.Options -> { - protected onRender() { - Dom.addClass(this.container, this.prefixClassName('cell-tool-boundary')) - - if (this.options.attrs) { - const { class: className, ...attrs } = this.options.attrs - Dom.attr(this.container, Dom.kebablizeAttrs(attrs)) - if (className) { - Dom.addClass(this.container, className as string) - } - } - this.update() - } - - update() { - const view = this.cellView - const options = this.options - const { useCellGeometry, rotate } = options - const padding = NumberExt.normalizeSides(options.padding) - let bbox = Util.getViewBBox(view, useCellGeometry).moveAndExpand({ - x: -padding.left, - y: -padding.top, - width: padding.left + padding.right, - height: padding.top + padding.bottom, - }) - - const cell = view.cell - if (cell.isNode()) { - const angle = cell.getAngle() - if (angle) { - if (rotate) { - const origin = cell.getBBox().getCenter() - Dom.rotate(this.container, angle, origin.x, origin.y, { - absolute: true, - }) - } else { - bbox = bbox.bbox(angle) - } - } - } - - Dom.attr(this.container, bbox.toJSON()) - - return this - } -} - -export namespace Boundary { - export interface Options extends ToolsView.ToolItem.Options { - padding?: NumberExt.SideOptions - rotate?: boolean - useCellGeometry?: boolean - attrs?: Attr.SimpleAttrs - } -} - -export namespace Boundary { - Boundary.config({ - name: 'boundary', - tagName: 'rect', - padding: 10, - attrs: { - fill: 'none', - stroke: '#333', - 'stroke-width': 0.5, - 'stroke-dasharray': '5, 5', - 'pointer-events': 'none', - }, - }) -} diff --git a/packages/x6-next/src/registry/tool/button.ts b/packages/x6-next/src/registry/tool/button.ts deleted file mode 100644 index 5c8fbc14fc5..00000000000 --- a/packages/x6-next/src/registry/tool/button.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Dom, NumberExt, FunctionExt } from '@antv/x6-common' -import { CellView } from '../../view/cell' -import { NodeView } from '../../view/node' -import { EdgeView } from '../../view/edge' -import { ToolsView } from '../../view/tool' -import * as Util from './util' -import { Cell } from '../../model' - -export class Button extends ToolsView.ToolItem< - EdgeView | NodeView, - Button.Options -> { - protected onRender() { - Dom.addClass(this.container, this.prefixClassName('cell-tool-button')) - this.update() - } - - update() { - this.updatePosition() - return this - } - - protected updatePosition() { - const view = this.cellView - const matrix = view.cell.isEdge() - ? this.getEdgeMatrix() - : this.getNodeMatrix() - Dom.transform(this.container as SVGElement, matrix, { absolute: true }) - } - - protected getNodeMatrix() { - const view = this.cellView as NodeView - const options = this.options - - let { x = 0, y = 0 } = options - const { offset, useCellGeometry, rotate } = options - - let bbox = Util.getViewBBox(view, useCellGeometry) - const angle = view.cell.getAngle() - if (!rotate) { - bbox = bbox.bbox(angle) - } - - let offsetX = 0 - let offsetY = 0 - if (typeof offset === 'number') { - offsetX = offset - offsetY = offset - } else if (typeof offset === 'object') { - offsetX = offset.x - offsetY = offset.y - } - - x = NumberExt.normalizePercentage(x, bbox.width) - y = NumberExt.normalizePercentage(y, bbox.height) - - let matrix = Dom.createSVGMatrix().translate( - bbox.x + bbox.width / 2, - bbox.y + bbox.height / 2, - ) - - if (rotate) { - matrix = matrix.rotate(angle) - } - - matrix = matrix.translate( - x + offsetX - bbox.width / 2, - y + offsetY - bbox.height / 2, - ) - - return matrix - } - - protected getEdgeMatrix() { - const view = this.cellView as EdgeView - const options = this.options - const { offset = 0, distance = 0, rotate } = options - - let tangent - let position - let angle - if (NumberExt.isPercentage(distance)) { - tangent = view.getTangentAtRatio(parseFloat(distance) / 100) - } else { - tangent = view.getTangentAtLength(distance) - } - - if (tangent) { - position = tangent.start - angle = tangent.vector().vectorAngle(new Point(1, 0)) || 0 - } else { - position = view.getConnection()!.start! - angle = 0 - } - - let matrix = Dom.createSVGMatrix() - .translate(position.x, position.y) - .rotate(angle) - - if (typeof offset === 'object') { - matrix = matrix.translate(offset.x || 0, offset.y || 0) - } else { - matrix = matrix.translate(0, offset) - } - - if (!rotate) { - matrix = matrix.rotate(-angle) - } - - return matrix - } - - protected onMouseDown(e: Dom.MouseDownEvent) { - if (this.guard(e)) { - return - } - - e.stopPropagation() - e.preventDefault() - - const onClick = this.options.onClick - if (typeof onClick === 'function') { - FunctionExt.call(onClick, this.cellView, { - e, - view: this.cellView, - cell: this.cellView.cell, - btn: this, - }) - } - } -} - -export namespace Button { - export interface Options extends ToolsView.ToolItem.Options { - x?: number - y?: number - distance?: number - offset?: number | Point.PointLike - rotate?: boolean - useCellGeometry?: boolean - onClick?: ( - this: CellView, - args: { - e: Dom.MouseDownEvent - cell: Cell - view: CellView - btn: Button - }, - ) => any - } -} - -export namespace Button { - Button.config({ - name: 'button', - events: { - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - }, - }) -} - -export namespace Button { - export const Remove = Button.define({ - name: 'button-remove', - markup: [ - { - tagName: 'circle', - selector: 'button', - attrs: { - r: 7, - fill: '#FF1D00', - cursor: 'pointer', - }, - }, - { - tagName: 'path', - selector: 'icon', - attrs: { - d: 'M -3 -3 3 3 M -3 3 3 -3', - fill: 'none', - stroke: '#FFFFFF', - 'stroke-width': 2, - 'pointer-events': 'none', - }, - }, - ], - distance: 60, - offset: 0, - onClick({ view, btn }) { - btn.parent.remove() - view.cell.remove({ ui: true, toolId: btn.cid }) - }, - }) -} diff --git a/packages/x6-next/src/registry/tool/editor.less b/packages/x6-next/src/registry/tool/editor.less deleted file mode 100644 index 33355e4947d..00000000000 --- a/packages/x6-next/src/registry/tool/editor.less +++ /dev/null @@ -1,21 +0,0 @@ -@import '../../style/index'; - -.@{x6-prefix}-cell-tool-editor { - position: relative; - display: inline-block; - min-height: 1em; - margin: 0; - padding: 0; - line-height: 1; - white-space: normal; - text-align: center; - vertical-align: top; - overflow-wrap: normal; - outline: none; - transform-origin: 0 0; - -webkit-user-drag: none; -} -.@{x6-prefix}-edge-tool-editor { - border: 1px solid #275fc5; - border-radius: 2px; -} diff --git a/packages/x6-next/src/registry/tool/editor.ts b/packages/x6-next/src/registry/tool/editor.ts deleted file mode 100644 index 6a5941377b7..00000000000 --- a/packages/x6-next/src/registry/tool/editor.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Dom, FunctionExt } from '@antv/x6-common' -import { ToolsView } from '../../view/tool' -import { Cell, Edge } from '../../model' -import { CellView, NodeView, EdgeView } from '../../view' -import { Util } from '../../util' - -export class CellEditor extends ToolsView.ToolItem< - NodeView | EdgeView, - CellEditor.CellEditorOptions & { event: Dom.EventObject } -> { - private editor: HTMLDivElement - private labelIndex = -1 - private distance = 0.5 - - render() { - this.createElement() - this.update() - this.autoFocus() - this.delegateDocumentEvents(this.options.documentEvents!) - - return this - } - - createElement() { - const { cell } = this - const classNames = [ - this.prefixClassName(`${cell.isEdge() ? 'edge' : 'node'}-tool-editor`), - this.prefixClassName('cell-tool-editor'), - ] - this.editor = ToolsView.createElement('div', false) as HTMLDivElement - this.addClass(classNames, this.editor) - this.editor.contentEditable = 'true' - this.container.appendChild(this.editor) - } - - update() { - const { graph, cell, editor } = this - const style = editor.style - - // set tool position - let pos = new Point() - let minWidth = 20 - if (cell.isNode()) { - pos = cell.getBBox().center - minWidth = cell.size().width - 4 - } else if (cell.isEdge()) { - const e = this.options.event - const target = e.target - const parent = target.parentElement - const isEdgeLabel = - parent && Dom.hasClass(parent, this.prefixClassName('edge-label')) - if (isEdgeLabel) { - const index = parent.getAttribute('data-index') || '0' - this.labelIndex = parseInt(index, 10) - const matrix = parent.getAttribute('transform') - const { translation } = Dom.parseTransformString(matrix) - pos = new Point(translation.tx, translation.ty) - minWidth = Util.getBBox(target).width - } else { - if (!this.options.labelAddable) { - return this - } - pos = graph.clientToLocal(Point.create(e.clientX, e.clientY)) - const view = this.cellView as EdgeView - const d = view.path.closestPointLength(pos) - this.distance = d - } - } - pos = graph.localToGraph(pos) - style.left = `${pos.x}px` - style.top = `${pos.y}px` - style.minWidth = `${minWidth}px` - - // set tool transform - const scale = graph.scale() - style.transform = `scale(${scale.sx}, ${scale.sy}) translate(-50%, -50%)` - - // set font style - const attrs = this.options.attrs - style.fontSize = `${attrs.fontSize}px` - style.fontFamily = attrs.fontFamily - style.color = attrs.color - style.backgroundColor = attrs.backgroundColor - - // set init value - const getText = this.options.getText - let text - if (typeof getText === 'function') { - text = FunctionExt.call(getText, this.cellView, { - cell: this.cell, - index: this.labelIndex, - }) - } - editor.innerText = text || '' - - return this - } - - onDocumentMouseDown(e: Dom.MouseDownEvent) { - if (e.target !== this.editor) { - const cell = this.cell - const value = this.editor.innerText.replace(/\n$/, '') || '' - // set value - const setText = this.options.setText - if (typeof setText === 'function') { - FunctionExt.call(setText, this.cellView, { - cell: this.cell, - value, - index: this.labelIndex, - distance: this.distance, - }) - } - // remove tool - cell.removeTool(cell.isEdge() ? 'edge-editor' : 'node-editor') - this.undelegateDocumentEvents() - } - } - - onDblClick(e: Dom.DoubleClickEvent) { - e.stopPropagation() - } - - onMouseDown(e: Dom.MouseDownEvent) { - e.stopPropagation() - } - - autoFocus() { - setTimeout(() => { - this.editor.focus() - this.selectText() - }) - } - - selectText() { - if (window.getSelection) { - const range = document.createRange() - const selection = window.getSelection()! - range.selectNodeContents(this.editor) - selection.removeAllRanges() - selection.addRange(range) - } - } -} - -export namespace CellEditor { - export interface CellEditorOptions extends ToolsView.ToolItem.Options { - attrs: { - fontSize: number - fontFamily: string - color: string - backgroundColor: string - } - labelAddable?: boolean - getText: ( - this: CellView, - args: { - cell: Cell - index?: number - }, - ) => string - setText: ( - this: CellView, - args: { - cell: Cell - value: string - index?: number - distance?: number - }, - ) => void - } -} - -export namespace CellEditor { - CellEditor.config({ - tagName: 'div', - isSVGElement: false, - events: { - dblclick: 'onDblClick', - mousedown: 'onMouseDown', - }, - documentEvents: { - mousedown: 'onDocumentMouseDown', - }, - }) -} - -export namespace CellEditor { - export const NodeEditor = CellEditor.define({ - attrs: { - fontSize: 14, - fontFamily: 'Arial, helvetica, sans-serif', - color: '#000', - backgroundColor: '#fff', - }, - getText({ cell }) { - return cell.attr('text/text') - }, - setText({ cell, value }) { - cell.attr('text/text', value) - }, - }) - - export const EdgeEditor = CellEditor.define({ - attrs: { - fontSize: 14, - fontFamily: 'Arial, helvetica, sans-serif', - color: '#000', - backgroundColor: '#fff', - }, - labelAddable: true, - getText({ cell, index }) { - if (index === -1) { - return '' - } - return cell.prop(`labels/${index}/attrs/label/text`) - }, - setText({ cell, value, index, distance }) { - const edge = cell as Edge - if (index === -1) { - edge.appendLabel({ - position: { - distance: distance!, - }, - attrs: { - label: { - text: value, - }, - }, - }) - } else { - if (value) { - edge.prop(`labels/${index}/attrs/label/text`, value) - } else if (typeof index === 'number') { - edge.removeLabelAt(index) - } - } - }, - }) -} diff --git a/packages/x6-next/src/registry/tool/index.ts b/packages/x6-next/src/registry/tool/index.ts deleted file mode 100644 index caf75cde049..00000000000 --- a/packages/x6-next/src/registry/tool/index.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { Registry, KeyValue } from '@antv/x6-common' -import { ToolsView } from '../../view/tool' -import { Button } from './button' -import { Boundary } from './boundary' -import { Vertices } from './vertices' -import { Segments } from './segments' -import { SourceAnchor, TargetAnchor } from './anchor' -import { SourceArrowhead, TargetArrowhead } from './arrowhead' -import { CellEditor } from './editor' - -export namespace NodeTool { - export const presets = { - boundary: Boundary, - button: Button, - 'button-remove': Button.Remove, - 'node-editor': CellEditor.NodeEditor, - } - - export type Definition = ToolsView.ToolItem.Definition - - export const registry = Registry.create< - Definition, - Presets, - ToolsView.ToolItem.Options & { inherit?: string } & KeyValue - >({ - type: 'node tool', - process(name, options) { - if (typeof options === 'function') { - return options - } - - let parent = ToolsView.ToolItem - const { inherit, ...others } = options - if (inherit) { - const base = this.get(inherit) - if (base == null) { - this.onNotFound(inherit, 'inherited') - } else { - parent = base - } - } - - if (others.name == null) { - others.name = name - } - - return parent.define.call(parent, others) - }, - }) - - registry.register(presets, true) -} - -export namespace NodeTool { - export type Presets = typeof NodeTool['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: ConstructorParameters[0] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: ToolsView.ToolItem.Options - } -} - -export namespace EdgeTool { - export const presets = { - boundary: Boundary, - vertices: Vertices, - segments: Segments, - button: Button, - 'button-remove': Button.Remove, - 'source-anchor': SourceAnchor, - 'target-anchor': TargetAnchor, - 'source-arrowhead': SourceArrowhead, - 'target-arrowhead': TargetArrowhead, - 'edge-editor': CellEditor.EdgeEditor, - } - - export type Definition = NodeTool.Definition - - export const registry = Registry.create< - Definition, - Presets, - ToolsView.ToolItem.Options & { inherit?: string } & KeyValue - >({ - type: 'edge tool', - process(name, options) { - if (typeof options === 'function') { - return options - } - - let parent = ToolsView.ToolItem - const { inherit, ...others } = options - if (inherit) { - const base = this.get(inherit) - if (base == null) { - this.onNotFound(inherit, 'inherited') - } else { - parent = base - } - } - - if (others.name == null) { - others.name = name - } - - return parent.define.call(parent, others) - }, - }) - - registry.register(presets, true) -} - -export namespace EdgeTool { - export type Presets = typeof EdgeTool['presets'] - - export type OptionsMap = { - readonly [K in keyof Presets]-?: ConstructorParameters[0] - } - - export type NativeNames = keyof Presets - - export interface NativeItem { - name: T - args?: OptionsMap[T] - } - - export interface ManaualItem { - name: Exclude - args?: ToolsView.ToolItem.Options - } -} diff --git a/packages/x6-next/src/registry/tool/segments.ts b/packages/x6-next/src/registry/tool/segments.ts deleted file mode 100644 index 89668ddbcec..00000000000 --- a/packages/x6-next/src/registry/tool/segments.ts +++ /dev/null @@ -1,516 +0,0 @@ -import { Dom, ObjectExt, FunctionExt } from '@antv/x6-common' -import { Point, Line } from '@antv/x6-geometry' -import { View } from '../../view/view' -import { ToolsView } from '../../view/tool' -import * as Util from './util' -import { Attr } from '../attr' -import { CellView } from '../../view/cell' -import { EdgeView } from '../../view/edge' -import { Edge } from '../../model/edge' -import { Graph } from '../../graph' - -export class Segments extends ToolsView.ToolItem { - protected handles: Segments.Handle[] = [] - - protected get vertices() { - return this.cellView.cell.getVertices() - } - - update() { - this.render() - return this - } - - protected onRender() { - Dom.addClass(this.container, this.prefixClassName('edge-tool-segments')) - this.resetHandles() - const edgeView = this.cellView - const vertices = [...this.vertices] - vertices.unshift(edgeView.sourcePoint) - vertices.push(edgeView.targetPoint) - - for (let i = 0, l = vertices.length; i < l - 1; i += 1) { - const vertex = vertices[i] - const nextVertex = vertices[i + 1] - const handle = this.renderHandle(vertex, nextVertex, i) - this.stamp(handle.container) - this.handles.push(handle) - } - return this - } - - protected renderHandle( - vertex: Point.PointLike, - nextVertex: Point.PointLike, - index: number, - ) { - const handle = this.options.createHandle!({ - index, - graph: this.graph, - guard: (evt) => this.guard(evt), - attrs: this.options.attrs || {}, - }) - - if (this.options.processHandle) { - this.options.processHandle(handle) - } - - this.updateHandle(handle, vertex, nextVertex) - this.container.appendChild(handle.container) - this.startHandleListening(handle) - return handle - } - - protected startHandleListening(handle: Segments.Handle) { - handle.on('change', this.onHandleChange, this) - handle.on('changing', this.onHandleChanging, this) - handle.on('changed', this.onHandleChanged, this) - } - - protected stopHandleListening(handle: Segments.Handle) { - handle.off('change', this.onHandleChange, this) - handle.off('changing', this.onHandleChanging, this) - handle.off('changed', this.onHandleChanged, this) - } - - protected resetHandles() { - const handles = this.handles - this.handles = [] - if (handles) { - handles.forEach((handle) => { - this.stopHandleListening(handle) - handle.remove() - }) - } - } - - protected shiftHandleIndexes(delta: number) { - const handles = this.handles - for (let i = 0, n = handles.length; i < n; i += 1) { - handles[i].options.index! += delta - } - } - - protected resetAnchor( - type: Edge.TerminalType, - anchor: Edge.TerminalCellData['anchor'], - ) { - const edge = this.cellView.cell - const options = { - ui: true, - toolId: this.cid, - } - - if (anchor) { - edge.prop([type, 'anchor'], anchor, options) - } else { - edge.removeProp([type, 'anchor'], options) - } - } - - protected snapHandle( - handle: Segments.Handle, - position: Point.PointLike, - data: Segments.EventData, - ) { - const axis = handle.options.axis! - const index = handle.options.index! - const edgeView = this.cellView - const edge = edgeView.cell - const vertices = edge.getVertices() - const prev = vertices[index - 2] || data.sourceAnchor - const next = vertices[index + 1] || data.targetAnchor - const snapRadius = this.options.snapRadius - if (Math.abs(position[axis] - prev[axis]) < snapRadius) { - position[axis] = prev[axis] - } else if (Math.abs(position[axis] - next[axis]) < snapRadius) { - position[axis] = next[axis] - } - return position - } - - protected onHandleChanging({ - handle, - e, - }: Segments.Handle.EventArgs['changing']) { - const graph = this.graph - const options = this.options - const edgeView = this.cellView - const anchorFn = options.anchor - - const axis = handle.options.axis! - const index = handle.options.index! - 1 - - const data = this.getEventData(e) - const evt = this.normalizeEvent(e) - const coords = graph.snapToGrid(evt.clientX, evt.clientY) - const position = this.snapHandle(handle, coords.clone(), data) - const vertices = ObjectExt.cloneDeep(this.vertices) - let vertex = vertices[index] - let nextVertex = vertices[index + 1] - - // First Segment - const sourceView = edgeView.sourceView - const sourceBBox = edgeView.sourceBBox - let changeSourceAnchor = false - let deleteSourceAnchor = false - - if (!vertex) { - vertex = edgeView.sourceAnchor.toJSON() - vertex[axis] = position[axis] - if (sourceBBox.containsPoint(vertex)) { - changeSourceAnchor = true - } else { - vertices.unshift(vertex) - this.shiftHandleIndexes(1) - deleteSourceAnchor = true - } - } else if (index === 0) { - if (sourceBBox.containsPoint(vertex)) { - vertices.shift() - this.shiftHandleIndexes(-1) - changeSourceAnchor = true - } else { - vertex[axis] = position[axis] - deleteSourceAnchor = true - } - } else { - vertex[axis] = position[axis] - } - - if (typeof anchorFn === 'function' && sourceView) { - if (changeSourceAnchor) { - const sourceAnchorPosition = data.sourceAnchor.clone() - sourceAnchorPosition[axis] = position[axis] - const sourceAnchor = FunctionExt.call( - anchorFn, - edgeView, - sourceAnchorPosition, - sourceView, - edgeView.sourceMagnet || sourceView.container, - 'source', - edgeView, - this, - ) - this.resetAnchor('source', sourceAnchor) - } - - if (deleteSourceAnchor) { - this.resetAnchor('source', data.sourceAnchorDef) - } - } - - // Last segment - const targetView = edgeView.targetView - const targetBBox = edgeView.targetBBox - let changeTargetAnchor = false - let deleteTargetAnchor = false - if (!nextVertex) { - nextVertex = edgeView.targetAnchor.toJSON() - nextVertex[axis] = position[axis] - if (targetBBox.containsPoint(nextVertex)) { - changeTargetAnchor = true - } else { - vertices.push(nextVertex) - deleteTargetAnchor = true - } - } else if (index === vertices.length - 2) { - if (targetBBox.containsPoint(nextVertex)) { - vertices.pop() - changeTargetAnchor = true - } else { - nextVertex[axis] = position[axis] - deleteTargetAnchor = true - } - } else { - nextVertex[axis] = position[axis] - } - - if (typeof anchorFn === 'function' && targetView) { - if (changeTargetAnchor) { - const targetAnchorPosition = data.targetAnchor.clone() - targetAnchorPosition[axis] = position[axis] - const targetAnchor = FunctionExt.call( - anchorFn, - edgeView, - targetAnchorPosition, - targetView, - edgeView.targetMagnet || targetView.container, - 'target', - edgeView, - this, - ) - this.resetAnchor('target', targetAnchor) - } - if (deleteTargetAnchor) { - this.resetAnchor('target', data.targetAnchorDef) - } - } - - if (!Point.equalPoints(vertices, this.vertices)) { - this.cellView.cell.setVertices(vertices, { ui: true, toolId: this.cid }) - } - - this.updateHandle(handle, vertex, nextVertex, 0) - if (!options.stopPropagation) { - edgeView.notifyMouseMove(evt, coords.x, coords.y) - } - } - - protected onHandleChange({ handle, e }: Segments.Handle.EventArgs['change']) { - const options = this.options - const handles = this.handles - const edgeView = this.cellView - - const index = handle.options.index - if (!Array.isArray(handles)) { - return - } - - for (let i = 0, n = handles.length; i < n; i += 1) { - if (i !== index) { - handles[i].hide() - } - } - - this.focus() - this.setEventData(e, { - sourceAnchor: edgeView.sourceAnchor.clone(), - targetAnchor: edgeView.targetAnchor.clone(), - sourceAnchorDef: ObjectExt.cloneDeep( - this.cell.prop(['source', 'anchor']), - ), - targetAnchorDef: ObjectExt.cloneDeep( - this.cell.prop(['target', 'anchor']), - ), - }) - - this.cell.startBatch('move-segment', { ui: true, toolId: this.cid }) - - if (!options.stopPropagation) { - const normalizedEvent = this.normalizeEvent(e) - const coords = this.graph.snapToGrid( - normalizedEvent.clientX, - normalizedEvent.clientY, - ) - edgeView.notifyMouseDown(normalizedEvent, coords.x, coords.y) - } - } - - protected onHandleChanged({ e }: Segments.Handle.EventArgs['changed']) { - const options = this.options - const edgeView = this.cellView - if (options.removeRedundancies) { - edgeView.removeRedundantLinearVertices({ ui: true, toolId: this.cid }) - } - - const normalizedEvent = this.normalizeEvent(e) - const coords = this.graph.snapToGrid( - normalizedEvent.clientX, - normalizedEvent.clientY, - ) - - this.render() - this.blur() - - this.cell.stopBatch('move-segment', { ui: true, toolId: this.cid }) - if (!options.stopPropagation) { - edgeView.notifyMouseUp(normalizedEvent, coords.x, coords.y) - } - edgeView.checkMouseleave(normalizedEvent) - - options.onChanged && options.onChanged({ edge: edgeView.cell, edgeView }) - } - - protected updateHandle( - handle: Segments.Handle, - vertex: Point.PointLike, - nextVertex: Point.PointLike, - offset = 0, - ) { - const precision = this.options.precision || 0 - const vertical = Math.abs(vertex.x - nextVertex.x) < precision - const horizontal = Math.abs(vertex.y - nextVertex.y) < precision - if (vertical || horizontal) { - const segmentLine = new Line(vertex, nextVertex) - const length = segmentLine.length() - if (length < this.options.threshold) { - handle.hide() - } else { - const position = segmentLine.getCenter() - const axis = vertical ? 'x' : 'y' - position[axis] += offset || 0 - const angle = segmentLine.vector().vectorAngle(new Point(1, 0)) - handle.updatePosition(position.x, position.y, angle, this.cellView) - handle.show() - handle.options.axis = axis - } - } else { - handle.hide() - } - } - - protected onRemove() { - this.resetHandles() - } -} - -export namespace Segments { - export interface Options extends ToolsView.ToolItem.Options { - threshold: number - precision?: number - snapRadius: number - stopPropagation: boolean - removeRedundancies: boolean - attrs: Attr.SimpleAttrs | ((handle: Handle) => Attr.SimpleAttrs) - anchor?: ( - this: EdgeView, - pos: Point, - terminalView: CellView, - terminalMagnet: Element | null, - terminalType: Edge.TerminalType, - edgeView: EdgeView, - toolView: Segments, - ) => Edge.TerminalCellData['anchor'] - createHandle?: (options: Handle.Options) => Handle - processHandle?: (handle: Handle) => void - onChanged?: (options: { edge: Edge; edgeView: EdgeView }) => void - } - - export interface EventData { - sourceAnchor: Point - targetAnchor: Point - sourceAnchorDef: Edge.TerminalCellData['anchor'] - targetAnchorDef: Edge.TerminalCellData['anchor'] - } -} - -export namespace Segments { - export class Handle extends View { - public container: SVGRectElement - - constructor(public options: Handle.Options) { - super() - this.render() - this.delegateEvents({ - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - }) - } - - render() { - this.container = View.createElement('rect', true) as SVGRectElement - const attrs = this.options.attrs - if (typeof attrs === 'function') { - const defaults = Segments.getDefaults() - this.setAttrs({ - ...defaults.attrs, - ...attrs(this), - }) - } else { - this.setAttrs(attrs) - } - this.addClass(this.prefixClassName('edge-tool-segment')) - } - - updatePosition(x: number, y: number, angle: number, view: EdgeView) { - const p = view.getClosestPoint(new Point(x, y)) || new Point(x, y) - let matrix = Dom.createSVGMatrix().translate(p.x, p.y) - if (!p.equals({ x, y })) { - const line = new Line(x, y, p.x, p.y) - let deg = line.vector().vectorAngle(new Point(1, 0)) - if (deg !== 0) { - deg += 90 - } - matrix = matrix.rotate(deg) - } else { - matrix = matrix.rotate(angle) - } - - this.setAttrs({ - transform: Dom.matrixToTransformString(matrix), - cursor: angle % 180 === 0 ? 'row-resize' : 'col-resize', - }) - } - - protected onMouseDown(evt: Dom.MouseDownEvent) { - if (this.options.guard(evt)) { - return - } - - this.trigger('change', { e: evt, handle: this }) - - evt.stopPropagation() - evt.preventDefault() - this.options.graph.view.undelegateEvents() - this.delegateDocumentEvents( - { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - }, - evt.data, - ) - } - - protected onMouseMove(evt: Dom.MouseMoveEvent) { - this.emit('changing', { e: evt, handle: this }) - } - - protected onMouseUp(evt: Dom.MouseUpEvent) { - this.emit('changed', { e: evt, handle: this }) - this.undelegateDocumentEvents() - this.options.graph.view.delegateEvents() - } - - show() { - this.container.style.display = '' - } - - hide() { - this.container.style.display = 'none' - } - } - - export namespace Handle { - export interface Options { - graph: Graph - guard: (evt: Dom.EventObject) => boolean - attrs: Attr.SimpleAttrs | ((handle: Handle) => Attr.SimpleAttrs) - index?: number - axis?: 'x' | 'y' - } - - export interface EventArgs { - change: { e: Dom.MouseDownEvent; handle: Handle } - changing: { e: Dom.MouseMoveEvent; handle: Handle } - changed: { e: Dom.MouseUpEvent; handle: Handle } - } - } -} - -export namespace Segments { - Segments.config({ - name: 'segments', - precision: 0.5, - threshold: 40, - snapRadius: 10, - stopPropagation: true, - removeRedundancies: true, - attrs: { - width: 20, - height: 8, - x: -10, - y: -4, - rx: 4, - ry: 4, - fill: '#333', - stroke: '#fff', - 'stroke-width': 2, - }, - createHandle: (options) => new Handle(options), - anchor: Util.getAnchor, - }) -} diff --git a/packages/x6-next/src/registry/tool/util.ts b/packages/x6-next/src/registry/tool/util.ts deleted file mode 100644 index 1429b6e7cca..00000000000 --- a/packages/x6-next/src/registry/tool/util.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { FunctionExt } from '@antv/x6-common' -import { Point } from '@antv/x6-geometry' -import { ConnectionStrategy } from '../connection-strategy' -import { Edge } from '../../model/edge' -import { CellView } from '../../view/cell' -import { EdgeView } from '../../view/edge' - -export function getAnchor( - this: EdgeView, - pos: Point.PointLike, - terminalView: CellView, - terminalMagnet: Element, - type: Edge.TerminalType, -) { - const end = FunctionExt.call( - ConnectionStrategy.presets.pinRelative, - this.graph, - {} as Edge.TerminalCellData, - terminalView, - terminalMagnet, - pos, - this.cell, - type, - {}, - ) - - return end.anchor -} - -export function getViewBBox(view: CellView, quick?: boolean) { - if (quick) { - return view.cell.getBBox() - } - - return view.cell.isEdge() - ? (view as EdgeView).getConnection()!.bbox()! - : view.getUnrotatedBBoxOfElement(view.container as SVGElement) -} diff --git a/packages/x6-next/src/registry/tool/vertices.ts b/packages/x6-next/src/registry/tool/vertices.ts deleted file mode 100644 index 07e0d976736..00000000000 --- a/packages/x6-next/src/registry/tool/vertices.ts +++ /dev/null @@ -1,415 +0,0 @@ -import { Point } from '@antv/x6-geometry' -import { Dom } from '@antv/x6-common' -import { Config } from '../../config' -import { View } from '../../view/view' -import { ToolsView } from '../../view/tool' -import { EdgeView } from '../../view/edge' -import { Edge } from '../../model/edge' -import { Attr } from '../attr' -import { Graph } from '../../graph' - -export class Vertices extends ToolsView.ToolItem { - protected handles: Vertices.Handle[] = [] - - protected get vertices() { - return this.cellView.cell.getVertices() - } - - protected onRender() { - this.addClass(this.prefixClassName('edge-tool-vertices')) - if (this.options.addable) { - this.updatePath() - } - this.resetHandles() - this.renderHandles() - return this - } - - update() { - const vertices = this.vertices - if (vertices.length === this.handles.length) { - this.updateHandles() - } else { - this.resetHandles() - this.renderHandles() - } - - if (this.options.addable) { - this.updatePath() - } - - return this - } - - protected resetHandles() { - const handles = this.handles - this.handles = [] - if (handles) { - handles.forEach((handle) => { - this.stopHandleListening(handle) - handle.remove() - }) - } - } - - protected renderHandles() { - const vertices = this.vertices - for (let i = 0, l = vertices.length; i < l; i += 1) { - const vertex = vertices[i] - const createHandle = this.options.createHandle! - const processHandle = this.options.processHandle - const handle = createHandle({ - index: i, - graph: this.graph, - guard: (evt: Dom.EventObject) => this.guard(evt), // eslint-disable-line no-loop-func - attrs: this.options.attrs || {}, - }) - - if (processHandle) { - processHandle(handle) - } - - handle.updatePosition(vertex.x, vertex.y) - this.stamp(handle.container) - this.container.appendChild(handle.container) - this.handles.push(handle) - this.startHandleListening(handle) - } - } - - protected updateHandles() { - const vertices = this.vertices - for (let i = 0, l = vertices.length; i < l; i += 1) { - const vertex = vertices[i] - const handle = this.handles[i] - if (handle) { - handle.updatePosition(vertex.x, vertex.y) - } - } - } - - protected updatePath() { - const connection = this.childNodes.connection - if (connection) { - connection.setAttribute('d', this.cellView.getConnectionPathData()) - } - } - - protected startHandleListening(handle: Vertices.Handle) { - const edgeView = this.cellView - if (edgeView.can('vertexMovable')) { - handle.on('change', this.onHandleChange, this) - handle.on('changing', this.onHandleChanging, this) - handle.on('changed', this.onHandleChanged, this) - } - - if (edgeView.can('vertexDeletable')) { - handle.on('remove', this.onHandleRemove, this) - } - } - - protected stopHandleListening(handle: Vertices.Handle) { - const edgeView = this.cellView - if (edgeView.can('vertexMovable')) { - handle.off('change', this.onHandleChange, this) - handle.off('changing', this.onHandleChanging, this) - handle.off('changed', this.onHandleChanged, this) - } - - if (edgeView.can('vertexDeletable')) { - handle.off('remove', this.onHandleRemove, this) - } - } - - protected getNeighborPoints(index: number) { - const edgeView = this.cellView - const vertices = this.vertices - const prev = index > 0 ? vertices[index - 1] : edgeView.sourceAnchor - const next = - index < vertices.length - 1 ? vertices[index + 1] : edgeView.targetAnchor - return { - prev: Point.create(prev), - next: Point.create(next), - } - } - - protected getMouseEventArgs(evt: T) { - const e = this.normalizeEvent(evt) - const { x, y } = this.graph.snapToGrid(e.clientX!, e.clientY!) - return { e, x, y } - } - - protected onHandleChange({ e }: Vertices.Handle.EventArgs['change']) { - this.focus() - const edgeView = this.cellView - edgeView.cell.startBatch('move-vertex', { ui: true, toolId: this.cid }) - if (!this.options.stopPropagation) { - const { e: evt, x, y } = this.getMouseEventArgs(e) - edgeView.notifyMouseDown(evt, x, y) - } - } - - protected onHandleChanging({ - handle, - e, - }: Vertices.Handle.EventArgs['changing']) { - const edgeView = this.cellView - const index = handle.options.index - const { e: evt, x, y } = this.getMouseEventArgs(e) - const vertex = { x, y } - this.snapVertex(vertex, index) - edgeView.cell.setVertexAt(index, vertex, { ui: true, toolId: this.cid }) - handle.updatePosition(vertex.x, vertex.y) - if (!this.options.stopPropagation) { - edgeView.notifyMouseMove(evt, x, y) - } - } - - protected onHandleChanged({ e }: Vertices.Handle.EventArgs['changed']) { - const options = this.options - const edgeView = this.cellView - - if (options.addable) { - this.updatePath() - } - - if (!options.removeRedundancies) { - return - } - - const verticesRemoved = edgeView.removeRedundantLinearVertices({ - ui: true, - toolId: this.cid, - }) - - if (verticesRemoved) { - this.render() - } - - this.blur() - - edgeView.cell.stopBatch('move-vertex', { ui: true, toolId: this.cid }) - - if (this.eventData(e).vertexAdded) { - edgeView.cell.stopBatch('add-vertex', { ui: true, toolId: this.cid }) - } - - const { e: evt, x, y } = this.getMouseEventArgs(e) - - if (!this.options.stopPropagation) { - edgeView.notifyMouseUp(evt, x, y) - } - - edgeView.checkMouseleave(evt) - - options.onChanged && options.onChanged({ edge: edgeView.cell, edgeView }) - } - - protected snapVertex(vertex: Point.PointLike, index: number) { - const snapRadius = this.options.snapRadius || 0 - if (snapRadius > 0) { - const neighbors = this.getNeighborPoints(index) - const prev = neighbors.prev - const next = neighbors.next - if (Math.abs(vertex.x - prev.x) < snapRadius) { - vertex.x = prev.x - } else if (Math.abs(vertex.x - next.x) < snapRadius) { - vertex.x = next.x - } - - if (Math.abs(vertex.y - prev.y) < snapRadius) { - vertex.y = neighbors.prev.y - } else if (Math.abs(vertex.y - next.y) < snapRadius) { - vertex.y = next.y - } - } - } - - protected onHandleRemove({ handle, e }: Vertices.Handle.EventArgs['remove']) { - if (this.options.removable) { - const index = handle.options.index - const edgeView = this.cellView - edgeView.cell.removeVertexAt(index, { ui: true }) - if (this.options.addable) { - this.updatePath() - } - edgeView.checkMouseleave(this.normalizeEvent(e)) - } - } - - protected onPathMouseDown(evt: Dom.MouseDownEvent) { - const edgeView = this.cellView - - if ( - this.guard(evt) || - !this.options.addable || - !edgeView.can('vertexAddable') - ) { - return - } - - evt.stopPropagation() - evt.preventDefault() - - const e = this.normalizeEvent(evt) - const vertex = this.graph.snapToGrid(e.clientX, e.clientY).toJSON() - edgeView.cell.startBatch('add-vertex', { ui: true, toolId: this.cid }) - const index = edgeView.getVertexIndex(vertex.x, vertex.y) - this.snapVertex(vertex, index) - edgeView.cell.insertVertex(vertex, index, { - ui: true, - toolId: this.cid, - }) - this.render() - const handle = this.handles[index] - this.eventData(e, { vertexAdded: true }) - handle.onMouseDown(e) - } - - protected onRemove() { - this.resetHandles() - } -} - -export namespace Vertices { - export interface Options extends ToolsView.ToolItem.Options { - snapRadius?: number - addable?: boolean - removable?: boolean - removeRedundancies?: boolean - stopPropagation?: boolean - attrs?: Attr.SimpleAttrs | ((handle: Handle) => Attr.SimpleAttrs) - createHandle?: (options: Handle.Options) => Handle - processHandle?: (handle: Handle) => void - onChanged?: (options: { edge: Edge; edgeView: EdgeView }) => void - } -} - -export namespace Vertices { - export class Handle extends View { - protected get graph() { - return this.options.graph - } - - constructor(public readonly options: Handle.Options) { - super() - this.render() - this.delegateEvents({ - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - dblclick: 'onDoubleClick', - }) - } - - render() { - this.container = View.createElement('circle', true) - const attrs = this.options.attrs - if (typeof attrs === 'function') { - const defaults = Vertices.getDefaults() - this.setAttrs({ - ...defaults.attrs, - ...attrs(this), - }) - } else { - this.setAttrs(attrs) - } - - this.addClass(this.prefixClassName('edge-tool-vertex')) - } - - updatePosition(x: number, y: number) { - this.setAttrs({ cx: x, cy: y }) - } - - onMouseDown(evt: Dom.MouseDownEvent) { - if (this.options.guard(evt)) { - return - } - - evt.stopPropagation() - evt.preventDefault() - this.graph.view.undelegateEvents() - - this.delegateDocumentEvents( - { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - }, - evt.data, - ) - - this.emit('change', { e: evt, handle: this }) - } - - protected onMouseMove(evt: Dom.MouseMoveEvent) { - this.emit('changing', { e: evt, handle: this }) - } - - protected onMouseUp(evt: Dom.MouseUpEvent) { - this.emit('changed', { e: evt, handle: this }) - this.undelegateDocumentEvents() - this.graph.view.delegateEvents() - } - - protected onDoubleClick(evt: Dom.DoubleClickEvent) { - this.emit('remove', { e: evt, handle: this }) - } - } - - export namespace Handle { - export interface Options { - graph: Graph - index: number - guard: (evt: Dom.EventObject) => boolean - attrs: Attr.SimpleAttrs | ((handle: Handle) => Attr.SimpleAttrs) - } - - export interface EventArgs { - change: { e: Dom.MouseDownEvent; handle: Handle } - changing: { e: Dom.MouseMoveEvent; handle: Handle } - changed: { e: Dom.MouseUpEvent; handle: Handle } - remove: { e: Dom.DoubleClickEvent; handle: Handle } - } - } -} - -export namespace Vertices { - const pathClassName = Config.prefix('edge-tool-vertex-path') - - Vertices.config({ - name: 'vertices', - snapRadius: 20, - addable: true, - removable: true, - removeRedundancies: true, - stopPropagation: true, - attrs: { - r: 6, - fill: '#333', - stroke: '#fff', - cursor: 'move', - 'stroke-width': 2, - }, - createHandle: (options) => new Handle(options), - markup: [ - { - tagName: 'path', - selector: 'connection', - className: pathClassName, - attrs: { - fill: 'none', - stroke: 'transparent', - 'stroke-width': 10, - cursor: 'pointer', - }, - }, - ], - events: { - [`mousedown .${pathClassName}`]: 'onPathMouseDown', - [`touchstart .${pathClassName}`]: 'onPathMouseDown', - }, - }) -} diff --git a/packages/x6-next/src/shape/base.ts b/packages/x6-next/src/shape/base.ts deleted file mode 100644 index 8c7cc86d24a..00000000000 --- a/packages/x6-next/src/shape/base.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { ObjectExt } from '@antv/x6-common' -import { Node } from '../model' - -export class Base< - Properties extends Node.Properties = Node.Properties, -> extends Node { - get label() { - return this.getLabel() - } - - set label(val: string | undefined | null) { - this.setLabel(val) - } - - getLabel() { - return this.getAttrByPath('text/text') - } - - setLabel(label?: string | null, options?: Node.SetOptions) { - if (label == null) { - this.removeLabel() - } else { - this.setAttrByPath('text/text', label, options) - } - - return this - } - - removeLabel() { - this.removeAttrByPath('text/text') - return this - } -} - -export namespace Base { - export const bodyAttr = { - fill: '#ffffff', - stroke: '#333333', - strokeWidth: 2, - } - - export const labelAttr = { - fontSize: 14, - fill: '#000000', - refX: 0.5, - refY: 0.5, - textAnchor: 'middle', - textVerticalAnchor: 'middle', - fontFamily: 'Arial, helvetica, sans-serif', - } - - Base.config({ - attrs: { text: { ...labelAttr } }, - propHooks(metadata) { - const { label, ...others } = metadata - if (label) { - ObjectExt.setByPath(others, 'attrs/text/text', label) - } - return others - }, - }) -} diff --git a/packages/x6-next/src/shape/index.ts b/packages/x6-next/src/shape/index.ts deleted file mode 100644 index 8410e167dec..00000000000 --- a/packages/x6-next/src/shape/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './rect' -export * from './edge' -export * from './rect' -export * from './ellipse' -export * from './polygon' -export * from './polyline' -export * from './path' -export * from './text-block' -export * from './image' -export * from './edge' -export * from './circle' diff --git a/packages/x6-next/src/style/index.less b/packages/x6-next/src/style/index.less deleted file mode 100755 index 223d5317bda..00000000000 --- a/packages/x6-next/src/style/index.less +++ /dev/null @@ -1,177 +0,0 @@ -@import './themes/index'; - -.noScalingStroke() { - vector-effect: non-scaling-stroke; -} - -.@{x6-prefix}-graph { - position: relative; - outline: none; - - &-background, - &-grid, - &-svg { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - - &-stage { - user-select: none; - } - } - - &&-pannable { - cursor: grab; - cursor: -moz-grab; - cursor: -webkit-grab; - } - - &&-panning { - cursor: grabbing; - cursor: -moz-grabbing; - cursor: -webkit-grabbing; - user-select: none; - } -} - -.@{x6-prefix}-node { - cursor: move; - - &&-immovable { - cursor: default; - } - - * { - -webkit-user-drag: none; - } - - .scalable * { - .noScalingStroke(); - } - - [magnet='true'] { - cursor: crosshair; - transition: opacity 0.3s; - - &:hover { - opacity: 0.7; - } - } - - /* stylelint-disable-next-line */ - foreignObject { - display: block; - overflow: visible; - background-color: transparent; - - & > body { - position: static; - width: 100%; - height: 100%; - margin: 0; - padding: 0; - overflow: visible; - background-color: transparent; - } - } -} - -.@{x6-prefix}-edge { - .source-marker, - .target-marker { - .noScalingStroke(); - } - - .connection { - stroke-linejoin: round; - fill: none; - } - - .connection-wrap { - cursor: move; - opacity: 0; - fill: none; - stroke: #000; - stroke-width: 15; - stroke-linecap: round; - stroke-linejoin: round; - - &:hover { - opacity: 0.4; - stroke-opacity: 0.4; - } - } - - .vertices { - cursor: move; - opacity: 0; - - .vertex { - fill: #1abc9c; - - :hover { - fill: #34495e; - stroke: none; - } - } - - .vertex-remove { - cursor: pointer; - fill: #fff; - } - - .vertex-remove-area { - cursor: pointer; - opacity: 0.1; - } - - .vertex-group:hover .vertex-remove-area { - opacity: 1; - } - } - - .arrowheads { - cursor: move; - opacity: 0; - - .arrowhead { - fill: #1abc9c; - - :hover { - fill: #f39c12; - stroke: none; - } - } - } - - .tools { - cursor: pointer; - opacity: 0; - - .tool-options { - display: none; - } - - .tool-remove circle { - fill: #f00; - } - - .tool-remove path { - fill: #fff; - } - } - - &:hover { - .vertices, - .arrowheads, - .tools { - opacity: 1; - } - } -} - -.@{x6-prefix}-highlight-opacity { - opacity: 0.3; -} diff --git a/packages/x6-next/src/style/raw.ts b/packages/x6-next/src/style/raw.ts deleted file mode 100644 index 132848d68b2..00000000000 --- a/packages/x6-next/src/style/raw.ts +++ /dev/null @@ -1,164 +0,0 @@ -/* eslint-disable */ - -/** - * Auto generated file, do not modify it! - */ - -export const content = `.x6-graph { - position: relative; - outline: none; -} -.x6-graph-background, -.x6-graph-grid, -.x6-graph-svg { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; -} -.x6-graph-background-stage, -.x6-graph-grid-stage, -.x6-graph-svg-stage { - user-select: none; -} -.x6-graph.x6-graph-pannable { - cursor: grab; - cursor: -moz-grab; - cursor: -webkit-grab; -} -.x6-graph.x6-graph-panning { - cursor: grabbing; - cursor: -moz-grabbing; - cursor: -webkit-grabbing; - user-select: none; -} -.x6-node { - cursor: move; - /* stylelint-disable-next-line */ -} -.x6-node.x6-node-immovable { - cursor: default; -} -.x6-node * { - -webkit-user-drag: none; -} -.x6-node .scalable * { - vector-effect: non-scaling-stroke; -} -.x6-node [magnet='true'] { - cursor: crosshair; - transition: opacity 0.3s; -} -.x6-node [magnet='true']:hover { - opacity: 0.7; -} -.x6-node foreignObject { - display: block; - overflow: visible; - background-color: transparent; -} -.x6-node foreignObject > body { - position: static; - width: 100%; - height: 100%; - margin: 0; - padding: 0; - overflow: visible; - background-color: transparent; -} -.x6-edge .source-marker, -.x6-edge .target-marker { - vector-effect: non-scaling-stroke; -} -.x6-edge .connection { - stroke-linejoin: round; - fill: none; -} -.x6-edge .connection-wrap { - cursor: move; - opacity: 0; - fill: none; - stroke: #000; - stroke-width: 15; - stroke-linecap: round; - stroke-linejoin: round; -} -.x6-edge .connection-wrap:hover { - opacity: 0.4; - stroke-opacity: 0.4; -} -.x6-edge .vertices { - cursor: move; - opacity: 0; -} -.x6-edge .vertices .vertex { - fill: #1abc9c; -} -.x6-edge .vertices .vertex :hover { - fill: #34495e; - stroke: none; -} -.x6-edge .vertices .vertex-remove { - cursor: pointer; - fill: #fff; -} -.x6-edge .vertices .vertex-remove-area { - cursor: pointer; - opacity: 0.1; -} -.x6-edge .vertices .vertex-group:hover .vertex-remove-area { - opacity: 1; -} -.x6-edge .arrowheads { - cursor: move; - opacity: 0; -} -.x6-edge .arrowheads .arrowhead { - fill: #1abc9c; -} -.x6-edge .arrowheads .arrowhead :hover { - fill: #f39c12; - stroke: none; -} -.x6-edge .tools { - cursor: pointer; - opacity: 0; -} -.x6-edge .tools .tool-options { - display: none; -} -.x6-edge .tools .tool-remove circle { - fill: #f00; -} -.x6-edge .tools .tool-remove path { - fill: #fff; -} -.x6-edge:hover .vertices, -.x6-edge:hover .arrowheads, -.x6-edge:hover .tools { - opacity: 1; -} -.x6-highlight-opacity { - opacity: 0.3; -} -.x6-cell-tool-editor { - position: relative; - display: inline-block; - min-height: 1em; - margin: 0; - padding: 0; - line-height: 1; - white-space: normal; - text-align: center; - vertical-align: top; - overflow-wrap: normal; - outline: none; - transform-origin: 0 0; - -webkit-user-drag: none; -} -.x6-edge-tool-editor { - border: 1px solid #275fc5; - border-radius: 2px; -} -` diff --git a/packages/x6-next/src/style/themes/default.less b/packages/x6-next/src/style/themes/default.less deleted file mode 100644 index 879dd1a8c8a..00000000000 --- a/packages/x6-next/src/style/themes/default.less +++ /dev/null @@ -1,3 +0,0 @@ -@theme: default; - -@x6-prefix: x6; diff --git a/packages/x6-next/src/style/themes/index.less b/packages/x6-next/src/style/themes/index.less deleted file mode 100644 index f19386d8b2f..00000000000 --- a/packages/x6-next/src/style/themes/index.less +++ /dev/null @@ -1 +0,0 @@ -@import './default.less'; diff --git a/packages/x6-next/src/util/index.ts b/packages/x6-next/src/util/index.ts deleted file mode 100644 index e5d45013021..00000000000 --- a/packages/x6-next/src/util/index.ts +++ /dev/null @@ -1,407 +0,0 @@ -import { - Point, - Line, - Rectangle, - Polyline, - Ellipse, - Path, -} from '@antv/x6-geometry' -import { Dom, PointData, PointLike } from '@antv/x6-common' - -export namespace Util { - const svgDocument = Dom.createSvgElement('svg') as SVGSVGElement - /** - * Transforms point by an SVG transformation represented by `matrix`. - */ - export function transformPoint(point: Point.PointLike, matrix: DOMMatrix) { - const ret = Dom.createSVGPoint(point.x, point.y).matrixTransform(matrix) - return new Point(ret.x, ret.y) - } - - /** - * Transforms line by an SVG transformation represented by `matrix`. - */ - export function transformLine(line: Line, matrix: DOMMatrix) { - return new Line( - transformPoint(line.start, matrix), - transformPoint(line.end, matrix), - ) - } - - /** - * Transforms polyline by an SVG transformation represented by `matrix`. - */ - export function transformPolyline(polyline: Polyline, matrix: DOMMatrix) { - let points = polyline instanceof Polyline ? polyline.points : polyline - if (!Array.isArray(points)) { - points = [] - } - - return new Polyline(points.map((p) => transformPoint(p, matrix))) - } - - export function transformRectangle( - rect: Rectangle.RectangleLike, - matrix: DOMMatrix, - ) { - const p = svgDocument.createSVGPoint() - - p.x = rect.x - p.y = rect.y - const corner1 = p.matrixTransform(matrix) - - p.x = rect.x + rect.width - p.y = rect.y - const corner2 = p.matrixTransform(matrix) - - p.x = rect.x + rect.width - p.y = rect.y + rect.height - const corner3 = p.matrixTransform(matrix) - - p.x = rect.x - p.y = rect.y + rect.height - const corner4 = p.matrixTransform(matrix) - - const minX = Math.min(corner1.x, corner2.x, corner3.x, corner4.x) - const maxX = Math.max(corner1.x, corner2.x, corner3.x, corner4.x) - const minY = Math.min(corner1.y, corner2.y, corner3.y, corner4.y) - const maxY = Math.max(corner1.y, corner2.y, corner3.y, corner4.y) - - return new Rectangle(minX, minY, maxX - minX, maxY - minY) - } - - /** - * Returns the bounding box of the element after transformations are - * applied. If `withoutTransformations` is `true`, transformations of - * the element will not be considered when computing the bounding box. - * If `target` is specified, bounding box will be computed relatively - * to the `target` element. - */ - export function bbox( - elem: SVGElement, - withoutTransformations?: boolean, - target?: SVGElement, - ): Rectangle { - let box - const ownerSVGElement = elem.ownerSVGElement - - // If the element is not in the live DOM, it does not have a bounding - // box defined and so fall back to 'zero' dimension element. - if (!ownerSVGElement) { - return new Rectangle(0, 0, 0, 0) - } - - try { - box = (elem as SVGGraphicsElement).getBBox() - } catch (e) { - // Fallback for IE. - box = { - x: elem.clientLeft, - y: elem.clientTop, - width: elem.clientWidth, - height: elem.clientHeight, - } - } - - if (withoutTransformations) { - return Rectangle.create(box) - } - - const matrix = Dom.getTransformToElement(elem, target || ownerSVGElement) - return transformRectangle(box, matrix) - } - - /** - * Returns the bounding box of the element after transformations are - * applied. Unlike `bbox()`, this function fixes a browser implementation - * bug to return the correct bounding box if this elemenent is a group of - * svg elements (if `options.recursive` is specified). - */ - export function getBBox( - elem: SVGElement, - options: { - target?: SVGElement | null - recursive?: boolean - } = {}, - ): Rectangle { - let outputBBox - const ownerSVGElement = elem.ownerSVGElement - - // If the element is not in the live DOM, it does not have a bounding box - // defined and so fall back to 'zero' dimension element. - // If the element is not an SVGGraphicsElement, we could not measure the - // bounding box either - if (!ownerSVGElement || !Dom.isSVGGraphicsElement(elem)) { - if (Dom.isHTMLElement(elem)) { - // If the element is a HTMLElement, return the position relative to the body - const { left, top, width, height } = getBoundingOffsetRect(elem as any) - return new Rectangle(left, top, width, height) - } - return new Rectangle(0, 0, 0, 0) - } - - let target = options.target - const recursive = options.recursive - - if (!recursive) { - try { - outputBBox = elem.getBBox() - } catch (e) { - outputBBox = { - x: elem.clientLeft, - y: elem.clientTop, - width: elem.clientWidth, - height: elem.clientHeight, - } - } - - if (!target) { - return Rectangle.create(outputBBox) - } - - // transform like target - const matrix = Dom.getTransformToElement(elem, target) - return transformRectangle(outputBBox, matrix) - } - - // recursive - { - const children = elem.childNodes - const n = children.length - - if (n === 0) { - return getBBox(elem, { - target, - }) - } - - if (!target) { - target = elem // eslint-disable-line - } - - for (let i = 0; i < n; i += 1) { - const child = children[i] as SVGElement - let childBBox - - if (child.childNodes.length === 0) { - childBBox = getBBox(child, { - target, - }) - } else { - // if child is a group element, enter it with a recursive call - childBBox = getBBox(child, { - target, - recursive: true, - }) - } - - if (!outputBBox) { - outputBBox = childBBox - } else { - outputBBox = outputBBox.union(childBBox) - } - } - - return outputBBox as Rectangle - } - } - - export function getBoundingOffsetRect(elem: HTMLElement) { - let left = 0 - let top = 0 - let width = 0 - let height = 0 - if (elem) { - let current = elem as any - while (current) { - left += current.offsetLeft - top += current.offsetTop - current = current.offsetParent - if (current) { - left += parseInt(Dom.getComputedStyle(current, 'borderLeft'), 10) - top += parseInt(Dom.getComputedStyle(current, 'borderTop'), 10) - } - } - width = elem.offsetWidth - height = elem.offsetHeight - } - return { - left, - top, - width, - height, - } - } - - /** - * Convert the SVGElement to an equivalent geometric shape. The element's - * transformations are not taken into account. - * - * SVGRectElement => Rectangle - * - * SVGLineElement => Line - * - * SVGCircleElement => Ellipse - * - * SVGEllipseElement => Ellipse - * - * SVGPolygonElement => Polyline - * - * SVGPolylineElement => Polyline - * - * SVGPathElement => Path - * - * others => Rectangle - */ - export function toGeometryShape(elem: SVGElement) { - const attr = (name: string) => { - const s = elem.getAttribute(name) - const v = s ? parseFloat(s) : 0 - return Number.isNaN(v) ? 0 : v - } - - switch (elem instanceof SVGElement && elem.nodeName.toLowerCase()) { - case 'rect': - return new Rectangle( - attr('x'), - attr('y'), - attr('width'), - attr('height'), - ) - case 'circle': - return new Ellipse(attr('cx'), attr('cy'), attr('r'), attr('r')) - case 'ellipse': - return new Ellipse(attr('cx'), attr('cy'), attr('rx'), attr('ry')) - case 'polyline': { - const points = Dom.getPointsFromSvgElement(elem as SVGPolylineElement) - return new Polyline(points) - } - case 'polygon': { - const points = Dom.getPointsFromSvgElement(elem as SVGPolygonElement) - if (points.length > 1) { - points.push(points[0]) - } - return new Polyline(points) - } - case 'path': { - let d = elem.getAttribute('d') as string - if (!Path.isValid(d)) { - d = Path.normalize(d) - } - return Path.parse(d) - } - case 'line': { - return new Line(attr('x1'), attr('y1'), attr('x2'), attr('y2')) - } - default: - break - } - - // Anything else is a rectangle - return getBBox(elem) - } - - export function translateAndAutoOrient( - elem: SVGElement, - position: PointLike | PointData, - reference: PointLike | PointData, - target?: SVGElement, - ) { - const pos = Point.create(position) - const ref = Point.create(reference) - - if (!target) { - const svg = elem instanceof SVGSVGElement ? elem : elem.ownerSVGElement! - target = svg // eslint-disable-line - } - - // Clean-up previously set transformations except the scale. - // If we didn't clean up the previous transformations then they'd - // add up with the old ones. Scale is an exception as it doesn't - // add up, consider: `this.scale(2).scale(2).scale(2)`. The result - // is that the element is scaled by the factor 2, not 8. - const s = Dom.scale(elem) - elem.setAttribute('transform', '') - const bbox = getBBox(elem, { - target, - }).scale(s.sx, s.sy) - - // 1. Translate to origin. - const translateToOrigin = Dom.createSVGTransform() - translateToOrigin.setTranslate( - -bbox.x - bbox.width / 2, - -bbox.y - bbox.height / 2, - ) - - // 2. Rotate around origin. - const rotateAroundOrigin = Dom.createSVGTransform() - const angle = pos.angleBetween(ref, pos.clone().translate(1, 0)) - if (angle) rotateAroundOrigin.setRotate(angle, 0, 0) - - // 3. Translate to the `position` + the offset (half my width) - // towards the `reference` point. - const translateFromOrigin = Dom.createSVGTransform() - const finalPosition = pos.clone().move(ref, bbox.width / 2) - translateFromOrigin.setTranslate( - 2 * pos.x - finalPosition.x, - 2 * pos.y - finalPosition.y, - ) - - // 4. Get the current transformation matrix of this node - const ctm = Dom.getTransformToElement(elem, target) - - // 5. Apply transformations and the scale - const transform = Dom.createSVGTransform() - transform.setMatrix( - translateFromOrigin.matrix.multiply( - rotateAroundOrigin.matrix.multiply( - translateToOrigin.matrix.multiply(ctm.scale(s.sx, s.sy)), - ), - ), - ) - - elem.setAttribute( - 'transform', - Dom.matrixToTransformString(transform.matrix), - ) - } - - export function findShapeNode(magnet: Element) { - if (magnet == null) { - return null - } - - let node = magnet - do { - let tagName = node.tagName - if (typeof tagName !== 'string') return null - tagName = tagName.toUpperCase() - if (Dom.hasClass(node, 'x6-port')) { - node = node.nextElementSibling as Element - } else if (tagName === 'G') { - node = node.firstElementChild as Element - } else if (tagName === 'TITLE') { - node = node.nextElementSibling as Element - } else break - } while (node) - - return node - } - - // BBox is calculated by the attribute and shape of the node. - // Because of the reduction in DOM API calls, there is a significant performance improvement. - export function getBBoxV2(elem: SVGElement) { - const node = findShapeNode(elem) - - if (!Dom.isSVGGraphicsElement(node)) { - if (Dom.isHTMLElement(elem)) { - const { left, top, width, height } = getBoundingOffsetRect(elem as any) - return new Rectangle(left, top, width, height) - } - return new Rectangle(0, 0, 0, 0) - } - - const shape = toGeometryShape(node) - return shape.bbox() || Rectangle.create() - } -} diff --git a/packages/x6-next/src/view/attr.ts b/packages/x6-next/src/view/attr.ts deleted file mode 100644 index 4812f733cff..00000000000 --- a/packages/x6-next/src/view/attr.ts +++ /dev/null @@ -1,510 +0,0 @@ -import { - ObjectExt, - ArrayExt, - Dom, - FunctionExt, - Dictionary, - StringExt, -} from '@antv/x6-common' -import { Rectangle, Point } from '@antv/x6-geometry' -import { Attr } from '../registry/attr' -import { View } from './view' -import { Markup } from './markup' -import { CellView } from './cell' -import { Util } from '../util' - -export class AttrManager { - constructor(protected view: CellView) {} - - protected get cell() { - return this.view.cell - } - - protected getDefinition(attrName: string): Attr.Definition | null { - return this.cell.getAttrDefinition(attrName) - } - - protected processAttrs( - elem: Element, - raw: Attr.ComplexAttrs, - ): AttrManager.ProcessedAttrs { - let normal: Attr.SimpleAttrs | undefined - let set: Attr.ComplexAttrs | undefined - let offset: Attr.ComplexAttrs | undefined - let position: Attr.ComplexAttrs | undefined - - const specials: { name: string; definition: Attr.Definition }[] = [] - - // divide the attributes between normal and special - Object.keys(raw).forEach((name) => { - const val = raw[name] - const definition = this.getDefinition(name) - const isValid = FunctionExt.call( - Attr.isValidDefinition, - this.view, - definition, - val, - { - elem, - attrs: raw, - cell: this.cell, - view: this.view, - }, - ) - - if (definition && isValid) { - if (typeof definition === 'string') { - if (normal == null) { - normal = {} - } - normal[definition] = val as Attr.SimpleAttrValue - } else if (val !== null) { - specials.push({ name, definition }) - } - } else { - if (normal == null) { - normal = {} - } - const normalName = StringExt.kebabCase(name) - normal[normalName] = val as Attr.SimpleAttrValue - } - }) - - specials.forEach(({ name, definition }) => { - const val = raw[name] - - const setDefine = definition as Attr.SetDefinition - if (typeof setDefine.set === 'function') { - if (set == null) { - set = {} - } - set[name] = val - } - - const offsetDefine = definition as Attr.OffsetDefinition - if (typeof offsetDefine.offset === 'function') { - if (offset == null) { - offset = {} - } - offset[name] = val - } - - const positionDefine = definition as Attr.PositionDefinition - if (typeof positionDefine.position === 'function') { - if (position == null) { - position = {} - } - position[name] = val - } - }) - - return { - raw, - normal, - set, - offset, - position, - } - } - - protected mergeProcessedAttrs( - allProcessedAttrs: AttrManager.ProcessedAttrs, - roProcessedAttrs: AttrManager.ProcessedAttrs, - ) { - allProcessedAttrs.set = { - ...allProcessedAttrs.set, - ...roProcessedAttrs.set, - } - - allProcessedAttrs.position = { - ...allProcessedAttrs.position, - ...roProcessedAttrs.position, - } - - allProcessedAttrs.offset = { - ...allProcessedAttrs.offset, - ...roProcessedAttrs.offset, - } - - // Handle also the special transform property. - const transform = - allProcessedAttrs.normal && allProcessedAttrs.normal.transform - if (transform != null && roProcessedAttrs.normal) { - roProcessedAttrs.normal.transform = transform - } - allProcessedAttrs.normal = roProcessedAttrs.normal - } - - protected findAttrs( - cellAttrs: Attr.CellAttrs, - rootNode: Element, - selectorCache: { [selector: string]: Element[] }, - selectors: Markup.Selectors, - ) { - const merge: Element[] = [] - const result: Dictionary< - Element, - { - elem: Element - array: boolean - priority: number | number[] - attrs: Attr.ComplexAttrs | Attr.ComplexAttrs[] - } - > = new Dictionary() - - Object.keys(cellAttrs).forEach((selector) => { - const attrs = cellAttrs[selector] - if (!ObjectExt.isPlainObject(attrs)) { - return - } - - const { isCSSSelector, elems } = View.find(selector, rootNode, selectors) - selectorCache[selector] = elems - for (let i = 0, l = elems.length; i < l; i += 1) { - const elem = elems[i] - const unique = selectors && selectors[selector] === elem - const prev = result.get(elem) - if (prev) { - if (!prev.array) { - merge.push(elem) - prev.array = true - prev.attrs = [prev.attrs as Attr.ComplexAttrs] - prev.priority = [prev.priority as number] - } - - const attributes = prev.attrs as Attr.ComplexAttrs[] - const selectedLength = prev.priority as number[] - if (unique) { - // node referenced by `selector` - attributes.unshift(attrs) - selectedLength.unshift(-1) - } else { - // node referenced by `groupSelector` or CSSSelector - const sortIndex = ArrayExt.sortedIndex( - selectedLength, - isCSSSelector ? -1 : l, - ) - - attributes.splice(sortIndex, 0, attrs) - selectedLength.splice(sortIndex, 0, l) - } - } else { - result.set(elem, { - elem, - attrs, - priority: unique ? -1 : l, - array: false, - }) - } - } - }) - - merge.forEach((node) => { - const item = result.get(node)! - const arr = item.attrs as Attr.ComplexAttrs[] - item.attrs = arr.reduceRight( - (memo, attrs) => ObjectExt.merge(memo, attrs), - {}, - ) - }) - - return result as Dictionary< - Element, - { - elem: Element - array: boolean - priority: number | number[] - attrs: Attr.ComplexAttrs - } - > - } - - protected updateRelativeAttrs( - elem: Element, - processedAttrs: AttrManager.ProcessedAttrs, - refBBox: Rectangle, - ) { - const rawAttrs = processedAttrs.raw || {} - let nodeAttrs = processedAttrs.normal || {} - const setAttrs = processedAttrs.set - const positionAttrs = processedAttrs.position - const offsetAttrs = processedAttrs.offset - const getOptions = () => ({ - elem, - cell: this.cell, - view: this.view, - attrs: rawAttrs, - refBBox: refBBox.clone(), - }) - - if (setAttrs != null) { - Object.keys(setAttrs).forEach((name) => { - const val = setAttrs[name] - const def = this.getDefinition(name) - if (def != null) { - const ret = FunctionExt.call( - (def as Attr.SetDefinition).set, - this.view, - val, - getOptions(), - ) - if (typeof ret === 'object') { - nodeAttrs = { - ...nodeAttrs, - ...ret, - } - } else if (ret != null) { - nodeAttrs[name] = ret - } - } - }) - } - - if (elem instanceof HTMLElement) { - // TODO: setting the `transform` attribute on HTMLElements - // via `node.style.transform = 'matrix(...)';` would introduce - // a breaking change (e.g. basic.TextBlock). - this.view.setAttrs(nodeAttrs, elem) - return - } - - // The final translation of the subelement. - const nodeTransform = nodeAttrs.transform - const transform = nodeTransform ? `${nodeTransform}` : null - const nodeMatrix = Dom.transformStringToMatrix(transform) - const nodePosition = new Point(nodeMatrix.e, nodeMatrix.f) - if (nodeTransform) { - delete nodeAttrs.transform - nodeMatrix.e = 0 - nodeMatrix.f = 0 - } - - let positioned = false - if (positionAttrs != null) { - Object.keys(positionAttrs).forEach((name) => { - const val = positionAttrs[name] - const def = this.getDefinition(name) - if (def != null) { - const ts = FunctionExt.call( - (def as Attr.PositionDefinition).position, - this.view, - val, - getOptions(), - ) - - if (ts != null) { - positioned = true - nodePosition.translate(Point.create(ts)) - } - } - }) - } - - // The node bounding box could depend on the `size` - // set from the previous loop. - this.view.setAttrs(nodeAttrs, elem) - - let offseted = false - if (offsetAttrs != null) { - // Check if the node is visible - const nodeBoundingRect = this.view.getBoundingRectOfElement(elem) - if (nodeBoundingRect.width > 0 && nodeBoundingRect.height > 0) { - const nodeBBox = Util.transformRectangle(nodeBoundingRect, nodeMatrix) - - Object.keys(offsetAttrs).forEach((name) => { - const val = offsetAttrs[name] - const def = this.getDefinition(name) - if (def != null) { - const ts = FunctionExt.call( - (def as Attr.OffsetDefinition).offset, - this.view, - val, - { - elem, - cell: this.cell, - view: this.view, - attrs: rawAttrs, - refBBox: nodeBBox, - }, - ) - - if (ts != null) { - offseted = true - nodePosition.translate(Point.create(ts)) - } - } - }) - } - } - - if (nodeTransform != null || positioned || offseted) { - nodePosition.round(1) - nodeMatrix.e = nodePosition.x - nodeMatrix.f = nodePosition.y - elem.setAttribute('transform', Dom.matrixToTransformString(nodeMatrix)) - } - } - - update( - rootNode: Element, - attrs: Attr.CellAttrs, - options: AttrManager.UpdateOptions, - ) { - const selectorCache: { [selector: string]: Element[] } = {} - const nodesAttrs = this.findAttrs( - options.attrs || attrs, - rootNode, - selectorCache, - options.selectors, - ) - - // `nodesAttrs` are different from all attributes, when - // rendering only attributes sent to this method. - const nodesAllAttrs = options.attrs - ? this.findAttrs(attrs, rootNode, selectorCache, options.selectors) - : nodesAttrs - - const specialItems: { - node: Element - refNode: Element | null - attributes: Attr.ComplexAttrs | null - processedAttributes: AttrManager.ProcessedAttrs - }[] = [] - - nodesAttrs.each((data) => { - const node = data.elem - const nodeAttrs = data.attrs - const processed = this.processAttrs(node, nodeAttrs) - if ( - processed.set == null && - processed.position == null && - processed.offset == null - ) { - this.view.setAttrs(processed.normal, node) - } else { - const data = nodesAllAttrs.get(node) - const nodeAllAttrs = data ? data.attrs : null - const refSelector = - nodeAllAttrs && nodeAttrs.ref == null - ? nodeAllAttrs.ref - : nodeAttrs.ref - - let refNode: Element | null - if (refSelector) { - refNode = (selectorCache[refSelector as string] || - this.view.find( - refSelector as string, - rootNode, - options.selectors, - ))[0] - if (!refNode) { - throw new Error(`"${refSelector}" reference does not exist.`) - } - } else { - refNode = null - } - - const item = { - node, - refNode, - attributes: nodeAllAttrs, - processedAttributes: processed, - } - - // If an element in the list is positioned relative to this one, then - // we want to insert this one before it in the list. - const index = specialItems.findIndex((item) => item.refNode === node) - if (index > -1) { - specialItems.splice(index, 0, item) - } else { - specialItems.push(item) - } - } - }) - - const bboxCache: Dictionary = new Dictionary() - let rotatableMatrix: DOMMatrix - specialItems.forEach((item) => { - const node = item.node - const refNode = item.refNode - - let unrotatedRefBBox: Rectangle | undefined - const isRefNodeRotatable = - refNode != null && - options.rotatableNode != null && - Dom.contains(options.rotatableNode, refNode) - - // Find the reference element bounding box. If no reference was - // provided, we use the optional bounding box. - if (refNode) { - unrotatedRefBBox = bboxCache.get(refNode) - } - - if (!unrotatedRefBBox) { - const target = ( - isRefNodeRotatable ? options.rotatableNode! : rootNode - ) as SVGElement - - unrotatedRefBBox = refNode - ? Util.getBBox(refNode as SVGElement, { target }) - : options.rootBBox - - if (refNode) { - bboxCache.set(refNode, unrotatedRefBBox!) - } - } - - let processedAttrs - if (options.attrs && item.attributes) { - // If there was a special attribute affecting the position amongst - // passed-in attributes we have to merge it with the rest of the - // element's attributes as they are necessary to update the position - // relatively (i.e `ref-x` && 'ref-dx'). - processedAttrs = this.processAttrs(node, item.attributes) - this.mergeProcessedAttrs(processedAttrs, item.processedAttributes) - } else { - processedAttrs = item.processedAttributes - } - - let refBBox = unrotatedRefBBox! - if ( - isRefNodeRotatable && - options.rotatableNode != null && - !options.rotatableNode.contains(node) - ) { - // If the referenced node is inside the rotatable group while the - // updated node is outside, we need to take the rotatable node - // transformation into account. - if (!rotatableMatrix) { - rotatableMatrix = Dom.transformStringToMatrix( - Dom.attr(options.rotatableNode, 'transform'), - ) - } - refBBox = Util.transformRectangle(unrotatedRefBBox!, rotatableMatrix) - } - - this.updateRelativeAttrs(node, processedAttrs, refBBox) - }) - } -} - -export namespace AttrManager { - export interface UpdateOptions { - rootBBox: Rectangle - selectors: Markup.Selectors - scalableNode?: Element | null - rotatableNode?: Element | null - /** - * Rendering only the specified attributes. - */ - attrs?: Attr.CellAttrs | null - } - - export interface ProcessedAttrs { - raw: Attr.ComplexAttrs - normal?: Attr.SimpleAttrs | undefined - set?: Attr.ComplexAttrs | undefined - offset?: Attr.ComplexAttrs | undefined - position?: Attr.ComplexAttrs | undefined - } -} diff --git a/packages/x6-next/src/view/cache.ts b/packages/x6-next/src/view/cache.ts deleted file mode 100644 index 5a9869c74a1..00000000000 --- a/packages/x6-next/src/view/cache.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Dictionary, JSONObject, Dom } from '@antv/x6-common' -import { - Line, - Rectangle, - Ellipse, - Polyline, - Path, - Segment, -} from '@antv/x6-geometry' -import { Util } from '../util' -import { CellView } from './cell' - -export class Cache { - protected elemCache: Dictionary - - public pathCache: { - data?: string - length?: number - segmentSubdivisions?: Segment[][] - } - - constructor(protected view: CellView) { - this.clean() - } - - clean() { - if (this.elemCache) { - this.elemCache.dispose() - } - this.elemCache = new Dictionary() - this.pathCache = {} - } - - get(elem: Element) { - const cache = this.elemCache - if (!cache.has(elem)) { - this.elemCache.set(elem, {}) - } - return this.elemCache.get(elem)! - } - - getData(elem: Element) { - const meta = this.get(elem) - if (!meta.data) { - meta.data = {} - } - return meta.data - } - - getMatrix(elem: Element) { - const meta = this.get(elem) - if (meta.matrix == null) { - const target = this.view.container - meta.matrix = Dom.getTransformToParentElement( - elem as any, - target as SVGElement, - ) - } - - return Dom.createSVGMatrix(meta.matrix) - } - - getShape(elem: Element) { - const meta = this.get(elem) - if (meta.shape == null) { - meta.shape = Util.toGeometryShape(elem as SVGElement) - } - return meta.shape.clone() - } - - getBoundingRect(elem: Element) { - const meta = this.get(elem) - if (meta.boundingRect == null) { - meta.boundingRect = Util.getBBoxV2(elem as SVGElement) - } - return meta.boundingRect.clone() - } -} - -export namespace Cache { - export interface Item { - data?: JSONObject - matrix?: DOMMatrix - boundingRect?: Rectangle - shape?: Rectangle | Ellipse | Polyline | Path | Line - } -} diff --git a/packages/x6-next/src/view/cell.ts b/packages/x6-next/src/view/cell.ts deleted file mode 100644 index 2edc0f89422..00000000000 --- a/packages/x6-next/src/view/cell.ts +++ /dev/null @@ -1,911 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { Rectangle } from '@antv/x6-geometry' -import { - ArrayExt, - ObjectExt, - Dom, - FunctionExt, - Registry, - Nilable, - KeyValue, -} from '@antv/x6-common' -import { View } from './view' -import { Cache } from './cache' -import { Markup } from './markup' -import { ToolsView } from './tool' -import { AttrManager } from './attr' -import { FlagManager } from './flag' -import { Util } from '../util' -import { Attr } from '../registry/attr' -import { Cell } from '../model/cell' -import { Edge } from '../model/edge' -import { Model } from '../model/model' -import { EdgeView } from './edge' -import { NodeView } from './node' -import { Graph } from '../graph' - -export class CellView< - Entity extends Cell = Cell, - Options extends CellView.Options = CellView.Options, -> extends View { - protected static defaults: Partial = { - isSvgElement: true, - rootSelector: 'root', - priority: 0, - bootstrap: [], - actions: {}, - } - - public static getDefaults() { - return this.defaults - } - - public static config( - options: Partial, - ) { - this.defaults = this.getOptions(options) - } - - public static getOptions( - options: Partial, - ): T { - const mergeActions = (arr1: T | T[], arr2?: T | T[]) => { - if (arr2 != null) { - return ArrayExt.uniq([ - ...(Array.isArray(arr1) ? arr1 : [arr1]), - ...(Array.isArray(arr2) ? arr2 : [arr2]), - ]) - } - return Array.isArray(arr1) ? [...arr1] : [arr1] - } - - const ret = ObjectExt.cloneDeep(this.getDefaults()) as T - const { bootstrap, actions, events, documentEvents, ...others } = options - - if (bootstrap) { - ret.bootstrap = mergeActions(ret.bootstrap, bootstrap) - } - - if (actions) { - Object.keys(actions).forEach((key) => { - const val = actions[key] - const raw = ret.actions[key] - if (val && raw) { - ret.actions[key] = mergeActions(raw, val) - } else if (val) { - ret.actions[key] = mergeActions(val) - } - }) - } - - if (events) { - ret.events = { ...ret.events, ...events } - } - - if (options.documentEvents) { - ret.documentEvents = { ...ret.documentEvents, ...documentEvents } - } - - return ObjectExt.merge(ret, others) as T - } - - public graph: Graph - public cell: Entity - protected selectors: Markup.Selectors - protected readonly options: Options - protected readonly flag: FlagManager - protected readonly attr: AttrManager - protected readonly cache: Cache - - protected get [Symbol.toStringTag]() { - return CellView.toStringTag - } - - constructor(cell: Entity, options: Partial = {}) { - super() - - this.cell = cell - this.options = this.ensureOptions(options) - this.graph = this.options.graph - this.attr = new AttrManager(this) - this.flag = new FlagManager( - this, - this.options.actions, - this.options.bootstrap, - ) - this.cache = new Cache(this) - - this.setContainer(this.ensureContainer()) - this.setup() - - this.init() - } - - protected init() {} - - protected onRemove() { - this.removeTools() - } - - public get priority() { - return this.options.priority - } - - protected get rootSelector() { - return this.options.rootSelector - } - - protected getConstructor() { - return this.constructor as any as T - } - - protected ensureOptions(options: Partial) { - return this.getConstructor().getOptions(options) as Options - } - - protected getContainerTagName(): string { - return this.options.isSvgElement ? 'g' : 'div' - } - - protected getContainerStyle(): Nilable< - Record - > | void {} - - protected getContainerAttrs(): Nilable { - return { - 'data-cell-id': this.cell.id, - 'data-shape': this.cell.shape, - } - } - - protected getContainerClassName(): Nilable { - return this.prefixClassName('cell') - } - - protected ensureContainer() { - return View.createElement( - this.getContainerTagName(), - this.options.isSvgElement, - ) - } - - protected setContainer(container: Element) { - if (this.container !== container) { - this.undelegateEvents() - this.container = container - - if (this.options.events != null) { - this.delegateEvents(this.options.events) - } - - const attrs = this.getContainerAttrs() - if (attrs != null) { - this.setAttrs(attrs, container) - } - - const style = this.getContainerStyle() - if (style != null) { - this.setStyle(style, container) - } - - const className = this.getContainerClassName() - if (className != null) { - this.addClass(className, container) - } - } - - return this - } - - isNodeView(): this is NodeView { - return false - } - - isEdgeView(): this is EdgeView { - return false - } - - render() { - return this - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - confirmUpdate(flag: number, options: any = {}) { - return 0 - } - - getBootstrapFlag() { - return this.flag.getBootstrapFlag() - } - - getFlag(actions: FlagManager.Actions) { - return this.flag.getFlag(actions) - } - - hasAction(flag: number, actions: FlagManager.Actions) { - return this.flag.hasAction(flag, actions) - } - - removeAction(flag: number, actions: FlagManager.Actions) { - return this.flag.removeAction(flag, actions) - } - - handleAction( - flag: number, - action: FlagManager.Action, - handle: () => void, - additionalRemovedActions?: FlagManager.Actions | null, - ) { - if (this.hasAction(flag, action)) { - handle() - const removedFlags = [action] - if (additionalRemovedActions) { - if (typeof additionalRemovedActions === 'string') { - removedFlags.push(additionalRemovedActions) - } else { - removedFlags.push(...additionalRemovedActions) - } - } - return this.removeAction(flag, removedFlags) - } - return flag - } - - protected setup() { - this.cell.on('changed', ({ options }) => this.onAttrsChange(options)) - } - - protected onAttrsChange(options: Cell.MutateOptions) { - let flag = this.flag.getChangedFlag() - if (options.updated || !flag) { - return - } - - if (options.dirty && this.hasAction(flag, 'update')) { - flag |= this.getFlag('render') // eslint-disable-line no-bitwise - } - - // tool changes should be sync render - if (options.toolId) { - options.async = false - } - - if (this.graph != null) { - this.graph.renderer.requestViewUpdate(this, flag, options) - } - } - - parseJSONMarkup( - markup: Markup.JSONMarkup | Markup.JSONMarkup[], - rootElem?: Element, - ) { - const result = Markup.parseJSONMarkup(markup) - const selectors = result.selectors - const rootSelector = this.rootSelector - if (rootElem && rootSelector) { - if (selectors[rootSelector]) { - throw new Error('Invalid root selector') - } - selectors[rootSelector] = rootElem - } - return result - } - - can(feature: CellView.InteractionNames): boolean { - let interacting = this.graph.options.interacting - - if (typeof interacting === 'function') { - interacting = FunctionExt.call(interacting, this.graph, this) - } - - if (typeof interacting === 'object') { - let val = interacting[feature] - if (typeof val === 'function') { - val = FunctionExt.call(val, this.graph, this) - } - return val !== false - } - - if (typeof interacting === 'boolean') { - return interacting - } - - return false - } - - cleanCache() { - this.cache.clean() - return this - } - - getCache(elem: Element) { - return this.cache.get(elem) - } - - getDataOfElement(elem: Element) { - return this.cache.getData(elem) - } - - getMatrixOfElement(elem: Element) { - return this.cache.getMatrix(elem) - } - - getShapeOfElement(elem: SVGElement) { - return this.cache.getShape(elem) - } - - getBoundingRectOfElement(elem: Element) { - return this.cache.getBoundingRect(elem) - } - - getBBoxOfElement(elem: Element) { - const rect = this.getBoundingRectOfElement(elem) - const matrix = this.getMatrixOfElement(elem) - const rm = this.getRootRotatedMatrix() - const tm = this.getRootTranslatedMatrix() - return Util.transformRectangle(rect, tm.multiply(rm).multiply(matrix)) - } - - getUnrotatedBBoxOfElement(elem: SVGElement) { - const rect = this.getBoundingRectOfElement(elem) - const matrix = this.getMatrixOfElement(elem) - const tm = this.getRootTranslatedMatrix() - return Util.transformRectangle(rect, tm.multiply(matrix)) - } - - getBBox(options: { useCellGeometry?: boolean } = {}) { - let bbox - if (options.useCellGeometry) { - const cell = this.cell - const angle = cell.isNode() ? cell.getAngle() : 0 - bbox = cell.getBBox().bbox(angle) - } else { - bbox = this.getBBoxOfElement(this.container) - } - - return this.graph.coord.localToGraphRect(bbox) - } - - getRootTranslatedMatrix() { - const cell = this.cell - const pos = cell.isNode() ? cell.getPosition() : { x: 0, y: 0 } - return Dom.createSVGMatrix().translate(pos.x, pos.y) - } - - getRootRotatedMatrix() { - let matrix = Dom.createSVGMatrix() - const cell = this.cell - const angle = cell.isNode() ? cell.getAngle() : 0 - if (angle) { - const bbox = cell.getBBox() - const cx = bbox.width / 2 - const cy = bbox.height / 2 - matrix = matrix.translate(cx, cy).rotate(angle).translate(-cx, -cy) - } - return matrix - } - - findMagnet(elem: Element = this.container) { - return this.findByAttr('magnet', elem) - } - - updateAttrs( - rootNode: Element, - attrs: Attr.CellAttrs, - options: Partial = {}, - ) { - if (options.rootBBox == null) { - options.rootBBox = new Rectangle() - } - - if (options.selectors == null) { - options.selectors = this.selectors - } - - this.attr.update(rootNode, attrs, options as AttrManager.UpdateOptions) - } - - isEdgeElement(magnet?: Element | null) { - return this.cell.isEdge() && (magnet == null || magnet === this.container) - } - - // #region highlight - - protected prepareHighlight( - elem?: Element | null, - options: CellView.HighlightOptions = {}, - ) { - const magnet = elem || this.container - options.partial = magnet === this.container - return magnet - } - - highlight(elem?: Element | null, options: CellView.HighlightOptions = {}) { - const magnet = this.prepareHighlight(elem, options) - this.notify('cell:highlight', { - magnet, - options, - view: this, - cell: this.cell, - }) - if (this.isEdgeView()) { - this.notify('edge:highlight', { - magnet, - options, - view: this, - edge: this.cell, - cell: this.cell, - }) - } else if (this.isNodeView()) { - this.notify('node:highlight', { - magnet, - options, - view: this, - node: this.cell, - cell: this.cell, - }) - } - return this - } - - unhighlight(elem?: Element | null, options: CellView.HighlightOptions = {}) { - const magnet = this.prepareHighlight(elem, options) - this.notify('cell:unhighlight', { - magnet, - options, - view: this, - cell: this.cell, - }) - if (this.isNodeView()) { - this.notify('node:unhighlight', { - magnet, - options, - view: this, - node: this.cell, - cell: this.cell, - }) - } else if (this.isEdgeView()) { - this.notify('edge:unhighlight', { - magnet, - options, - view: this, - edge: this.cell, - cell: this.cell, - }) - } - return this - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - notifyUnhighlight(magnet: Element, options: CellView.HighlightOptions) {} - - // #endregion - - getEdgeTerminal( - magnet: Element, - x: number, - y: number, - edge: Edge, - type: Edge.TerminalType, - ) { - const cell = this.cell - const portId = this.findAttr('port', magnet) - const selector = magnet.getAttribute('data-selector') - const terminal: Edge.TerminalCellData = { cell: cell.id } - - if (selector != null) { - terminal.magnet = selector - } - - if (portId != null) { - terminal.port = portId - if (cell.isNode()) { - if (!cell.hasPort(portId) && selector == null) { - // port created via the `port` attribute (not API) - terminal.selector = this.getSelector(magnet) - } - } - } else if (selector == null && this.container !== magnet) { - terminal.selector = this.getSelector(magnet) - } - - return terminal - } - - getMagnetFromEdgeTerminal(terminal: Edge.TerminalData) { - const cell = this.cell - const root = this.container - const portId = (terminal as Edge.TerminalCellData).port - let selector = terminal.magnet - let magnet - if (portId != null && cell.isNode() && cell.hasPort(portId)) { - magnet = (this as any).findPortElem(portId, selector) || root - } else { - if (!selector) { - selector = terminal.selector - } - if (!selector && portId != null) { - selector = `[port="${portId}"]` - } - magnet = this.findOne(selector, root, this.selectors) - } - - return magnet - } - - // #region tools - - protected tools: ToolsView | null - - hasTools(name?: string) { - const tools = this.tools - if (tools == null) { - return false - } - - if (name == null) { - return true - } - - return tools.name === name - } - - addTools(options: ToolsView.Options | null): this - addTools(tools: ToolsView | null): this - addTools(config: ToolsView | ToolsView.Options | null) { - if (!this.can('toolsAddable')) { - return this - } - this.removeTools() - if (config) { - const tools = ToolsView.isToolsView(config) - ? config - : new ToolsView(config) - this.tools = tools - tools.config({ view: this }) - tools.mount() - } - return this - } - - updateTools(options: ToolsView.UpdateOptions = {}) { - if (this.tools) { - this.tools.update(options) - } - return this - } - - removeTools() { - if (this.tools) { - this.tools.remove() - this.tools = null - } - return this - } - - hideTools() { - if (this.tools) { - this.tools.hide() - } - return this - } - - showTools() { - if (this.tools) { - this.tools.show() - } - return this - } - - protected renderTools() { - const tools = this.cell.getTools() - this.addTools(tools as ToolsView.Options) - return this - } - - // #endregion - - // #region events - - notify( - name: Key, - args: CellView.EventArgs[Key], - ): this - notify(name: Exclude, args: any): this - notify( - name: Key, - args: CellView.EventArgs[Key], - ) { - this.trigger(name, args) - this.graph.trigger(name, args) - return this - } - - protected getEventArgs(e: E): CellView.MouseEventArgs - protected getEventArgs( - e: E, - x: number, - y: number, - ): CellView.MousePositionEventArgs - protected getEventArgs(e: E, x?: number, y?: number) { - const view = this // eslint-disable-line @typescript-eslint/no-this-alias - const cell = view.cell - if (x == null || y == null) { - return { e, view, cell } as CellView.MouseEventArgs - } - return { e, x, y, view, cell } as CellView.MousePositionEventArgs - } - - onClick(e: Dom.ClickEvent, x: number, y: number) { - this.notify('cell:click', this.getEventArgs(e, x, y)) - } - - onDblClick(e: Dom.DoubleClickEvent, x: number, y: number) { - this.notify('cell:dblclick', this.getEventArgs(e, x, y)) - } - - onContextMenu(e: Dom.ContextMenuEvent, x: number, y: number) { - this.notify('cell:contextmenu', this.getEventArgs(e, x, y)) - } - - protected cachedModelForMouseEvent: Model | null - - onMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - if (this.cell.model) { - this.cachedModelForMouseEvent = this.cell.model - this.cachedModelForMouseEvent.startBatch('mouse') - } - - this.notify('cell:mousedown', this.getEventArgs(e, x, y)) - } - - onMouseUp(e: Dom.MouseUpEvent, x: number, y: number) { - this.notify('cell:mouseup', this.getEventArgs(e, x, y)) - - if (this.cachedModelForMouseEvent) { - this.cachedModelForMouseEvent.stopBatch('mouse', { cell: this.cell }) - this.cachedModelForMouseEvent = null - } - } - - onMouseMove(e: Dom.MouseMoveEvent, x: number, y: number) { - this.notify('cell:mousemove', this.getEventArgs(e, x, y)) - } - - onMouseOver(e: Dom.MouseOverEvent) { - this.notify('cell:mouseover', this.getEventArgs(e)) - } - - onMouseOut(e: Dom.MouseOutEvent) { - this.notify('cell:mouseout', this.getEventArgs(e)) - } - - onMouseEnter(e: Dom.MouseEnterEvent) { - this.notify('cell:mouseenter', this.getEventArgs(e)) - } - - onMouseLeave(e: Dom.MouseLeaveEvent) { - this.notify('cell:mouseleave', this.getEventArgs(e)) - } - - onMouseWheel(e: Dom.EventObject, x: number, y: number, delta: number) { - this.notify('cell:mousewheel', { - delta, - ...this.getEventArgs(e, x, y), - }) - } - - onCustomEvent(e: Dom.MouseDownEvent, name: string, x: number, y: number) { - this.notify('cell:customevent', { name, ...this.getEventArgs(e, x, y) }) - this.notify(name, { ...this.getEventArgs(e, x, y) }) - } - - onMagnetMouseDown( - e: Dom.MouseDownEvent, - magnet: Element, - x: number, - y: number, - ) {} - - onMagnetDblClick( - e: Dom.DoubleClickEvent, - magnet: Element, - x: number, - y: number, - ) {} - - onMagnetContextMenu( - e: Dom.ContextMenuEvent, - magnet: Element, - x: number, - y: number, - ) {} - - onLabelMouseDown(e: Dom.MouseDownEvent, x: number, y: number) {} - - checkMouseleave(e: Dom.EventObject) { - const target = this.getEventTarget(e, { fromPoint: true }) - const view = this.graph.findViewByElem(target) - if (view === this) { - return - } - - // Leaving the current view - this.onMouseLeave(e as Dom.MouseLeaveEvent) - if (!view) { - return - } - - // Entering another view - view.onMouseEnter(e as Dom.MouseEnterEvent) - } - - // #endregion -} - -export namespace CellView { - export interface Options { - graph: Graph - priority: number - isSvgElement: boolean - rootSelector: string - bootstrap: FlagManager.Actions - actions: KeyValue - events?: View.Events | null - documentEvents?: View.Events | null - } - - type Interactable = boolean | ((this: Graph, cellView: CellView) => boolean) - - interface InteractionMap { - // edge - edgeMovable?: Interactable - edgeLabelMovable?: Interactable - arrowheadMovable?: Interactable - vertexMovable?: Interactable - vertexAddable?: Interactable - vertexDeletable?: Interactable - useEdgeTools?: Interactable - - // node - nodeMovable?: Interactable - magnetConnectable?: Interactable - stopDelegateOnDragging?: Interactable - - // general - toolsAddable?: Interactable - } - - export type InteractionNames = keyof InteractionMap - - export type Interacting = - | boolean - | InteractionMap - | ((this: Graph, cellView: CellView) => InteractionMap | boolean) - - export interface HighlightOptions { - highlighter?: - | string - | { - name: string - args: KeyValue - } - - type?: 'embedding' | 'nodeAvailable' | 'magnetAvailable' | 'magnetAdsorbed' - - partial?: boolean - } -} - -export namespace CellView { - export interface PositionEventArgs { - x: number - y: number - } - - export interface MouseDeltaEventArgs { - delta: number - } - - export interface MouseEventArgs { - e: E - view: CellView - cell: Cell - } - - export interface MousePositionEventArgs - extends MouseEventArgs, - PositionEventArgs {} - - export interface EventArgs extends NodeView.EventArgs, EdgeView.EventArgs { - 'cell:click': MousePositionEventArgs - 'cell:dblclick': MousePositionEventArgs - 'cell:contextmenu': MousePositionEventArgs - 'cell:mousedown': MousePositionEventArgs - 'cell:mousemove': MousePositionEventArgs - 'cell:mouseup': MousePositionEventArgs - 'cell:mouseover': MouseEventArgs - 'cell:mouseout': MouseEventArgs - 'cell:mouseenter': MouseEventArgs - 'cell:mouseleave': MouseEventArgs - 'cell:mousewheel': MousePositionEventArgs & - MouseDeltaEventArgs - 'cell:customevent': MousePositionEventArgs & { - name: string - } - 'cell:highlight': { - magnet: Element - view: CellView - cell: Cell - options: CellView.HighlightOptions - } - 'cell:unhighlight': EventArgs['cell:highlight'] - } -} - -export namespace CellView { - export const Flag = FlagManager - export const Attr = AttrManager -} - -export namespace CellView { - export const toStringTag = `X6.${CellView.name}` - - export function isCellView(instance: any): instance is CellView { - if (instance == null) { - return false - } - - if (instance instanceof CellView) { - return true - } - - const tag = instance[Symbol.toStringTag] - const view = instance as CellView - - if ( - (tag == null || tag === toStringTag) && - typeof view.isNodeView === 'function' && - typeof view.isEdgeView === 'function' && - typeof view.confirmUpdate === 'function' - ) { - return true - } - - return false - } -} - -// decorators -// ---- -export namespace CellView { - export function priority(value: number) { - return function (ctor: Definition) { - ctor.config({ priority: value }) - } - } - - export function bootstrap(actions: FlagManager.Actions) { - return function (ctor: Definition) { - ctor.config({ bootstrap: actions }) - } - } -} - -export namespace CellView { - type CellViewClass = typeof CellView - - export interface Definition extends CellViewClass { - new < - Entity extends Cell = Cell, - Options extends CellView.Options = CellView.Options, - >( - cell: Entity, - options: Partial, - ): CellView - } - - export const registry = Registry.create({ - type: 'view', - }) -} diff --git a/packages/x6-next/src/view/edge.ts b/packages/x6-next/src/view/edge.ts deleted file mode 100644 index 3dc7ee4cd08..00000000000 --- a/packages/x6-next/src/view/edge.ts +++ /dev/null @@ -1,2516 +0,0 @@ -import { - Rectangle, - Polyline, - Point, - Angle, - Path, - Line, -} from '@antv/x6-geometry' -import { - ObjectExt, - NumberExt, - FunctionExt, - Dom, - Vector, - KeyValue, -} from '@antv/x6-common' -import { - Router, - Connector, - NodeAnchor, - EdgeAnchor, - ConnectionPoint, -} from '../registry' -import { Cell } from '../model/cell' -import { Edge } from '../model/edge' -import { Markup } from './markup' -import { CellView } from './cell' -import { NodeView } from './node' -import { ToolsView } from './tool' -import { Graph } from '../graph' -import { Options as GraphOptions } from '../graph/options' - -export class EdgeView< - Entity extends Edge = Edge, - Options extends EdgeView.Options = EdgeView.Options, -> extends CellView { - protected readonly POINT_ROUNDING = 2 - public path: Path - public routePoints: Point[] - public sourceAnchor: Point - public targetAnchor: Point - public sourcePoint: Point - public targetPoint: Point - public sourceMarkerPoint: Point - public targetMarkerPoint: Point - public sourceView: CellView | null - public targetView: CellView | null - public sourceMagnet: Element | null - public targetMagnet: Element | null - - protected labelContainer: Element | null - protected labelCache: { [index: number]: Element } - protected labelSelectors: { [index: number]: Markup.Selectors } - - protected get [Symbol.toStringTag]() { - return EdgeView.toStringTag - } - - protected getContainerClassName() { - return [super.getContainerClassName(), this.prefixClassName('edge')].join( - ' ', - ) - } - - get sourceBBox() { - const sourceView = this.sourceView - if (!sourceView) { - const sourceDef = this.cell.getSource() as Edge.TerminalPointData - return new Rectangle(sourceDef.x, sourceDef.y) - } - const sourceMagnet = this.sourceMagnet - if (sourceView.isEdgeElement(sourceMagnet)) { - return new Rectangle(this.sourceAnchor.x, this.sourceAnchor.y) - } - return sourceView.getBBoxOfElement(sourceMagnet || sourceView.container) - } - - get targetBBox() { - const targetView = this.targetView - if (!targetView) { - const targetDef = this.cell.getTarget() as Edge.TerminalPointData - return new Rectangle(targetDef.x, targetDef.y) - } - const targetMagnet = this.targetMagnet - if (targetView.isEdgeElement(targetMagnet)) { - return new Rectangle(this.targetAnchor.x, this.targetAnchor.y) - } - return targetView.getBBoxOfElement(targetMagnet || targetView.container) - } - - isEdgeView(): this is EdgeView { - return true - } - - confirmUpdate(flag: number, options: any = {}) { - let ref = flag - if (this.hasAction(ref, 'source')) { - if (!this.updateTerminalProperties('source')) { - return ref - } - ref = this.removeAction(ref, 'source') - } - - if (this.hasAction(ref, 'target')) { - if (!this.updateTerminalProperties('target')) { - return ref - } - ref = this.removeAction(ref, 'target') - } - - const graph = this.graph - const sourceView = this.sourceView - const targetView = this.targetView - - if ( - graph && - ((sourceView && !graph.renderer.isViewMounted(sourceView)) || - (targetView && !graph.renderer.isViewMounted(targetView))) - ) { - // Wait for the sourceView and targetView to be rendered. - return ref - } - - if (this.hasAction(ref, 'render')) { - this.render() - ref = this.removeAction(ref, ['render', 'update', 'labels', 'tools']) - return ref - } - ref = this.handleAction(ref, 'update', () => this.update(options)) - ref = this.handleAction(ref, 'labels', () => this.onLabelsChange(options)) - ref = this.handleAction(ref, 'tools', () => this.renderTools()) - - return ref - } - - // #region render - render() { - this.empty() - - this.renderMarkup() - - this.labelContainer = null - this.renderLabels() - - this.update() - this.renderTools() - - return this - } - - protected renderMarkup() { - const markup = this.cell.markup - if (markup) { - if (typeof markup === 'string') { - throw new TypeError('Not support string markup.') - } - return this.renderJSONMarkup(markup) - } - throw new TypeError('Invalid edge markup.') - } - - protected renderJSONMarkup(markup: Markup.JSONMarkup | Markup.JSONMarkup[]) { - const ret = this.parseJSONMarkup(markup, this.container) - this.selectors = ret.selectors - this.container.append(ret.fragment) - } - - // protected customizeLabels() { - // if (this.containers.labels) { - // const edge = this.cell - // const labels = edge.labels - // for (let i = 0, n = labels.length; i < n; i += 1) { - // const label = labels[i] - // const container = this.labelCache[i] - // const selectors = this.labelSelectors[i] - // this.graph.hook.onEdgeLabelRendered({ - // edge, - // label, - // container, - // selectors, - // }) - // } - // } - // } - - protected renderLabels() { - const edge = this.cell - const labels = edge.getLabels() - const count = labels.length - let container = this.labelContainer - - this.labelCache = {} - this.labelSelectors = {} - - if (count <= 0) { - if (container && container.parentNode) { - container.parentNode.removeChild(container) - } - return this - } - - if (container) { - this.empty(container) - } else { - container = Dom.createSvgElement('g') - this.addClass(this.prefixClassName('edge-labels'), container) - this.labelContainer = container - } - - for (let i = 0, ii = labels.length; i < ii; i += 1) { - const label = labels[i] - const normalized = this.normalizeLabelMarkup( - this.parseLabelMarkup(label.markup), - ) - let labelNode - let selectors - if (normalized) { - labelNode = normalized.node - selectors = normalized.selectors - } else { - const defaultLabel = edge.getDefaultLabel() - const normalized = this.normalizeLabelMarkup( - this.parseLabelMarkup(defaultLabel.markup), - )! - - labelNode = normalized.node - selectors = normalized.selectors - } - - labelNode.setAttribute('data-index', `${i}`) - container.appendChild(labelNode) - - const rootSelector = this.rootSelector - if (selectors[rootSelector]) { - throw new Error('Ambiguous label root selector.') - } - selectors[rootSelector] = labelNode - - this.labelCache[i] = labelNode - this.labelSelectors[i] = selectors - } - - if (container.parentNode == null) { - this.container.appendChild(container) - } - - this.updateLabels() - // todo - // this.customizeLabels() - - return this - } - - onLabelsChange(options: any = {}) { - if (this.shouldRerenderLabels(options)) { - this.renderLabels() - } else { - this.updateLabels() - } - - this.updateLabelPositions() - } - - protected shouldRerenderLabels(options: any = {}) { - const previousLabels = this.cell.previous('labels') - if (previousLabels == null) { - return true - } - - // Here is an optimization for cases when we know, that change does - // not require re-rendering of all labels. - if ('propertyPathArray' in options && 'propertyValue' in options) { - // The label is setting by `prop()` method - const pathArray = options.propertyPathArray || [] - const pathLength = pathArray.length - if (pathLength > 1) { - // We are changing a single label here e.g. 'labels/0/position' - const index = pathArray[1] - if (previousLabels[index]) { - if (pathLength === 2) { - // We are changing the entire label. Need to check if the - // markup is also being changed. - return ( - typeof options.propertyValue === 'object' && - ObjectExt.has(options.propertyValue, 'markup') - ) - } - - // We are changing a label property but not the markup - if (pathArray[2] !== 'markup') { - return false - } - } - } - } - - return true - } - - protected parseLabelMarkup(markup?: Markup) { - if (markup) { - if (typeof markup === 'string') { - return this.parseLabelStringMarkup(markup) - } - return this.parseJSONMarkup(markup) - } - - return null - } - - protected parseLabelStringMarkup(labelMarkup: string) { - const children = Vector.createVectors(labelMarkup) - const fragment = document.createDocumentFragment() - for (let i = 0, n = children.length; i < n; i += 1) { - const currentChild = children[i].node - fragment.appendChild(currentChild) - } - - return { fragment, selectors: {} } - } - - protected normalizeLabelMarkup( - markup?: { - fragment: DocumentFragment - selectors: Markup.Selectors - } | null, - ) { - if (markup == null) { - return - } - - const fragment = markup.fragment - if (!(fragment instanceof DocumentFragment) || !fragment.hasChildNodes()) { - throw new Error('Invalid label markup.') - } - - let vel - const childNodes = fragment.childNodes - if (childNodes.length > 1 || childNodes[0].nodeName.toUpperCase() !== 'G') { - vel = Vector.create('g').append(fragment) - } else { - vel = Vector.create(childNodes[0] as SVGElement) - } - - vel.addClass(this.prefixClassName('edge-label')) - - return { - node: vel.node, - selectors: markup.selectors, - } - } - - protected updateLabels() { - if (this.labelContainer) { - const edge = this.cell - const labels = edge.labels - const canLabelMove = this.can('edgeLabelMovable') - const defaultLabel = edge.getDefaultLabel() - - for (let i = 0, n = labels.length; i < n; i += 1) { - const elem = this.labelCache[i] - const selectors = this.labelSelectors[i] - - elem.setAttribute('cursor', canLabelMove ? 'move' : 'default') - - const label = labels[i] - const attrs = ObjectExt.merge({}, defaultLabel.attrs, label.attrs) - this.updateAttrs(elem, attrs, { - selectors, - rootBBox: label.size ? Rectangle.fromSize(label.size) : undefined, - }) - } - } - } - - protected renderTools() { - const tools = this.cell.getTools() - this.addTools(tools as ToolsView.Options) - return this - } - - // #endregion - - // #region updating - - update(options: any = {}) { - this.cleanCache() - this.updateConnection(options) - - const attrs = this.cell.getAttrs() - if (attrs != null) { - this.updateAttrs(this.container, attrs, { - selectors: this.selectors, - }) - } - - this.updateLabelPositions() - this.updateTools(options) - - return this - } - - removeRedundantLinearVertices(options: Edge.SetOptions = {}) { - const edge = this.cell - const vertices = edge.getVertices() - const routePoints = [this.sourceAnchor, ...vertices, this.targetAnchor] - const rawCount = routePoints.length - - // Puts the route points into a polyline and try to simplify. - const polyline = new Polyline(routePoints) - polyline.simplify({ threshold: 0.01 }) - const simplifiedPoints = polyline.points.map((point) => point.toJSON()) - const simplifiedCount = simplifiedPoints.length - - // If simplification did not remove any redundant vertices. - if (rawCount === simplifiedCount) { - return 0 - } - - // Sets simplified polyline points as edge vertices. - // Removes first and last polyline points again (source/target anchors). - edge.setVertices(simplifiedPoints.slice(1, simplifiedCount - 1), options) - return rawCount - simplifiedCount - } - - getTerminalView(type: Edge.TerminalType) { - switch (type) { - case 'source': - return this.sourceView || null - case 'target': - return this.targetView || null - default: - throw new Error(`Unknown terminal type '${type}'`) - } - } - - getTerminalAnchor(type: Edge.TerminalType) { - switch (type) { - case 'source': - return Point.create(this.sourceAnchor) - case 'target': - return Point.create(this.targetAnchor) - default: - throw new Error(`Unknown terminal type '${type}'`) - } - } - - getTerminalConnectionPoint(type: Edge.TerminalType) { - switch (type) { - case 'source': - return Point.create(this.sourcePoint) - case 'target': - return Point.create(this.targetPoint) - default: - throw new Error(`Unknown terminal type '${type}'`) - } - } - - getTerminalMagnet(type: Edge.TerminalType, options: { raw?: boolean } = {}) { - switch (type) { - case 'source': { - if (options.raw) { - return this.sourceMagnet - } - const sourceView = this.sourceView - if (!sourceView) { - return null - } - return this.sourceMagnet || sourceView.container - } - case 'target': { - if (options.raw) { - return this.targetMagnet - } - const targetView = this.targetView - if (!targetView) { - return null - } - return this.targetMagnet || targetView.container - } - default: { - throw new Error(`Unknown terminal type '${type}'`) - } - } - } - - updateConnection(options: any = {}) { - const edge = this.cell - - // The edge is being translated by an ancestor that will shift - // source, target and vertices by an equal distance. - // todo isFragmentDescendantOf is invalid - if ( - options.translateBy && - edge.isFragmentDescendantOf(options.translateBy) - ) { - const tx = options.tx || 0 - const ty = options.ty || 0 - this.routePoints = new Polyline(this.routePoints).translate(tx, ty).points - this.translateConnectionPoints(tx, ty) - this.path.translate(tx, ty) - } else { - const vertices = edge.getVertices() - - // 1. Find anchor points - const anchors = this.findAnchors(vertices) - this.sourceAnchor = anchors.source - this.targetAnchor = anchors.target - - // 2. Find route points - this.routePoints = this.findRoutePoints(vertices) - - // 3. Find connection points - const connectionPoints = this.findConnectionPoints( - this.routePoints, - this.sourceAnchor, - this.targetAnchor, - ) - this.sourcePoint = connectionPoints.source - this.targetPoint = connectionPoints.target - - // 4. Find Marker Connection Point - const markerPoints = this.findMarkerPoints( - this.routePoints, - this.sourcePoint, - this.targetPoint, - ) - - // 5. Make path - this.path = this.findPath( - this.routePoints, - markerPoints.source || this.sourcePoint, - markerPoints.target || this.targetPoint, - ) - } - - this.cleanCache() - } - - protected findAnchors(vertices: Point.PointLike[]) { - const edge = this.cell - const source = edge.source as Edge.TerminalCellData - const target = edge.target as Edge.TerminalCellData - const firstVertex = vertices[0] - const lastVertex = vertices[vertices.length - 1] - - if (target.priority && !source.priority) { - // Reversed order - return this.findAnchorsOrdered( - 'target', - lastVertex, - 'source', - firstVertex, - ) - } - - // Usual order - return this.findAnchorsOrdered('source', firstVertex, 'target', lastVertex) - } - - protected findAnchorsOrdered( - firstType: Edge.TerminalType, - firstPoint: Point.PointLike, - secondType: Edge.TerminalType, - secondPoint: Point.PointLike, - ) { - let firstAnchor: Point - let secondAnchor: Point - - const edge = this.cell - const firstTerminal = edge[firstType] - const secondTerminal = edge[secondType] - const firstView = this.getTerminalView(firstType) - const secondView = this.getTerminalView(secondType) - const firstMagnet = this.getTerminalMagnet(firstType) - const secondMagnet = this.getTerminalMagnet(secondType) - - if (firstView) { - let firstRef - if (firstPoint) { - firstRef = Point.create(firstPoint) - } else if (secondView) { - firstRef = secondMagnet - } else { - firstRef = Point.create(secondTerminal as Edge.TerminalPointData) - } - - firstAnchor = this.getAnchor( - (firstTerminal as Edge.SetCellTerminalArgs).anchor, - firstView, - firstMagnet, - firstRef, - firstType, - ) - } else { - firstAnchor = Point.create(firstTerminal as Edge.TerminalPointData) - } - - if (secondView) { - const secondRef = Point.create(secondPoint || firstAnchor) - secondAnchor = this.getAnchor( - (secondTerminal as Edge.SetCellTerminalArgs).anchor, - secondView, - secondMagnet, - secondRef, - secondType, - ) - } else { - secondAnchor = Point.isPointLike(secondTerminal) - ? Point.create(secondTerminal) - : new Point() - } - - return { - [firstType]: firstAnchor, - [secondType]: secondAnchor, - } - } - - protected getAnchor( - def: NodeAnchor.ManaualItem | string | undefined, - cellView: CellView, - magnet: Element | null, - ref: Point | Element | null, - terminalType: Edge.TerminalType, - ): Point { - const isEdge = cellView.isEdgeElement(magnet) - const connecting = this.graph.options.connecting - let config = typeof def === 'string' ? { name: def } : def - if (!config) { - const defaults = isEdge - ? (terminalType === 'source' - ? connecting.sourceEdgeAnchor - : connecting.targetEdgeAnchor) || connecting.edgeAnchor - : (terminalType === 'source' - ? connecting.sourceAnchor - : connecting.targetAnchor) || connecting.anchor - - config = typeof defaults === 'string' ? { name: defaults } : defaults - } - - if (!config) { - throw new Error(`Anchor should be specified.`) - } - - let anchor - - const name = config.name - if (isEdge) { - const fn = EdgeAnchor.registry.get(name) - if (typeof fn !== 'function') { - return EdgeAnchor.registry.onNotFound(name) - } - anchor = FunctionExt.call( - fn, - this, - cellView as EdgeView, - magnet as SVGElement, - ref as Point.PointLike, - config.args || {}, - terminalType, - ) - } else { - const fn = NodeAnchor.registry.get(name) - if (typeof fn !== 'function') { - return NodeAnchor.registry.onNotFound(name) - } - - anchor = FunctionExt.call( - fn, - this, - cellView as NodeView, - magnet as SVGElement, - ref as Point.PointLike, - config.args || {}, - terminalType, - ) - } - - return anchor ? anchor.round(this.POINT_ROUNDING) : new Point() - } - - protected findRoutePoints(vertices: Point.PointLike[] = []): Point[] { - const defaultRouter = - this.graph.options.connecting.router || Router.presets.normal - const router = this.cell.getRouter() || defaultRouter - let routePoints - - if (typeof router === 'function') { - routePoints = FunctionExt.call( - router as Router.Definition, - this, - vertices, - {}, - this, - ) - } else { - const name = typeof router === 'string' ? router : router.name - const args = typeof router === 'string' ? {} : router.args || {} - const fn = name ? Router.registry.get(name) : Router.presets.normal - if (typeof fn !== 'function') { - return Router.registry.onNotFound(name!) - } - - routePoints = FunctionExt.call(fn, this, vertices, args, this) - } - - return routePoints == null - ? vertices.map((p) => Point.create(p)) - : routePoints.map((p) => Point.create(p)) - } - - protected findConnectionPoints( - routePoints: Point[], - sourceAnchor: Point, - targetAnchor: Point, - ) { - const edge = this.cell - const connecting = this.graph.options.connecting - const sourceTerminal = edge.getSource() - const targetTerminal = edge.getTarget() - const sourceView = this.sourceView - const targetView = this.targetView - const firstRoutePoint = routePoints[0] - const lastRoutePoint = routePoints[routePoints.length - 1] - - // source - let sourcePoint - if (sourceView && !sourceView.isEdgeElement(this.sourceMagnet)) { - const sourceMagnet = this.sourceMagnet || sourceView.container - const sourcePointRef = firstRoutePoint || targetAnchor - const sourceLine = new Line(sourcePointRef, sourceAnchor) - const connectionPointDef = - sourceTerminal.connectionPoint || - connecting.sourceConnectionPoint || - connecting.connectionPoint - sourcePoint = this.getConnectionPoint( - connectionPointDef, - sourceView, - sourceMagnet, - sourceLine, - 'source', - ) - } else { - sourcePoint = sourceAnchor - } - - // target - let targetPoint - if (targetView && !targetView.isEdgeElement(this.targetMagnet)) { - const targetMagnet = this.targetMagnet || targetView.container - const targetConnectionPointDef = - targetTerminal.connectionPoint || - connecting.targetConnectionPoint || - connecting.connectionPoint - const targetPointRef = lastRoutePoint || sourceAnchor - const targetLine = new Line(targetPointRef, targetAnchor) - targetPoint = this.getConnectionPoint( - targetConnectionPointDef, - targetView, - targetMagnet, - targetLine, - 'target', - ) - } else { - targetPoint = targetAnchor - } - - return { - source: sourcePoint, - target: targetPoint, - } - } - - protected getConnectionPoint( - def: string | ConnectionPoint.ManaualItem | undefined, - view: CellView, - magnet: Element, - line: Line, - endType: Edge.TerminalType, - ) { - const anchor = line.end - if (def == null) { - return anchor - } - - const name = typeof def === 'string' ? def : def.name - const args = typeof def === 'string' ? {} : def.args - const fn = ConnectionPoint.registry.get(name) - if (typeof fn !== 'function') { - return ConnectionPoint.registry.onNotFound(name) - } - - const connectionPoint = FunctionExt.call( - fn, - this, - line, - view, - magnet as SVGElement, - args || {}, - endType, - ) - - return connectionPoint ? connectionPoint.round(this.POINT_ROUNDING) : anchor - } - - protected findMarkerPoints( - routePoints: Point[], - sourcePoint: Point, - targetPoint: Point, - ) { - const getLineWidth = (type: Edge.TerminalType) => { - const attrs = this.cell.getAttrs() - const keys = Object.keys(attrs) - for (let i = 0, l = keys.length; i < l; i += 1) { - const attr = attrs[keys[i]] - if (attr[`${type}Marker`] || attr[`${type}-marker`]) { - const strokeWidth = - (attr.strokeWidth as string) || (attr['stroke-width'] as string) - if (strokeWidth) { - return parseFloat(strokeWidth) - } - break - } - } - return null - } - - const firstRoutePoint = routePoints[0] - const lastRoutePoint = routePoints[routePoints.length - 1] - let sourceMarkerPoint - let targetMarkerPoint - - const sourceStrokeWidth = getLineWidth('source') - if (sourceStrokeWidth) { - sourceMarkerPoint = sourcePoint - .clone() - .move(firstRoutePoint || targetPoint, -sourceStrokeWidth) - } - - const targetStrokeWidth = getLineWidth('target') - if (targetStrokeWidth) { - targetMarkerPoint = targetPoint - .clone() - .move(lastRoutePoint || sourcePoint, -targetStrokeWidth) - } - - this.sourceMarkerPoint = sourceMarkerPoint || sourcePoint.clone() - this.targetMarkerPoint = targetMarkerPoint || targetPoint.clone() - - return { - source: sourceMarkerPoint, - target: targetMarkerPoint, - } - } - - protected findPath( - routePoints: Point[], - sourcePoint: Point, - targetPoint: Point, - ): Path { - const def = - this.cell.getConnector() || this.graph.options.connecting.connector - - let name: string | undefined - let args: Connector.BaseOptions | undefined - let fn: Connector.Definition - - if (typeof def === 'string') { - name = def - } else { - name = def.name - args = def.args - } - - if (name) { - const method = Connector.registry.get(name) - if (typeof method !== 'function') { - return Connector.registry.onNotFound(name) - } - fn = method - } else { - fn = Connector.presets.normal - } - - const path = FunctionExt.call( - fn, - this, - sourcePoint, - targetPoint, - routePoints, - { ...args, raw: true }, - this, - ) - - return typeof path === 'string' ? Path.parse(path) : path - } - - protected translateConnectionPoints(tx: number, ty: number) { - this.sourcePoint.translate(tx, ty) - this.targetPoint.translate(tx, ty) - this.sourceAnchor.translate(tx, ty) - this.targetAnchor.translate(tx, ty) - this.sourceMarkerPoint.translate(tx, ty) - this.targetMarkerPoint.translate(tx, ty) - } - - updateLabelPositions() { - if (this.labelContainer == null) { - return this - } - - const path = this.path - if (!path) { - return this - } - - const edge = this.cell - const labels = edge.getLabels() - if (labels.length === 0) { - return this - } - - const defaultLabel = edge.getDefaultLabel() - const defaultPosition = this.normalizeLabelPosition( - defaultLabel.position as Edge.LabelPosition, - ) - - for (let i = 0, ii = labels.length; i < ii; i += 1) { - const label = labels[i] - const labelPosition = this.normalizeLabelPosition( - label.position as Edge.LabelPosition, - ) - const pos = ObjectExt.merge({}, defaultPosition, labelPosition) - const matrix = this.getLabelTransformationMatrix(pos) - this.labelCache[i].setAttribute( - 'transform', - Dom.matrixToTransformString(matrix), - ) - } - - return this - } - - updateTerminalProperties(type: Edge.TerminalType) { - const edge = this.cell - const graph = this.graph - const terminal = edge[type] - const nodeId = terminal && (terminal as Edge.TerminalCellData).cell - const viewKey = `${type}View` as 'sourceView' | 'targetView' - - // terminal is a point - if (!nodeId) { - this[viewKey] = null - this.updateTerminalMagnet(type) - return true - } - - const terminalCell = graph.getCellById(nodeId) - if (!terminalCell) { - throw new Error(`Edge's ${type} node with id "${nodeId}" not exists`) - } - - const endView = terminalCell.findView(graph) - if (!endView) { - return false - } - - this[viewKey] = endView - this.updateTerminalMagnet(type) - return true - } - - updateTerminalMagnet(type: Edge.TerminalType) { - const propName = `${type}Magnet` as 'sourceMagnet' | 'targetMagnet' - const terminalView = this.getTerminalView(type) - if (terminalView) { - let magnet = terminalView.getMagnetFromEdgeTerminal(this.cell[type]) - if (magnet === terminalView.container) { - magnet = null - } - - this[propName] = magnet - } else { - this[propName] = null - } - } - - protected getLabelPositionAngle(idx: number) { - const label = this.cell.getLabelAt(idx) - if (label && label.position && typeof label.position === 'object') { - return label.position.angle || 0 - } - return 0 - } - - protected getLabelPositionArgs(idx: number) { - const label = this.cell.getLabelAt(idx) - if (label && label.position && typeof label.position === 'object') { - return label.position.options - } - } - - protected getDefaultLabelPositionArgs() { - const defaultLabel = this.cell.getDefaultLabel() - if ( - defaultLabel && - defaultLabel.position && - typeof defaultLabel.position === 'object' - ) { - return defaultLabel.position.options - } - } - - protected mergeLabelPositionArgs( - labelPositionArgs?: Edge.LabelPositionOptions, - defaultLabelPositionArgs?: Edge.LabelPositionOptions, - ) { - if (labelPositionArgs === null) { - return null - } - if (labelPositionArgs === undefined) { - if (defaultLabelPositionArgs === null) { - return null - } - return defaultLabelPositionArgs - } - - return ObjectExt.merge({}, defaultLabelPositionArgs, labelPositionArgs) - } - - // #endregion - - getConnection() { - return this.path != null ? this.path.clone() : null - } - - getConnectionPathData() { - if (this.path == null) { - return '' - } - - const cache = this.cache.pathCache - if (!ObjectExt.has(cache, 'data')) { - cache.data = this.path.serialize() - } - return cache.data || '' - } - - getConnectionSubdivisions() { - if (this.path == null) { - return null - } - - const cache = this.cache.pathCache - if (!ObjectExt.has(cache, 'segmentSubdivisions')) { - cache.segmentSubdivisions = this.path.getSegmentSubdivisions() - } - return cache.segmentSubdivisions - } - - getConnectionLength() { - if (this.path == null) { - return 0 - } - - const cache = this.cache.pathCache - if (!ObjectExt.has(cache, 'length')) { - cache.length = this.path.length({ - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - return cache.length - } - - getPointAtLength(length: number) { - if (this.path == null) { - return null - } - - return this.path.pointAtLength(length, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getPointAtRatio(ratio: number) { - if (this.path == null) { - return null - } - - if (NumberExt.isPercentage(ratio)) { - // eslint-disable-next-line - ratio = parseFloat(ratio) / 100 - } - - return this.path.pointAt(ratio, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getTangentAtLength(length: number) { - if (this.path == null) { - return null - } - - return this.path.tangentAtLength(length, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getTangentAtRatio(ratio: number) { - if (this.path == null) { - return null - } - - return this.path.tangentAt(ratio, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getClosestPoint(point: Point.PointLike) { - if (this.path == null) { - return null - } - - return this.path.closestPoint(point, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getClosestPointLength(point: Point.PointLike) { - if (this.path == null) { - return null - } - - return this.path.closestPointLength(point, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getClosestPointRatio(point: Point.PointLike) { - if (this.path == null) { - return null - } - - return this.path.closestPointNormalizedLength(point, { - segmentSubdivisions: this.getConnectionSubdivisions(), - }) - } - - getLabelPosition( - x: number, - y: number, - options?: Edge.LabelPositionOptions | null, - ): Edge.LabelPositionObject - getLabelPosition( - x: number, - y: number, - angle: number, - options?: Edge.LabelPositionOptions | null, - ): Edge.LabelPositionObject - getLabelPosition( - x: number, - y: number, - p3?: number | Edge.LabelPositionOptions | null, - p4?: Edge.LabelPositionOptions | null, - ): Edge.LabelPositionObject { - const pos: Edge.LabelPositionObject = { distance: 0 } - - // normalize data from the two possible signatures - let angle = 0 - let options - if (typeof p3 === 'number') { - angle = p3 - options = p4 - } else { - options = p3 - } - - if (options != null) { - pos.options = options - } - - // identify distance/offset settings - const isOffsetAbsolute = options && options.absoluteOffset - const isDistanceRelative = !(options && options.absoluteDistance) - const isDistanceAbsoluteReverse = - options && options.absoluteDistance && options.reverseDistance - - // find closest point t - const path = this.path - const pathOptions = { - segmentSubdivisions: this.getConnectionSubdivisions(), - } - - const labelPoint = new Point(x, y) - const t = path.closestPointT(labelPoint, pathOptions)! - - // distance - const totalLength = this.getConnectionLength() || 0 - let labelDistance = path.lengthAtT(t, pathOptions) - if (isDistanceRelative) { - labelDistance = totalLength > 0 ? labelDistance / totalLength : 0 - } - - if (isDistanceAbsoluteReverse) { - // fix for end point (-0 => 1) - labelDistance = -1 * (totalLength - labelDistance) || 1 - } - pos.distance = labelDistance - - // offset - // use absolute offset if: - // - options.absoluteOffset is true, - // - options.absoluteOffset is not true but there is no tangent - let tangent - if (!isOffsetAbsolute) tangent = path.tangentAtT(t) - let labelOffset - if (tangent) { - labelOffset = tangent.pointOffset(labelPoint) - } else { - const closestPoint = path.pointAtT(t)! - const labelOffsetDiff = labelPoint.diff(closestPoint) - labelOffset = { x: labelOffsetDiff.x, y: labelOffsetDiff.y } - } - - pos.offset = labelOffset - pos.angle = angle - - return pos - } - - protected normalizeLabelPosition(): undefined - protected normalizeLabelPosition( - pos: Edge.LabelPosition, - ): Edge.LabelPositionObject - protected normalizeLabelPosition( - pos?: Edge.LabelPosition, - ): Edge.LabelPositionObject | undefined { - if (typeof pos === 'number') { - return { distance: pos } - } - - return pos - } - - protected getLabelTransformationMatrix(labelPosition: Edge.LabelPosition) { - const pos = this.normalizeLabelPosition(labelPosition) - const options = pos.options || {} - const labelAngle = pos.angle || 0 - const labelDistance = pos.distance - const isDistanceRelative = labelDistance > 0 && labelDistance <= 1 - - let labelOffset = 0 - const offsetCoord = { x: 0, y: 0 } - const offset = pos.offset - if (offset) { - if (typeof offset === 'number') { - labelOffset = offset - } else { - if (offset.x != null) { - offsetCoord.x = offset.x - } - if (offset.y != null) { - offsetCoord.y = offset.y - } - } - } - - const isOffsetAbsolute = - offsetCoord.x !== 0 || offsetCoord.y !== 0 || labelOffset === 0 - - const isKeepGradient = options.keepGradient - const isEnsureLegibility = options.ensureLegibility - - const path = this.path - const pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() } - - const distance = isDistanceRelative - ? labelDistance * this.getConnectionLength()! - : labelDistance - const tangent = path.tangentAtLength(distance, pathOpt) - - let translation - let angle = labelAngle - if (tangent) { - if (isOffsetAbsolute) { - translation = tangent.start - translation.translate(offsetCoord) - } else { - const normal = tangent.clone() - normal.rotate(-90, tangent.start) - normal.setLength(labelOffset) - translation = normal.end - } - if (isKeepGradient) { - angle = tangent.angle() + labelAngle - if (isEnsureLegibility) { - angle = Angle.normalize(((angle + 90) % 180) - 90) - } - } - } else { - // fallback - the connection has zero length - translation = path.start! - if (isOffsetAbsolute) { - translation.translate(offsetCoord) - } - } - - return Dom.createSVGMatrix() - .translate(translation.x, translation.y) - .rotate(angle) - } - - getVertexIndex(x: number, y: number) { - const edge = this.cell - const vertices = edge.getVertices() - const vertexLength = this.getClosestPointLength(new Point(x, y)) - - let index = 0 - - if (vertexLength != null) { - for (const ii = vertices.length; index < ii; index += 1) { - const currentVertex = vertices[index] - const currentLength = this.getClosestPointLength(currentVertex) - if (currentLength != null && vertexLength < currentLength) { - break - } - } - } - - return index - } - - // #region events - - protected getEventArgs(e: E): EdgeView.MouseEventArgs - protected getEventArgs( - e: E, - x: number, - y: number, - ): EdgeView.PositionEventArgs - protected getEventArgs(e: E, x?: number, y?: number) { - const view = this // eslint-disable-line - const edge = view.cell - const cell = edge - if (x == null || y == null) { - return { e, view, edge, cell } as EdgeView.MouseEventArgs - } - return { e, x, y, view, edge, cell } as EdgeView.PositionEventArgs - } - - protected notifyUnhandledMouseDown( - e: Dom.MouseDownEvent, - x: number, - y: number, - ) { - this.notify('edge:unhandled:mousedown', { - e, - x, - y, - view: this, - cell: this.cell, - edge: this.cell, - }) - } - - notifyMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - super.onMouseDown(e, x, y) - this.notify('edge:mousedown', this.getEventArgs(e, x, y)) - } - - notifyMouseMove(e: Dom.MouseMoveEvent, x: number, y: number) { - super.onMouseMove(e, x, y) - this.notify('edge:mousemove', this.getEventArgs(e, x, y)) - } - - notifyMouseUp(e: Dom.MouseUpEvent, x: number, y: number) { - super.onMouseUp(e, x, y) - this.notify('edge:mouseup', this.getEventArgs(e, x, y)) - } - - onClick(e: Dom.ClickEvent, x: number, y: number) { - super.onClick(e, x, y) - this.notify('edge:click', this.getEventArgs(e, x, y)) - } - - onDblClick(e: Dom.DoubleClickEvent, x: number, y: number) { - super.onDblClick(e, x, y) - this.notify('edge:dblclick', this.getEventArgs(e, x, y)) - } - - onContextMenu(e: Dom.ContextMenuEvent, x: number, y: number) { - super.onContextMenu(e, x, y) - this.notify('edge:contextmenu', this.getEventArgs(e, x, y)) - } - - onMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - this.startEdgeDragging(e, x, y) - } - - onMouseMove(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - switch (data.action) { - case 'drag-label': { - this.dragLabel(e, x, y) - break - } - - case 'drag-arrowhead': { - this.dragArrowhead(e, x, y) - break - } - - case 'drag-edge': { - this.dragEdge(e, x, y) - break - } - - default: - break - } - - this.notifyMouseMove(e, x, y) - return data - } - - onMouseUp(e: Dom.MouseUpEvent, x: number, y: number) { - const data = this.getEventData(e) - switch (data.action) { - case 'drag-label': { - this.stopLabelDragging(e, x, y) - break - } - - case 'drag-arrowhead': { - this.stopArrowheadDragging(e, x, y) - break - } - - case 'drag-edge': { - this.stopEdgeDragging(e, x, y) - break - } - - default: - break - } - - this.notifyMouseUp(e, x, y) - this.checkMouseleave(e) - return data - } - - onMouseOver(e: Dom.MouseOverEvent) { - super.onMouseOver(e) - this.notify('edge:mouseover', this.getEventArgs(e)) - } - - onMouseOut(e: Dom.MouseOutEvent) { - super.onMouseOut(e) - this.notify('edge:mouseout', this.getEventArgs(e)) - } - - onMouseEnter(e: Dom.MouseEnterEvent) { - super.onMouseEnter(e) - this.notify('edge:mouseenter', this.getEventArgs(e)) - } - - onMouseLeave(e: Dom.MouseLeaveEvent) { - super.onMouseLeave(e) - this.notify('edge:mouseleave', this.getEventArgs(e)) - } - - onMouseWheel(e: Dom.EventObject, x: number, y: number, delta: number) { - super.onMouseWheel(e, x, y, delta) - this.notify('edge:mousewheel', { - delta, - ...this.getEventArgs(e, x, y), - }) - } - - onCustomEvent(e: Dom.MouseDownEvent, name: string, x: number, y: number) { - // For default edge tool - const tool = Dom.findParentByClass(e.target, 'edge-tool', this.container) - if (tool) { - e.stopPropagation() // no further action to be executed - if (this.can('useEdgeTools')) { - if (name === 'edge:remove') { - this.cell.remove({ ui: true }) - return - } - this.notify('edge:customevent', { name, ...this.getEventArgs(e, x, y) }) - } - - this.notifyMouseDown(e as Dom.MouseDownEvent, x, y) - } else { - this.notify('edge:customevent', { name, ...this.getEventArgs(e, x, y) }) - super.onCustomEvent(e, name, x, y) - } - } - - onLabelMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - this.notifyMouseDown(e, x, y) - this.startLabelDragging(e, x, y) - - const stopPropagation = this.getEventData(e).stopPropagation - if (stopPropagation) { - e.stopPropagation() - } - } - - // #region drag edge - - protected startEdgeDragging(e: Dom.MouseDownEvent, x: number, y: number) { - if (!this.can('edgeMovable')) { - this.notifyUnhandledMouseDown(e, x, y) - return - } - - this.setEventData(e, { - x, - y, - moving: false, - action: 'drag-edge', - }) - } - - protected dragEdge(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - if (!data.moving) { - data.moving = true - this.addClass('edge-moving') - this.notify('edge:move', { - e, - x, - y, - view: this, - cell: this.cell, - edge: this.cell, - }) - } - - this.cell.translate(x - data.x, y - data.y, { ui: true }) - this.setEventData>(e, { x, y }) - this.notify('edge:moving', { - e, - x, - y, - view: this, - cell: this.cell, - edge: this.cell, - }) - } - - protected stopEdgeDragging(e: Dom.MouseUpEvent, x: number, y: number) { - const data = this.getEventData(e) - if (data.moving) { - this.removeClass('edge-moving') - this.notify('edge:moved', { - e, - x, - y, - view: this, - cell: this.cell, - edge: this.cell, - }) - } - data.moving = false - } - - // #endregion - - // #region drag arrowhead - - prepareArrowheadDragging( - type: Edge.TerminalType, - options: { - x: number - y: number - options?: KeyValue - isNewEdge?: boolean - fallbackAction?: EventData.ArrowheadDragging['fallbackAction'] - }, - ) { - const magnet = this.getTerminalMagnet(type) - const data: EventData.ArrowheadDragging = { - action: 'drag-arrowhead', - x: options.x, - y: options.y, - isNewEdge: options.isNewEdge === true, - terminalType: type, - initialMagnet: magnet, - initialTerminal: ObjectExt.clone(this.cell[type]) as Edge.TerminalData, - fallbackAction: options.fallbackAction || 'revert', - getValidateConnectionArgs: this.createValidateConnectionArgs(type), - options: options.options, - } - - this.beforeArrowheadDragging(data) - - return data - } - - protected createValidateConnectionArgs(type: Edge.TerminalType) { - const args: EventData.ValidateConnectionArgs = [] as any - - args[4] = type - args[5] = this - - let opposite: Edge.TerminalType - let i = 0 - let j = 0 - - if (type === 'source') { - i = 2 - opposite = 'target' - } else { - j = 2 - opposite = 'source' - } - - const terminal = this.cell[opposite] - const cellId = (terminal as Edge.TerminalCellData).cell - if (cellId) { - let magnet - const view = (args[i] = this.graph.findViewByCell(cellId)) - if (view) { - magnet = view.getMagnetFromEdgeTerminal(terminal) - if (magnet === view.container) { - magnet = undefined - } - } - args[i + 1] = magnet - } - - return (cellView: CellView, magnet: Element) => { - args[j] = cellView - args[j + 1] = cellView.container === magnet ? undefined : magnet - return args - } - } - - protected beforeArrowheadDragging(data: EventData.ArrowheadDragging) { - data.zIndex = this.cell.zIndex - this.cell.toFront() - - const style = (this.container as HTMLElement).style - data.pointerEvents = style.pointerEvents - style.pointerEvents = 'none' - - if (this.graph.options.connecting.highlight) { - this.highlightAvailableMagnets(data) - } - } - - protected afterArrowheadDragging(data: EventData.ArrowheadDragging) { - if (data.zIndex != null) { - this.cell.setZIndex(data.zIndex, { ui: true }) - data.zIndex = null - } - - const container = this.container as HTMLElement - container.style.pointerEvents = data.pointerEvents || '' - - if (this.graph.options.connecting.highlight) { - this.unhighlightAvailableMagnets(data) - } - } - - protected validateConnection( - sourceView: CellView | null | undefined, - sourceMagnet: Element | null | undefined, - targetView: CellView | null | undefined, - targetMagnet: Element | null | undefined, - terminalType: Edge.TerminalType, - edgeView?: EdgeView | null | undefined, - candidateTerminal?: Edge.TerminalCellData | null | undefined, - ) { - const options = this.graph.options.connecting - const allowLoop = options.allowLoop - const allowNode = options.allowNode - const allowEdge = options.allowEdge - const allowPort = options.allowPort - const allowMulti = options.allowMulti - const validate = options.validateConnection - - const edge = edgeView ? edgeView.cell : null - const terminalView = terminalType === 'target' ? targetView : sourceView - const terminalMagnet = - terminalType === 'target' ? targetMagnet : sourceMagnet - - let valid = true - const doValidate = ( - validate: ( - this: Graph, - args: GraphOptions.ValidateConnectionArgs, - ) => boolean, - ) => { - const sourcePort = - terminalType === 'source' - ? candidateTerminal - ? candidateTerminal.port - : null - : edge - ? edge.getSourcePortId() - : null - const targetPort = - terminalType === 'target' - ? candidateTerminal - ? candidateTerminal.port - : null - : edge - ? edge.getTargetPortId() - : null - return FunctionExt.call(validate, this.graph, { - edge, - edgeView, - sourceView, - targetView, - sourcePort, - targetPort, - sourceMagnet, - targetMagnet, - sourceCell: sourceView ? sourceView.cell : null, - targetCell: targetView ? targetView.cell : null, - type: terminalType, - }) - } - - if (allowLoop != null) { - if (typeof allowLoop === 'boolean') { - if (!allowLoop && sourceView === targetView) { - valid = false - } - } else { - valid = doValidate(allowLoop) - } - } - - if (valid && allowPort != null) { - if (typeof allowPort === 'boolean') { - if (!allowPort && terminalMagnet) { - valid = false - } - } else { - valid = doValidate(allowPort) - } - } - - if (valid && allowEdge != null) { - if (typeof allowEdge === 'boolean') { - if (!allowEdge && EdgeView.isEdgeView(terminalView)) { - valid = false - } - } else { - valid = doValidate(allowEdge) - } - } - - // When judging nodes, the influence of the ports should be excluded, - // because the ports and nodes have the same terminalView - if (valid && allowNode != null && terminalMagnet == null) { - if (typeof allowNode === 'boolean') { - if (!allowNode && NodeView.isNodeView(terminalView)) { - valid = false - } - } else { - valid = doValidate(allowNode) - } - } - - if (valid && allowMulti != null && edgeView) { - const edge = edgeView.cell - const source = - terminalType === 'source' - ? candidateTerminal - : (edge.getSource() as Edge.TerminalCellData) - const target = - terminalType === 'target' - ? candidateTerminal - : (edge.getTarget() as Edge.TerminalCellData) - const terminalCell = candidateTerminal - ? this.graph.getCellById(candidateTerminal.cell) - : null - - if (source && target && source.cell && target.cell && terminalCell) { - if (typeof allowMulti === 'function') { - valid = doValidate(allowMulti) - } else { - const connectedEdges = this.graph.model.getConnectedEdges( - terminalCell, - { - outgoing: terminalType === 'source', - incoming: terminalType === 'target', - }, - ) - if (connectedEdges.length) { - if (allowMulti === 'withPort') { - const exist = connectedEdges.some((link) => { - const s = link.getSource() as Edge.TerminalCellData - const t = link.getTarget() as Edge.TerminalCellData - return ( - s && - t && - s.cell === source.cell && - t.cell === target.cell && - s.port != null && - s.port === source.port && - t.port != null && - t.port === target.port - ) - }) - if (exist) { - valid = false - } - } else if (!allowMulti) { - const exist = connectedEdges.some((link) => { - const s = link.getSource() as Edge.TerminalCellData - const t = link.getTarget() as Edge.TerminalCellData - return ( - s && t && s.cell === source.cell && t.cell === target.cell - ) - }) - if (exist) { - valid = false - } - } - } - } - } - } - - if (valid && validate != null) { - valid = doValidate(validate) - } - - return valid - } - - protected allowConnectToBlank(edge: Edge) { - const graph = this.graph - const options = graph.options.connecting - const allowBlank = options.allowBlank - - if (typeof allowBlank !== 'function') { - return !!allowBlank - } - - const edgeView = graph.findViewByCell(edge) as EdgeView - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - const sourceView = graph.findViewByCell(sourceCell) - const targetView = graph.findViewByCell(targetCell) - return FunctionExt.call(allowBlank, graph, { - edge, - edgeView, - sourceCell, - targetCell, - sourceView, - targetView, - sourcePort: edge.getSourcePortId(), - targetPort: edge.getTargetPortId(), - sourceMagnet: edgeView.sourceMagnet, - targetMagnet: edgeView.targetMagnet, - }) - } - - protected validateEdge( - edge: Edge, - type: Edge.TerminalType, - initialTerminal: Edge.TerminalData, - ) { - const graph = this.graph - if (!this.allowConnectToBlank(edge)) { - const sourceId = edge.getSourceCellId() - const targetId = edge.getTargetCellId() - if (!(sourceId && targetId)) { - return false - } - } - - const validate = graph.options.connecting.validateEdge - if (validate) { - return FunctionExt.call(validate, graph, { - edge, - type, - previous: initialTerminal, - }) - } - - return true - } - - protected arrowheadDragging( - target: Element, - x: number, - y: number, - data: EventData.ArrowheadDragging, - ) { - data.x = x - data.y = y - - // Checking views right under the pointer - if (data.currentTarget !== target) { - // Unhighlight the previous view under pointer if there was one. - if (data.currentMagnet && data.currentView) { - data.currentView.unhighlight(data.currentMagnet, { - type: 'magnetAdsorbed', - }) - } - - data.currentView = this.graph.findViewByElem(target) - if (data.currentView) { - // If we found a view that is under the pointer, we need to find - // the closest magnet based on the real target element of the event. - data.currentMagnet = data.currentView.findMagnet(target) - - if ( - data.currentMagnet && - this.validateConnection( - ...data.getValidateConnectionArgs( - data.currentView, - data.currentMagnet, - ), - data.currentView.getEdgeTerminal( - data.currentMagnet, - x, - y, - this.cell, - data.terminalType, - ), - ) - ) { - data.currentView.highlight(data.currentMagnet, { - type: 'magnetAdsorbed', - }) - } else { - // This type of connection is not valid. Disregard this magnet. - data.currentMagnet = null - } - } else { - // Make sure we'll unset previous magnet. - data.currentMagnet = null - } - } - - data.currentTarget = target - this.cell.prop(data.terminalType, { x, y }, { ...data.options, ui: true }) - } - - protected arrowheadDragged( - data: EventData.ArrowheadDragging, - x: number, - y: number, - ) { - const view = data.currentView - const magnet = data.currentMagnet - if (!magnet || !view) { - return - } - - view.unhighlight(magnet, { type: 'magnetAdsorbed' }) - - const type = data.terminalType - const terminal = view.getEdgeTerminal(magnet, x, y, this.cell, type) - this.cell.setTerminal(type, terminal, { ui: true }) - } - - protected snapArrowhead( - x: number, - y: number, - data: EventData.ArrowheadDragging, - ) { - const graph = this.graph - const snap = graph.options.connecting.snap - const radius = (typeof snap === 'object' && snap.radius) || 50 - const views = graph.findViewsInArea({ - x: x - radius, - y: y - radius, - width: 2 * radius, - height: 2 * radius, - }) - - const prevView = data.closestView || null - const prevMagnet = data.closestMagnet || null - - data.closestView = null - data.closestMagnet = null - - let distance - let minDistance = Number.MAX_SAFE_INTEGER - const pos = new Point(x, y) - - views.forEach((view) => { - if (view.container.getAttribute('magnet') !== 'false') { - // Find distance from the center of the cell to pointer coordinates - distance = view.cell.getBBox().getCenter().distance(pos) - // the connection is looked up in a circle area by `distance < r` - if (distance < radius && distance < minDistance) { - if ( - prevMagnet === view.container || - this.validateConnection( - ...data.getValidateConnectionArgs(view, null), - view.getEdgeTerminal( - view.container, - x, - y, - this.cell, - data.terminalType, - ), - ) - ) { - minDistance = distance - data.closestView = view - data.closestMagnet = view.container - } - } - } - - view.container.querySelectorAll('[magnet]').forEach((magnet) => { - if (magnet.getAttribute('magnet') !== 'false') { - const bbox = view.getBBoxOfElement(magnet) - distance = pos.distance(bbox.getCenter()) - if (distance < radius && distance < minDistance) { - if ( - prevMagnet === magnet || - this.validateConnection( - ...data.getValidateConnectionArgs(view, magnet), - view.getEdgeTerminal( - magnet, - x, - y, - this.cell, - data.terminalType, - ), - ) - ) { - minDistance = distance - data.closestView = view - data.closestMagnet = magnet - } - } - } - }) - }) - - let terminal - const type = data.terminalType - const closestView = data.closestView as any as CellView - const closestMagnet = data.closestMagnet as any as Element - const changed = prevMagnet !== closestMagnet - - if (prevView && changed) { - prevView.unhighlight(prevMagnet, { - type: 'magnetAdsorbed', - }) - } - - if (closestView) { - if (!changed) { - return - } - closestView.highlight(closestMagnet, { - type: 'magnetAdsorbed', - }) - terminal = closestView.getEdgeTerminal( - closestMagnet, - x, - y, - this.cell, - type, - ) - } else { - terminal = { x, y } - } - - this.cell.setTerminal(type, terminal, {}, { ...data.options, ui: true }) - } - - protected snapArrowheadEnd(data: EventData.ArrowheadDragging) { - // Finish off link snapping. - // Everything except view unhighlighting was already done on pointermove. - const closestView = data.closestView - const closestMagnet = data.closestMagnet - if (closestView && closestMagnet) { - closestView.unhighlight(closestMagnet, { - type: 'magnetAdsorbed', - }) - data.currentMagnet = closestView.findMagnet(closestMagnet) - } - - data.closestView = null - data.closestMagnet = null - } - - protected finishEmbedding(data: EventData.ArrowheadDragging) { - // Resets parent of the edge if embedding is enabled - if (this.graph.options.embedding.enabled && this.cell.updateParent()) { - // Make sure we don't reverse to the original 'z' index - data.zIndex = null - } - } - - protected fallbackConnection(data: EventData.ArrowheadDragging) { - switch (data.fallbackAction) { - case 'remove': - this.cell.remove({ ui: true }) - break - case 'revert': - default: - this.cell.prop(data.terminalType, data.initialTerminal, { - ui: true, - }) - break - } - } - - protected notifyConnectionEvent( - data: EventData.ArrowheadDragging, - e: Dom.MouseUpEvent, - ) { - const terminalType = data.terminalType - const initialTerminal = data.initialTerminal - const currentTerminal = this.cell[terminalType] - const changed = - currentTerminal && !Edge.equalTerminals(initialTerminal, currentTerminal) - - if (changed) { - const graph = this.graph - const previous = initialTerminal as Edge.TerminalCellData - const previousCell = previous.cell - ? graph.getCellById(previous.cell) - : null - const previousPort = previous.port - const previousView = previousCell - ? graph.findViewByCell(previousCell) - : null - const previousPoint = - previousCell || data.isNewEdge - ? null - : Point.create(initialTerminal as Edge.TerminalPointData).toJSON() - - const current = currentTerminal as Edge.TerminalCellData - const currentCell = current.cell ? graph.getCellById(current.cell) : null - const currentPort = current.port - const currentView = currentCell ? graph.findViewByCell(currentCell) : null - const currentPoint = currentCell - ? null - : Point.create(currentTerminal as Edge.TerminalPointData).toJSON() - - this.notify('edge:connected', { - e, - previousCell, - previousPort, - previousView, - previousPoint, - currentCell, - currentView, - currentPort, - currentPoint, - previousMagnet: data.initialMagnet, - currentMagnet: data.currentMagnet, - edge: this.cell, - view: this, - type: terminalType, - isNew: data.isNewEdge, - }) - } - } - - protected highlightAvailableMagnets(data: EventData.ArrowheadDragging) { - const graph = this.graph - const cells = graph.model.getCells() - data.marked = {} - - for (let i = 0, ii = cells.length; i < ii; i += 1) { - const view = graph.findViewByCell(cells[i]) - - if (!view) { - continue - } - - const magnets: Element[] = Array.prototype.slice.call( - view.container.querySelectorAll('[magnet]'), - ) - - if (view.container.getAttribute('magnet') !== 'false') { - magnets.push(view.container) - } - - const availableMagnets = magnets.filter((magnet) => - this.validateConnection( - ...data.getValidateConnectionArgs(view, magnet), - view.getEdgeTerminal( - magnet, - data.x, - data.y, - this.cell, - data.terminalType, - ), - ), - ) - - if (availableMagnets.length > 0) { - // highlight all available magnets - for (let j = 0, jj = availableMagnets.length; j < jj; j += 1) { - view.highlight(availableMagnets[j], { type: 'magnetAvailable' }) - } - - // highlight the entire view - view.highlight(null, { type: 'nodeAvailable' }) - data.marked[view.cell.id] = availableMagnets - } - } - } - - protected unhighlightAvailableMagnets(data: EventData.ArrowheadDragging) { - const marked = data.marked || {} - Object.keys(marked).forEach((id) => { - const view = this.graph.findViewByCell(id) - - if (view) { - const magnets = marked[id] - magnets.forEach((magnet) => { - view.unhighlight(magnet, { type: 'magnetAvailable' }) - }) - - view.unhighlight(null, { type: 'nodeAvailable' }) - } - }) - data.marked = null - } - - protected startArrowheadDragging( - e: Dom.MouseDownEvent, - x: number, - y: number, - ) { - if (!this.can('arrowheadMovable')) { - this.notifyUnhandledMouseDown(e, x, y) - return - } - - const elem = e.target - const type = elem.getAttribute('data-terminal') as Edge.TerminalType - const data = this.prepareArrowheadDragging(type, { x, y }) - this.setEventData(e, data) - } - - protected dragArrowhead(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - if (this.graph.options.connecting.snap) { - this.snapArrowhead(x, y, data) - } else { - this.arrowheadDragging(this.getEventTarget(e), x, y, data) - } - } - - protected stopArrowheadDragging(e: Dom.MouseUpEvent, x: number, y: number) { - const graph = this.graph - const data = this.getEventData(e) - if (graph.options.connecting.snap) { - this.snapArrowheadEnd(data) - } else { - this.arrowheadDragged(data, x, y) - } - - const valid = this.validateEdge( - this.cell, - data.terminalType, - data.initialTerminal, - ) - - if (valid) { - this.finishEmbedding(data) - this.notifyConnectionEvent(data, e) - } else { - // If the changed edge is not allowed, revert to its previous state. - this.fallbackConnection(data) - } - this.afterArrowheadDragging(data) - } - - // #endregion - - // #region drag lable - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - startLabelDragging(e: Dom.MouseDownEvent, x: number, y: number) { - if (this.can('edgeLabelMovable')) { - const target = e.currentTarget - const index = parseInt(target.getAttribute('data-index'), 10) - const positionAngle = this.getLabelPositionAngle(index) - const labelPositionArgs = this.getLabelPositionArgs(index) - const defaultLabelPositionArgs = this.getDefaultLabelPositionArgs() - const positionArgs = this.mergeLabelPositionArgs( - labelPositionArgs, - defaultLabelPositionArgs, - ) - - this.setEventData(e, { - index, - positionAngle, - positionArgs, - stopPropagation: true, - action: 'drag-label', - }) - } else { - // If labels can't be dragged no default action is triggered. - this.setEventData(e, { stopPropagation: true }) - } - - this.graph.view.delegateDragEvents(e, this) - } - - dragLabel(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - const originLabel = this.cell.getLabelAt(data.index) - const label = ObjectExt.merge({}, originLabel, { - position: this.getLabelPosition( - x, - y, - data.positionAngle, - data.positionArgs, - ), - }) - this.cell.setLabelAt(data.index, label) - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - stopLabelDragging(e: Dom.MouseUpEvent, x: number, y: number) {} - - // #endregion - // #endregion -} - -export namespace EdgeView { - export interface Options extends CellView.Options {} -} - -export namespace EdgeView { - export interface MouseEventArgs { - e: E - edge: Edge - cell: Edge - view: EdgeView - } - - export interface PositionEventArgs - extends MouseEventArgs, - CellView.PositionEventArgs {} - - export interface EventArgs { - 'edge:click': PositionEventArgs - 'edge:dblclick': PositionEventArgs - 'edge:contextmenu': PositionEventArgs - 'edge:mousedown': PositionEventArgs - 'edge:mousemove': PositionEventArgs - 'edge:mouseup': PositionEventArgs - 'edge:mouseover': MouseEventArgs - 'edge:mouseout': MouseEventArgs - 'edge:mouseenter': MouseEventArgs - 'edge:mouseleave': MouseEventArgs - 'edge:mousewheel': PositionEventArgs & - CellView.MouseDeltaEventArgs - - 'edge:customevent': EdgeView.PositionEventArgs & { - name: string - } - - 'edge:unhandled:mousedown': PositionEventArgs - - 'edge:connected': { - e: Dom.MouseUpEvent - edge: Edge - view: EdgeView - isNew: boolean - type: Edge.TerminalType - previousCell?: Cell | null - previousView?: CellView | null - previousPort?: string | null - previousPoint?: Point.PointLike | null - previousMagnet?: Element | null - currentCell?: Cell | null - currentView?: CellView | null - currentPort?: string | null - currentPoint?: Point.PointLike | null - currentMagnet?: Element | null - } - - 'edge:highlight': { - magnet: Element - view: EdgeView - edge: Edge - cell: Edge - options: CellView.HighlightOptions - } - 'edge:unhighlight': EventArgs['edge:highlight'] - - 'edge:move': PositionEventArgs - 'edge:moving': PositionEventArgs - 'edge:moved': PositionEventArgs - } -} - -export namespace EdgeView { - export const toStringTag = `X6.${EdgeView.name}` - - export function isEdgeView(instance: any): instance is EdgeView { - if (instance == null) { - return false - } - - if (instance instanceof EdgeView) { - return true - } - - const tag = instance[Symbol.toStringTag] - const view = instance as EdgeView - - if ( - (tag == null || tag === toStringTag) && - typeof view.isNodeView === 'function' && - typeof view.isEdgeView === 'function' && - typeof view.confirmUpdate === 'function' && - typeof view.update === 'function' && - typeof view.getConnection === 'function' - ) { - return true - } - - return false - } -} - -namespace EventData { - export interface MousemoveEventData {} - - export interface EdgeDragging { - action: 'drag-edge' - moving: boolean - x: number - y: number - } - - export type ValidateConnectionArgs = [ - CellView | null | undefined, // source view - Element | null | undefined, // source magnet - CellView | null | undefined, // target view - Element | null | undefined, // target magnet - Edge.TerminalType, - EdgeView, - ] - - export interface ArrowheadDragging { - action: 'drag-arrowhead' - x: number - y: number - isNewEdge: boolean - terminalType: Edge.TerminalType - fallbackAction: 'remove' | 'revert' - initialMagnet: Element | null - initialTerminal: Edge.TerminalData - getValidateConnectionArgs: ( - cellView: CellView, - magnet: Element | null, - ) => ValidateConnectionArgs - zIndex?: number | null - pointerEvents?: string | null - /** - * Current event target. - */ - currentTarget?: Element - /** - * Current view under pointer. - */ - currentView?: CellView | null - /** - * Current magnet under pointer. - */ - currentMagnet?: Element | null - closestView?: CellView | null - closestMagnet?: Element | null - marked?: KeyValue | null - options?: KeyValue - } - - export interface LabelDragging { - action: 'drag-label' - index: number - positionAngle: number - positionArgs?: Edge.LabelPositionOptions | null - stopPropagation: true - } -} - -EdgeView.config({ - isSvgElement: true, - priority: 1, - bootstrap: ['render', 'source', 'target'], - actions: { - view: ['render'], - markup: ['render'], - attrs: ['update'], - source: ['source', 'update'], - target: ['target', 'update'], - router: ['update'], - connector: ['update'], - labels: ['labels'], - defaultLabel: ['labels'], - tools: ['tools'], - }, -}) - -EdgeView.registry.register('edge', EdgeView, true) diff --git a/packages/x6-next/src/view/flag.ts b/packages/x6-next/src/view/flag.ts deleted file mode 100644 index 2233bc369c2..00000000000 --- a/packages/x6-next/src/view/flag.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* eslint-disable no-bitwise */ - -import { KeyValue } from '@antv/x6-common' -import { CellView } from './cell' - -export class FlagManager { - protected attrs: { [attr: string]: number } - protected flags: { [name: string]: number } - protected bootstrap: FlagManager.Actions - - protected get cell() { - return this.view.cell - } - - constructor( - protected view: CellView, - actions: KeyValue, - bootstrap: FlagManager.Actions = [], - ) { - const flags: { [name: string]: number } = {} - const attrs: { [attr: string]: number } = {} - - let shift = 0 - Object.keys(actions).forEach((attr) => { - let labels = actions[attr] - if (!Array.isArray(labels)) { - labels = [labels] - } - - labels.forEach((label) => { - let flag = flags[label] - if (!flag) { - shift += 1 - flag = flags[label] = 1 << shift - } - attrs[attr] |= flag - }) - }) - - let labels = bootstrap - if (!Array.isArray(labels)) { - labels = [labels] - } - - labels.forEach((label) => { - if (!flags[label]) { - shift += 1 - flags[label] = 1 << shift - } - }) - - // 26 - 30 are reserved for paper flags - // 31+ overflows maximal number - if (shift > 25) { - throw new Error('Maximum number of flags exceeded.') - } - - this.flags = flags - this.attrs = attrs - this.bootstrap = bootstrap - } - - getFlag(label: FlagManager.Actions) { - const flags = this.flags - if (flags == null) { - return 0 - } - - if (Array.isArray(label)) { - return label.reduce((memo, key) => memo | flags[key], 0) - } - - return flags[label] | 0 - } - - hasAction(flag: number, label: FlagManager.Actions) { - return flag & this.getFlag(label) - } - - removeAction(flag: number, label: FlagManager.Actions) { - return flag ^ (flag & this.getFlag(label)) - } - - getBootstrapFlag() { - return this.getFlag(this.bootstrap) - } - - getChangedFlag() { - let flag = 0 - - if (!this.attrs) { - return flag - } - - Object.keys(this.attrs).forEach((attr) => { - if (this.cell.hasChanged(attr)) { - flag |= this.attrs[attr] - } - }) - - return flag - } -} - -export namespace FlagManager { - export type Action = - | 'render' - | 'update' - | 'resize' - | 'rotate' - | 'translate' - | 'ports' - | 'tools' - | 'source' - | 'target' - | 'labels' - - export type Actions = Action | Action[] -} diff --git a/packages/x6-next/src/view/index.ts b/packages/x6-next/src/view/index.ts deleted file mode 100644 index add7ab68320..00000000000 --- a/packages/x6-next/src/view/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './markup' -export * from './view' -export * from './cell' -export * from './edge' -export * from './node' -export * from './tool' diff --git a/packages/x6-next/src/view/markup.ts b/packages/x6-next/src/view/markup.ts deleted file mode 100644 index c0b7ba3bff6..00000000000 --- a/packages/x6-next/src/view/markup.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { ObjectExt, Dom, Vector, KeyValue, Nilable } from '@antv/x6-common' -import { Attr } from '../registry' - -export type Markup = string | Markup.JSONMarkup | Markup.JSONMarkup[] - -// eslint-disable-next-line -export namespace Markup { - export type Selectors = KeyValue - - export interface JSONMarkup { - /** - * The namespace URI of the element. It defaults to SVG namespace - * `"http://www.w3.org/2000/svg"`. - */ - ns?: string | null - - /** - * The type of element to be created. - */ - tagName: string - - /** - * A unique selector for targeting the element within the `attr` - * cell attribute. - */ - selector?: string | null - - /** - * A selector for targeting multiple elements within the `attr` - * cell attribute. The group selector name must not be the same - * as an existing selector name. - */ - groupSelector?: string | string[] | null - - attrs?: Attr.SimpleAttrs - - style?: Record - - className?: string | string[] - - children?: JSONMarkup[] - - textContent?: string - } - - export interface ParseResult { - fragment: DocumentFragment - selectors: Selectors - groups: KeyValue - } -} - -// eslint-disable-next-line -export namespace Markup { - export function isJSONMarkup(markup?: Nilable) { - return markup != null && !isStringMarkup(markup) - } - - export function isStringMarkup(markup?: Nilable): markup is string { - return markup != null && typeof markup === 'string' - } - - export function clone(markup?: Nilable) { - return markup == null || isStringMarkup(markup) - ? markup - : ObjectExt.cloneDeep(markup) - } - - /** - * Removes blank space in markup to prevent create empty text node. - */ - export function sanitize(markup: string) { - return `${markup}` - .trim() - .replace(/[\r|\n]/g, ' ') - .replace(/>\s+<') - } - - export function parseJSONMarkup( - markup: JSONMarkup | JSONMarkup[], - options: { ns?: string } = { ns: Dom.ns.svg }, - ): ParseResult { - const fragment = document.createDocumentFragment() - const groups: KeyValue = {} - const selectors: Selectors = {} - - const queue: { - markup: JSONMarkup[] - parent: Element | DocumentFragment - ns?: string - }[] = [ - { - markup: Array.isArray(markup) ? markup : [markup], - parent: fragment, - ns: options.ns, - }, - ] - - while (queue.length > 0) { - const item = queue.pop()! - let ns = item.ns || Dom.ns.svg - const defines = item.markup - const parentNode = item.parent - - defines.forEach((define) => { - // tagName - const tagName = define.tagName - if (!tagName) { - throw new TypeError('Invalid tagName') - } - - // ns - if (define.ns) { - ns = define.ns - } - - const node = ns - ? Dom.createElementNS(tagName, ns) - : Dom.createElement(tagName) - - // attrs - const attrs = define.attrs - if (attrs) { - Dom.attr(node, Dom.kebablizeAttrs(attrs)) - } - - // style - const style = define.style - if (style) { - Dom.css(node, style) - } - - // classname - const className = define.className - if (className != null) { - node.setAttribute( - 'class', - Array.isArray(className) ? className.join(' ') : className, - ) - } - - // textContent - if (define.textContent) { - node.textContent = define.textContent - } - - // selector - const selector = define.selector - if (selector != null) { - if (selectors[selector]) { - throw new TypeError('Selector must be unique') - } - - selectors[selector] = node - } - - // group - if (define.groupSelector) { - let nodeGroups = define.groupSelector - if (!Array.isArray(nodeGroups)) { - nodeGroups = [nodeGroups] - } - - nodeGroups.forEach((name) => { - if (!groups[name]) { - groups[name] = [] - } - groups[name].push(node) - }) - } - - parentNode.appendChild(node) - - // children - const children = define.children - if (Array.isArray(children)) { - queue.push({ ns, markup: children, parent: node }) - } - }) - } - - Object.keys(groups).forEach((groupName) => { - if (selectors[groupName]) { - throw new Error('Ambiguous group selector') - } - selectors[groupName] = groups[groupName] - }) - - return { fragment, selectors, groups } - } - - function createContainer(firstChild: Element) { - return firstChild instanceof SVGElement - ? Dom.createSvgElement('g') - : Dom.createElement('div') - } - - export function renderMarkup(markup: Markup): { - elem?: Element - selectors?: Selectors - } { - if (isStringMarkup(markup)) { - const nodes = Vector.createVectors(markup) - const count = nodes.length - - if (count === 1) { - return { - elem: nodes[0].node as Element, - } - } - - if (count > 1) { - const elem = createContainer(nodes[0].node) - nodes.forEach((node) => { - elem.appendChild(node.node) - }) - - return { elem } - } - - return {} - } - - const result = parseJSONMarkup(markup) - const fragment = result.fragment - let elem: Element | null = null - if (fragment.childNodes.length > 1) { - elem = createContainer(fragment.firstChild as Element) - elem.appendChild(fragment) - } else { - elem = fragment.firstChild as Element - } - - return { elem, selectors: result.selectors } - } - - export function parseLabelStringMarkup(markup: string) { - const children = Vector.createVectors(markup) - const fragment = document.createDocumentFragment() - for (let i = 0, n = children.length; i < n; i += 1) { - const currentChild = children[i].node - fragment.appendChild(currentChild) - } - - return { fragment, selectors: {} } - } -} - -// eslint-disable-next-line -export namespace Markup { - export function getSelector( - elem: Element, - stop: Element, - prev?: string, - ): string | undefined { - if (elem != null) { - let selector - const tagName = elem.tagName.toLowerCase() - - if (elem === stop) { - if (typeof prev === 'string') { - selector = `> ${tagName} > ${prev}` - } else { - selector = `> ${tagName}` - } - return selector - } - - const parent = elem.parentNode - if (parent && parent.childNodes.length > 1) { - const nth = Dom.index(elem) + 1 - selector = `${tagName}:nth-child(${nth})` - } else { - selector = tagName - } - - if (prev) { - selector += ` > ${prev}` - } - - return getSelector(elem.parentNode as Element, stop, selector) - } - - return prev - } -} - -// eslint-disable-next-line -export namespace Markup { - export function getPortContainerMarkup(): Markup { - return 'g' - } - - export function getPortMarkup(): Markup { - return { - tagName: 'circle', - selector: 'circle', - attrs: { - r: 10, - fill: '#FFFFFF', - stroke: '#000000', - }, - } - } - - export function getPortLabelMarkup(): Markup { - return { - tagName: 'text', - selector: 'text', - attrs: { - fill: '#000000', - }, - } - } -} - -// eslint-disable-next-line -export namespace Markup { - export function getEdgeMarkup(): Markup { - return [ - { - tagName: 'path', - selector: 'wrap', - groupSelector: 'lines', - attrs: { - fill: 'none', - cursor: 'pointer', - stroke: 'transparent', - strokeLinecap: 'round', - }, - }, - { - tagName: 'path', - selector: 'line', - groupSelector: 'lines', - attrs: { - fill: 'none', - pointerEvents: 'none', - }, - }, - ] - } -} - -// eslint-disable-next-line -export namespace Markup { - export function getForeignObjectMarkup(bare = false): Markup.JSONMarkup { - return { - tagName: 'foreignObject', - selector: 'fo', - children: [ - { - ns: Dom.ns.xhtml, - tagName: 'body', - selector: 'foBody', - attrs: { - xmlns: Dom.ns.xhtml, - }, - style: { - width: '100%', - height: '100%', - background: 'transparent', - }, - children: bare - ? [] - : [ - { - tagName: 'div', - selector: 'foContent', - style: { - width: '100%', - height: '100%', - }, - }, - ], - }, - ], - } - } -} diff --git a/packages/x6-next/src/view/node.ts b/packages/x6-next/src/view/node.ts deleted file mode 100644 index a6c73bc10ad..00000000000 --- a/packages/x6-next/src/view/node.ts +++ /dev/null @@ -1,1238 +0,0 @@ -import { ArrayExt, FunctionExt, Dom, KeyValue } from '@antv/x6-common' -import { Rectangle, Point, Util as GeomUtil } from '@antv/x6-geometry' -import { Config } from '../config' -import { PortLayout } from '../registry' -import { Cell } from '../model/cell' -import { Node } from '../model/node' -import { Edge } from '../model/edge' -import { PortManager } from '../model/port' -import { CellView } from './cell' -import { EdgeView } from './edge' -import { Markup } from './markup' -import { AttrManager } from './attr' -import { Graph } from '../graph' - -export class NodeView< - Entity extends Node = Node, - Options extends NodeView.Options = NodeView.Options, -> extends CellView { - protected portsCache: { [id: string]: NodeView.PortCache } = {} - - protected get [Symbol.toStringTag]() { - return NodeView.toStringTag - } - - protected getContainerClassName() { - const classList = [ - super.getContainerClassName(), - this.prefixClassName('node'), - ] - if (!this.can('nodeMovable')) { - classList.push(this.prefixClassName('node-immovable')) - } - return classList.join(' ') - } - - protected updateClassName(e: Dom.MouseEnterEvent) { - const target = e.target - if (target.hasAttribute('magnet')) { - // port - const className = this.prefixClassName('port-unconnectable') - if (this.can('magnetConnectable')) { - Dom.removeClass(target, className) - } else { - Dom.addClass(target, className) - } - } else { - // node - const className = this.prefixClassName('node-immovable') - if (this.can('nodeMovable')) { - this.removeClass(className) - } else { - this.addClass(className) - } - } - } - - isNodeView(): this is NodeView { - return true - } - - confirmUpdate(flag: number) { - let ret = flag - if (this.hasAction(ret, 'ports')) { - this.removePorts() - this.cleanPortsCache() - } - - if (this.hasAction(ret, 'render')) { - this.render() - ret = this.removeAction(ret, [ - 'render', - 'update', - 'resize', - 'translate', - 'rotate', - 'ports', - 'tools', - ]) - } else { - ret = this.handleAction( - ret, - 'resize', - () => this.resize(), - 'update', // Resize method is calling `update()` internally - ) - - ret = this.handleAction( - ret, - 'update', - () => this.update(), - // `update()` will render ports when useCSSSelectors are enabled - Config.useCSSSelector ? 'ports' : null, - ) - - ret = this.handleAction(ret, 'translate', () => this.translate()) - ret = this.handleAction(ret, 'rotate', () => this.rotate()) - ret = this.handleAction(ret, 'ports', () => this.renderPorts()) - ret = this.handleAction(ret, 'tools', () => this.renderTools()) - } - - return ret - } - - update() { - this.cleanCache() - - // When CSS selector strings are used, make sure no rule matches port nodes. - if (Config.useCSSSelector) { - this.removePorts() - } - - const node = this.cell - const size = node.getSize() - const attrs = node.getAttrs() - this.updateAttrs(this.container, attrs, { - rootBBox: new Rectangle(0, 0, size.width, size.height), - selectors: this.selectors, - }) - - if (Config.useCSSSelector) { - this.renderPorts() - } - } - - protected renderMarkup() { - const markup = this.cell.markup - if (markup) { - if (typeof markup === 'string') { - throw new TypeError('Not support string markup.') - } - - return this.renderJSONMarkup(markup) - } - - throw new TypeError('Invalid node markup.') - } - - protected renderJSONMarkup(markup: Markup.JSONMarkup | Markup.JSONMarkup[]) { - const ret = this.parseJSONMarkup(markup, this.container) - this.selectors = ret.selectors - this.container.appendChild(ret.fragment) - } - - render() { - this.empty() - this.renderMarkup() - - this.resize() - this.updateTransform() - - if (!Config.useCSSSelector) { - this.renderPorts() - } - - this.renderTools() - - return this - } - - resize() { - if (this.cell.getAngle()) { - this.rotate() - } - - this.update() - } - - translate() { - this.updateTransform() - } - - rotate() { - this.updateTransform() - } - - protected getTranslationString() { - const position = this.cell.getPosition() - return `translate(${position.x},${position.y})` - } - - protected getRotationString() { - const angle = this.cell.getAngle() - if (angle) { - const size = this.cell.getSize() - return `rotate(${angle},${size.width / 2},${size.height / 2})` - } - } - - protected updateTransform() { - let transform = this.getTranslationString() - const rot = this.getRotationString() - if (rot) { - transform += ` ${rot}` - } - this.container.setAttribute('transform', transform) - } - - // #region ports - - findPortElem(portId?: string, selector?: string) { - const cache = portId ? this.portsCache[portId] : null - if (!cache) { - return null - } - const portRoot = cache.portContentElement - const portSelectors = cache.portContentSelectors || {} - return this.findOne(selector, portRoot, portSelectors) - } - - protected cleanPortsCache() { - this.portsCache = {} - } - - protected removePorts() { - Object.keys(this.portsCache).forEach((portId) => { - const cached = this.portsCache[portId] - Dom.remove(cached.portElement) - }) - } - - protected renderPorts() { - const container = this.container - // References to rendered elements without z-index - const references: Element[] = [] - container.childNodes.forEach((child) => { - references.push(child as Element) - }) - const parsedPorts = this.cell.getParsedPorts() - const portsGropsByZ = ArrayExt.groupBy(parsedPorts, 'zIndex') - const autoZIndexKey = 'auto' - - // render non-z first - if (portsGropsByZ[autoZIndexKey]) { - portsGropsByZ[autoZIndexKey].forEach((port) => { - const portElement = this.getPortElement(port) - container.append(portElement) - references.push(portElement) - }) - } - - Object.keys(portsGropsByZ).forEach((key) => { - if (key !== autoZIndexKey) { - const zIndex = parseInt(key, 10) - this.appendPorts(portsGropsByZ[key], zIndex, references) - } - }) - - this.updatePorts() - } - - protected appendPorts( - ports: PortManager.Port[], - zIndex: number, - refs: Element[], - ) { - const elems = ports.map((p) => this.getPortElement(p)) - if (refs[zIndex] || zIndex < 0) { - Dom.before(refs[Math.max(zIndex, 0)], elems) - } else { - Dom.append(this.container, elems) - } - } - - protected getPortElement(port: PortManager.Port) { - const cached = this.portsCache[port.id] - if (cached) { - return cached.portElement - } - - return this.createPortElement(port) - } - - protected createPortElement(port: PortManager.Port) { - let renderResult = Markup.renderMarkup(this.cell.getPortContainerMarkup()) - const portElement = renderResult.elem - if (portElement == null) { - throw new Error('Invalid port container markup.') - } - - renderResult = Markup.renderMarkup(this.getPortMarkup(port)) - const portContentElement = renderResult.elem - const portContentSelectors = renderResult.selectors - - if (portContentElement == null) { - throw new Error('Invalid port markup.') - } - - this.setAttrs( - { - port: port.id, - 'port-group': port.group, - }, - portContentElement, - ) - - Dom.addClass(portElement, 'x6-port') - Dom.addClass(portContentElement, 'x6-port-body') - portElement.appendChild(portContentElement) - - let portSelectors: Markup.Selectors | undefined = portContentSelectors - let portLabelElement: Element | undefined - let portLabelSelectors: Markup.Selectors | null | undefined - const existLabel = this.existPortLabel(port) - if (existLabel) { - renderResult = Markup.renderMarkup(this.getPortLabelMarkup(port.label)) - portLabelElement = renderResult.elem - portLabelSelectors = renderResult.selectors - if (portLabelElement == null) { - throw new Error('Invalid port label markup.') - } - if (portContentSelectors && portLabelSelectors) { - // eslint-disable-next-line - for (const key in portLabelSelectors) { - if (portContentSelectors[key] && key !== this.rootSelector) { - throw new Error('Selectors within port must be unique.') - } - } - portSelectors = { - ...portContentSelectors, - ...portLabelSelectors, - } - } - Dom.addClass(portLabelElement, 'x6-port-label') - portElement.appendChild(portLabelElement) - } - - this.portsCache[port.id] = { - portElement, - portSelectors, - portLabelElement, - portLabelSelectors, - portContentElement, - portContentSelectors, - } - - if (this.graph.options.onPortRendered) { - this.graph.options.onPortRendered({ - port, - node: this.cell, - container: portElement, - selectors: portSelectors, - labelContainer: portLabelElement, - labelSelectors: portLabelSelectors, - contentContainer: portContentElement, - contentSelectors: portContentSelectors, - }) - } - - return portElement - } - - protected updatePorts() { - const groups = this.cell.getParsedGroups() - Object.keys(groups).forEach((groupName) => this.updatePortGroup(groupName)) - } - - protected updatePortGroup(groupName?: string) { - const bbox = Rectangle.fromSize(this.cell.getSize()) - const metrics = this.cell.getPortsLayoutByGroup(groupName, bbox) - - for (let i = 0, n = metrics.length; i < n; i += 1) { - const metric = metrics[i] - const portId = metric.portId - const cached = this.portsCache[portId] || {} - const portLayout = metric.portLayout - this.applyPortTransform(cached.portElement, portLayout) - if (metric.portAttrs != null) { - const options: Partial = { - selectors: cached.portSelectors || {}, - } - - if (metric.portSize) { - options.rootBBox = Rectangle.fromSize(metric.portSize) - } - - this.updateAttrs(cached.portElement, metric.portAttrs, options) - } - - const labelLayout = metric.labelLayout - if (labelLayout && cached.portLabelElement) { - this.applyPortTransform( - cached.portLabelElement, - labelLayout, - -(portLayout.angle || 0), - ) - - if (labelLayout.attrs) { - const options: Partial = { - selectors: cached.portLabelSelectors || {}, - } - - if (metric.labelSize) { - options.rootBBox = Rectangle.fromSize(metric.labelSize) - } - - this.updateAttrs(cached.portLabelElement, labelLayout.attrs, options) - } - } - } - } - - protected applyPortTransform( - element: Element, - layout: PortLayout.Result, - initialAngle = 0, - ) { - const angle = layout.angle - const position = layout.position - const matrix = Dom.createSVGMatrix() - .rotate(initialAngle) - .translate(position.x || 0, position.y || 0) - .rotate(angle || 0) - - Dom.transform(element as SVGElement, matrix, { absolute: true }) - } - - protected getPortMarkup(port: PortManager.Port) { - return port.markup || this.cell.portMarkup - } - - protected getPortLabelMarkup(label: PortManager.Label) { - return label.markup || this.cell.portLabelMarkup - } - - protected existPortLabel(port: PortManager.Port) { - const traverse = (attrs: KeyValue | undefined): boolean => { - if (attrs) { - const keys = Object.keys(attrs) - for (let i = 0, len = keys.length; i < len; i += 1) { - const key = keys[i] - if (key === 'text') { - return true - } - const value = attrs[key] - if (typeof value === 'object') { - return traverse(value) - } - } - } - return false - } - return traverse(port.attrs) - } - - // #endregion - - // #region events - - protected getEventArgs(e: E): NodeView.MouseEventArgs - protected getEventArgs( - e: E, - x: number, - y: number, - ): NodeView.PositionEventArgs - protected getEventArgs(e: E, x?: number, y?: number) { - const view = this // eslint-disable-line - const node = view.cell - const cell = node - if (x == null || y == null) { - return { e, view, node, cell } as NodeView.MouseEventArgs - } - return { e, x, y, view, node, cell } as NodeView.PositionEventArgs - } - - notifyMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - super.onMouseDown(e, x, y) - this.notify('node:mousedown', this.getEventArgs(e, x, y)) - } - - notifyMouseMove(e: Dom.MouseMoveEvent, x: number, y: number) { - super.onMouseMove(e, x, y) - this.notify('node:mousemove', this.getEventArgs(e, x, y)) - } - - notifyMouseUp(e: Dom.MouseUpEvent, x: number, y: number) { - super.onMouseUp(e, x, y) - this.notify('node:mouseup', this.getEventArgs(e, x, y)) - } - - onClick(e: Dom.ClickEvent, x: number, y: number) { - super.onClick(e, x, y) - this.notify('node:click', this.getEventArgs(e, x, y)) - } - - onDblClick(e: Dom.DoubleClickEvent, x: number, y: number) { - super.onDblClick(e, x, y) - this.notify('node:dblclick', this.getEventArgs(e, x, y)) - } - - onContextMenu(e: Dom.ContextMenuEvent, x: number, y: number) { - super.onContextMenu(e, x, y) - this.notify('node:contextmenu', this.getEventArgs(e, x, y)) - } - - onMouseDown(e: Dom.MouseDownEvent, x: number, y: number) { - if (this.isPropagationStopped(e)) { - return - } - this.notifyMouseDown(e, x, y) - this.startNodeDragging(e, x, y) - } - - onMouseMove(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - const action = data.action - if (action === 'magnet') { - this.dragMagnet(e, x, y) - } else { - if (action === 'move') { - const meta = data as EventData.Moving - const view = meta.targetView || this - view.dragNode(e, x, y) - view.notify('node:moving', { - e, - x, - y, - view, - cell: view.cell, - node: view.cell, - }) - } - this.notifyMouseMove(e, x, y) - } - - this.setEventData(e, data) - } - - onMouseUp(e: Dom.MouseUpEvent, x: number, y: number) { - const data = this.getEventData(e) - const action = data.action - if (action === 'magnet') { - this.stopMagnetDragging(e, x, y) - } else { - this.notifyMouseUp(e, x, y) - if (action === 'move') { - const meta = data as EventData.Moving - const view = meta.targetView || this - view.stopNodeDragging(e, x, y) - } - } - - const magnet = (data as EventData.Magnet).targetMagnet - if (magnet) { - this.onMagnetClick(e, magnet, x, y) - } - - this.checkMouseleave(e) - } - - onMouseOver(e: Dom.MouseOverEvent) { - super.onMouseOver(e) - this.notify('node:mouseover', this.getEventArgs(e)) - } - - onMouseOut(e: Dom.MouseOutEvent) { - super.onMouseOut(e) - this.notify('node:mouseout', this.getEventArgs(e)) - } - - onMouseEnter(e: Dom.MouseEnterEvent) { - this.updateClassName(e) - super.onMouseEnter(e) - this.notify('node:mouseenter', this.getEventArgs(e)) - } - - onMouseLeave(e: Dom.MouseLeaveEvent) { - super.onMouseLeave(e) - this.notify('node:mouseleave', this.getEventArgs(e)) - } - - onMouseWheel(e: Dom.EventObject, x: number, y: number, delta: number) { - super.onMouseWheel(e, x, y, delta) - this.notify('node:mousewheel', { - delta, - ...this.getEventArgs(e, x, y), - }) - } - - onMagnetClick(e: Dom.MouseUpEvent, magnet: Element, x: number, y: number) { - const graph = this.graph - const count = graph.view.getMouseMovedCount(e) - if (count > graph.options.clickThreshold) { - return - } - this.notify('node:magnet:click', { - magnet, - ...this.getEventArgs(e, x, y), - }) - } - - onMagnetDblClick( - e: Dom.DoubleClickEvent, - magnet: Element, - x: number, - y: number, - ) { - this.notify('node:magnet:dblclick', { - magnet, - ...this.getEventArgs(e, x, y), - }) - } - - onMagnetContextMenu( - e: Dom.ContextMenuEvent, - magnet: Element, - x: number, - y: number, - ) { - this.notify('node:magnet:contextmenu', { - magnet, - ...this.getEventArgs(e, x, y), - }) - } - - onMagnetMouseDown( - e: Dom.MouseDownEvent, - magnet: Element, - x: number, - y: number, - ) { - this.startMagnetDragging(e, x, y) - } - - onCustomEvent(e: Dom.MouseDownEvent, name: string, x: number, y: number) { - this.notify('node:customevent', { name, ...this.getEventArgs(e, x, y) }) - super.onCustomEvent(e, name, x, y) - } - - protected prepareEmbedding(e: Dom.MouseMoveEvent) { - const graph = this.graph - const data = this.getEventData(e) - const node = data.cell || this.cell - const view = graph.findViewByCell(node) - const localPoint = graph.snapToGrid(e.clientX, e.clientY) - - this.notify('node:embed', { - e, - node, - view, - cell: node, - x: localPoint.x, - y: localPoint.y, - currentParent: node.getParent(), - }) - } - - processEmbedding(e: Dom.MouseMoveEvent, data: EventData.MovingTargetNode) { - const cell = data.cell || this.cell - const graph = data.graph || this.graph - const options = graph.options.embedding - const findParent = options.findParent - - let candidates = - typeof findParent === 'function' - ? ( - FunctionExt.call(findParent, graph, { - view: this, - node: this.cell, - }) as Cell[] - ).filter((c) => { - return ( - Cell.isCell(c) && - this.cell.id !== c.id && - !c.isDescendantOf(this.cell) - ) - }) - : graph.model.getNodesUnderNode(cell, { - by: findParent as Rectangle.KeyPoint, - }) - - // Picks the node with the highest `z` index - if (options.frontOnly) { - candidates = candidates.slice(-1) - } - - let newCandidateView = null - const prevCandidateView = data.candidateEmbedView - const validateEmbeding = options.validate - for (let i = candidates.length - 1; i >= 0; i -= 1) { - const candidate = candidates[i] - - if (prevCandidateView && prevCandidateView.cell.id === candidate.id) { - // candidate remains the same - newCandidateView = prevCandidateView - break - } else { - const view = candidate.findView(graph) as NodeView - if ( - FunctionExt.call(validateEmbeding, graph, { - child: this.cell, - parent: view.cell, - childView: this, - parentView: view, - }) - ) { - // flip to the new candidate - newCandidateView = view - break - } - } - } - - this.clearEmbedding(data) - if (newCandidateView) { - newCandidateView.highlight(null, { type: 'embedding' }) - } - data.candidateEmbedView = newCandidateView - - const localPoint = graph.snapToGrid(e.clientX, e.clientY) - this.notify('node:embedding', { - e, - cell, - node: cell, - view: graph.findViewByCell(cell), - x: localPoint.x, - y: localPoint.y, - currentParent: cell.getParent(), - candidateParent: newCandidateView ? newCandidateView.cell : null, - }) - } - - clearEmbedding(data: EventData.MovingTargetNode) { - const candidateView = data.candidateEmbedView - if (candidateView) { - candidateView.unhighlight(null, { type: 'embedding' }) - data.candidateEmbedView = null - } - } - - finalizeEmbedding(e: Dom.MouseUpEvent, data: EventData.MovingTargetNode) { - const cell = data.cell || this.cell - const graph = data.graph || this.graph - const view = graph.findViewByCell(cell) - const parent = cell.getParent() - const candidateView = data.candidateEmbedView - if (candidateView) { - // Candidate view is chosen to become the parent of the node. - candidateView.unhighlight(null, { type: 'embedding' }) - data.candidateEmbedView = null - if (parent == null || parent.id !== candidateView.cell.id) { - candidateView.cell.insertChild(cell, undefined, { ui: true }) - } - } else if (parent) { - parent.unembed(cell, { ui: true }) - } - - graph.model.getConnectedEdges(cell, { deep: true }).forEach((edge) => { - edge.updateParent({ ui: true }) - }) - - const localPoint = graph.snapToGrid(e.clientX, e.clientY) - - if (view) { - view.notify('node:embedded', { - e, - cell, - x: localPoint.x, - y: localPoint.y, - node: cell, - view: graph.findViewByCell(cell), - previousParent: parent, - currentParent: cell.getParent(), - }) - } - } - - getDelegatedView() { - let cell = this.cell - let view: NodeView = this // eslint-disable-line - - while (view) { - if (cell.isEdge()) { - break - } - if (!cell.hasParent() || view.can('stopDelegateOnDragging')) { - return view - } - cell = cell.getParent() as Entity - view = this.graph.findViewByCell(cell) as NodeView - } - - return null - } - - protected validateMagnet( - cellView: CellView, - magnet: Element, - e: Dom.MouseDownEvent | Dom.MouseEnterEvent, - ) { - if (magnet.getAttribute('magnet') !== 'passive') { - const validate = this.graph.options.connecting.validateMagnet - if (validate) { - return FunctionExt.call(validate, this.graph, { - e, - magnet, - view: cellView, - cell: cellView.cell, - }) - } - return true - } - return false - } - - protected startMagnetDragging(e: Dom.MouseDownEvent, x: number, y: number) { - if (!this.can('magnetConnectable')) { - return - } - - e.stopPropagation() - - const magnet = e.currentTarget - const graph = this.graph - - this.setEventData>(e, { - targetMagnet: magnet, - }) - - if (this.validateMagnet(this, magnet, e)) { - if (graph.options.magnetThreshold <= 0) { - this.startConnectting(e, magnet, x, y) - } - - this.setEventData>(e, { - action: 'magnet', - }) - this.stopPropagation(e) - } else { - this.onMouseDown(e, x, y) - } - - graph.view.delegateDragEvents(e, this) - } - - protected startConnectting( - e: Dom.MouseDownEvent, - magnet: Element, - x: number, - y: number, - ) { - this.graph.model.startBatch('add-edge') - const edgeView = this.createEdgeFromMagnet(magnet, x, y) - edgeView.notifyMouseDown(e, x, y) // backwards compatibility events - edgeView.setEventData( - e, - edgeView.prepareArrowheadDragging('target', { - x, - y, - isNewEdge: true, - fallbackAction: 'remove', - }), - ) - this.setEventData>(e, { edgeView }) - } - - protected getDefaultEdge(sourceView: CellView, sourceMagnet: Element) { - let edge: Edge | undefined | null | void - - const create = this.graph.options.connecting.createEdge - if (create) { - edge = FunctionExt.call(create, this.graph, { - sourceMagnet, - sourceView, - sourceCell: sourceView.cell, - }) - } - - return edge as Edge - } - - protected createEdgeFromMagnet(magnet: Element, x: number, y: number) { - const graph = this.graph - const model = graph.model - const edge = this.getDefaultEdge(this, magnet) - - edge.setSource({ - ...edge.getSource(), - ...this.getEdgeTerminal(magnet, x, y, edge, 'source'), - }) - edge.setTarget({ ...edge.getTarget(), x, y }) - edge.addTo(model, { async: false, ui: true }) - - return edge.findView(graph) as EdgeView - } - - protected dragMagnet(e: Dom.MouseMoveEvent, x: number, y: number) { - const data = this.getEventData(e) - const edgeView = data.edgeView - if (edgeView) { - edgeView.onMouseMove(e, x, y) - this.autoScrollGraph(e.clientX, e.clientY) - } else { - const graph = this.graph - const magnetThreshold = graph.options.magnetThreshold as any - const currentTarget = this.getEventTarget(e) - const targetMagnet = data.targetMagnet - - // magnetThreshold when the pointer leaves the magnet - if (magnetThreshold === 'onleave') { - if ( - targetMagnet === currentTarget || - targetMagnet.contains(currentTarget) - ) { - return - } - // eslint-disable-next-line no-lonely-if - } else { - // magnetThreshold defined as a number of movements - if (graph.view.getMouseMovedCount(e) <= magnetThreshold) { - return - } - } - this.startConnectting(e as any, targetMagnet, x, y) - } - } - - protected stopMagnetDragging(e: Dom.MouseUpEvent, x: number, y: number) { - const data = this.eventData(e) - const edgeView = data.edgeView - if (edgeView) { - edgeView.onMouseUp(e, x, y) - this.graph.model.stopBatch('add-edge') - } - } - - protected notifyUnhandledMouseDown( - e: Dom.MouseDownEvent, - x: number, - y: number, - ) { - this.notify('node:unhandled:mousedown', { - e, - x, - y, - view: this, - cell: this.cell, - node: this.cell, - }) - } - - protected notifyNodeMove( - name: Key, - e: Dom.MouseMoveEvent | Dom.MouseUpEvent, - x: number, - y: number, - cell: Cell, - ) { - const cells = [cell] - - // todo - // const selection = this.graph.selection.widget - // if (selection && selection.options.movable) { - // const selectedCells = this.graph.getSelectedCells() - // if (selectedCells.includes(cell)) { - // cells = selectedCells.filter((c: Cell) => c.isNode()) - // } - // } - - cells.forEach((c: Cell) => { - this.notify(name, { - e, - x, - y, - cell: c, - node: c, - view: c.findView(this.graph), - }) - }) - } - - protected getRestrictArea(view?: NodeView): Rectangle.RectangleLike | null { - const restrict = this.graph.options.translating.restrict - const area = - typeof restrict === 'function' - ? FunctionExt.call(restrict, this.graph, view!) - : restrict - - if (typeof area === 'number') { - return this.graph.transform.getGraphArea().inflate(area) - } - - if (area === true) { - return this.graph.transform.getGraphArea() - } - - return area || null - } - - protected startNodeDragging(e: Dom.MouseDownEvent, x: number, y: number) { - const targetView = this.getDelegatedView() - if (targetView == null || !targetView.can('nodeMovable')) { - return this.notifyUnhandledMouseDown(e, x, y) - } - - this.setEventData(e, { - targetView, - action: 'move', - }) - - const position = Point.create(targetView.cell.getPosition()) - targetView.setEventData(e, { - moving: false, - offset: position.diff(x, y), - restrict: this.getRestrictArea(targetView), - }) - } - - protected dragNode(e: Dom.MouseMoveEvent, x: number, y: number) { - const node = this.cell - const graph = this.graph - const gridSize = graph.getGridSize() - const data = this.getEventData(e) - const offset = data.offset - const restrict = data.restrict - - if (!data.moving) { - data.moving = true - this.addClass('node-moving') - this.notifyNodeMove('node:move', e, x, y, this.cell) - } - - this.autoScrollGraph(e.clientX, e.clientY) - - const posX = GeomUtil.snapToGrid(x + offset.x, gridSize) - const posY = GeomUtil.snapToGrid(y + offset.y, gridSize) - node.setPosition(posX, posY, { - restrict, - deep: true, - ui: true, - }) - - if (graph.options.embedding.enabled) { - if (!data.embedding) { - this.prepareEmbedding(e) - data.embedding = true - } - this.processEmbedding(e, data) - } - } - - protected stopNodeDragging(e: Dom.MouseUpEvent, x: number, y: number) { - const data = this.getEventData(e) - if (data.embedding) { - this.finalizeEmbedding(e, data) - } - - if (data.moving) { - this.removeClass('node-moving') - this.notifyNodeMove('node:moved', e, x, y, this.cell) - } - - data.moving = false - data.embedding = false - } - - // eslint-disable-next-line - protected autoScrollGraph(x: number, y: number) { - // todo - // const scroller = this.graph.scroller.widget - // if (scroller) { - // scroller.autoScroll(x, y) - // } - } - - // #endregion -} - -export namespace NodeView { - export interface Options extends CellView.Options {} - - export interface PortCache { - portElement: Element - portSelectors?: Markup.Selectors | null - portLabelElement?: Element - portLabelSelectors?: Markup.Selectors | null - portContentElement?: Element - portContentSelectors?: Markup.Selectors | null - } -} - -export namespace NodeView { - interface MagnetEventArgs { - magnet: Element - } - - export interface MouseEventArgs { - e: E - node: Node - cell: Node - view: NodeView - } - - export interface PositionEventArgs - extends MouseEventArgs, - CellView.PositionEventArgs {} - - export interface TranslateEventArgs extends PositionEventArgs {} - - export interface ResizeEventArgs extends PositionEventArgs {} - - export interface RotateEventArgs extends PositionEventArgs {} - - export interface EventArgs { - 'node:click': PositionEventArgs - 'node:dblclick': PositionEventArgs - 'node:contextmenu': PositionEventArgs - 'node:mousedown': PositionEventArgs - 'node:mousemove': PositionEventArgs - 'node:mouseup': PositionEventArgs - 'node:mouseover': MouseEventArgs - 'node:mouseout': MouseEventArgs - 'node:mouseenter': MouseEventArgs - 'node:mouseleave': MouseEventArgs - 'node:mousewheel': PositionEventArgs & - CellView.MouseDeltaEventArgs - - 'node:customevent': PositionEventArgs & { - name: string - } - - 'node:unhandled:mousedown': PositionEventArgs - - 'node:highlight': { - magnet: Element - view: NodeView - node: Node - cell: Node - options: CellView.HighlightOptions - } - 'node:unhighlight': EventArgs['node:highlight'] - - 'node:magnet:click': PositionEventArgs & MagnetEventArgs - 'node:magnet:dblclick': PositionEventArgs & - MagnetEventArgs - 'node:magnet:contextmenu': PositionEventArgs & - MagnetEventArgs - - 'node:move': TranslateEventArgs - 'node:moving': TranslateEventArgs - 'node:moved': TranslateEventArgs - - 'node:resize': ResizeEventArgs - 'node:resizing': ResizeEventArgs - 'node:resized': ResizeEventArgs - - 'node:rotate': RotateEventArgs - 'node:rotating': RotateEventArgs - 'node:rotated': RotateEventArgs - - 'node:embed': PositionEventArgs & { - currentParent: Node | null - } - 'node:embedding': PositionEventArgs & { - currentParent: Node | null - candidateParent: Node | null - } - 'node:embedded': PositionEventArgs & { - currentParent: Node | null - previousParent: Node | null - } - } -} - -export namespace NodeView { - export const toStringTag = `X6.${NodeView.name}` - - export function isNodeView(instance: any): instance is NodeView { - if (instance == null) { - return false - } - - if (instance instanceof NodeView) { - return true - } - - const tag = instance[Symbol.toStringTag] - const view = instance as NodeView - - if ( - (tag == null || tag === toStringTag) && - typeof view.isNodeView === 'function' && - typeof view.isEdgeView === 'function' && - typeof view.confirmUpdate === 'function' && - typeof view.update === 'function' && - typeof view.findPortElem === 'function' && - typeof view.resize === 'function' && - typeof view.rotate === 'function' && - typeof view.translate === 'function' - ) { - return true - } - - return false - } -} - -namespace EventData { - export type Mousemove = Moving | Magnet - - export interface Magnet { - action: 'magnet' - targetMagnet: Element - edgeView?: EdgeView - } - - export interface Moving { - action: 'move' - targetView: NodeView - } - - export interface MovingTargetNode { - moving: boolean - offset: Point.PointLike - restrict?: Rectangle.RectangleLike | null - embedding?: boolean - candidateEmbedView?: NodeView | null - cell?: Node - graph?: Graph - } -} - -NodeView.config({ - isSvgElement: true, - priority: 0, - bootstrap: ['render'], - actions: { - view: ['render'], - markup: ['render'], - attrs: ['update'], - size: ['resize', 'ports', 'tools'], - angle: ['rotate', 'tools'], - position: ['translate', 'tools'], - ports: ['ports'], - tools: ['tools'], - }, -}) - -NodeView.registry.register('node', NodeView, true) diff --git a/packages/x6-next/src/view/tool.ts b/packages/x6-next/src/view/tool.ts deleted file mode 100644 index 810686cf3d8..00000000000 --- a/packages/x6-next/src/view/tool.ts +++ /dev/null @@ -1,538 +0,0 @@ -import { Dom, ObjectExt, StringExt, KeyValue } from '@antv/x6-common' -import { NodeTool, EdgeTool } from '../registry/tool' -import { View } from './view' -import { CellView } from './cell' -import { Markup } from './markup' - -export class ToolsView extends View { - public tools: ToolsView.ToolItem[] | null - public options: ToolsView.Options - public cellView: CellView - public svgContainer: SVGGElement - public htmlContainer: HTMLDivElement - - public get name() { - return this.options.name - } - - public get graph() { - return this.cellView.graph - } - - public get cell() { - return this.cellView.cell - } - - protected get [Symbol.toStringTag]() { - return ToolsView.toStringTag - } - - constructor(options: ToolsView.Options = {}) { - super() - this.svgContainer = this.createContainer(true, options) as SVGGElement - this.htmlContainer = this.createContainer(false, options) as HTMLDivElement - this.config(options) - } - - protected createContainer(svg: boolean, options: ToolsView.Options) { - const container = svg - ? View.createElement('g', true) - : View.createElement('div', false) - Dom.addClass(container, this.prefixClassName('cell-tools')) - if (options.className) { - Dom.addClass(container, options.className) - } - return container - } - - config(options: ToolsView.ConfigOptions) { - this.options = { - ...this.options, - ...options, - } - - if (!CellView.isCellView(options.view) || options.view === this.cellView) { - return this - } - - this.cellView = options.view - - if (this.cell.isEdge()) { - Dom.addClass(this.svgContainer, this.prefixClassName('edge-tools')) - Dom.addClass(this.htmlContainer, this.prefixClassName('edge-tools')) - } else if (this.cell.isNode()) { - Dom.addClass(this.svgContainer, this.prefixClassName('node-tools')) - Dom.addClass(this.htmlContainer, this.prefixClassName('node-tools')) - } - - this.svgContainer.setAttribute('data-cell-id', this.cell.id) - this.htmlContainer.setAttribute('data-cell-id', this.cell.id) - - if (this.name) { - this.svgContainer.setAttribute('data-tools-name', this.name) - this.htmlContainer.setAttribute('data-tools-name', this.name) - } - - const tools = this.options.items - if (!Array.isArray(tools)) { - return this - } - - this.tools = [] - - const normalizedTools: typeof tools = [] - - tools.forEach((meta) => { - if (ToolsView.ToolItem.isToolItem(meta)) { - if (meta.name === 'vertices') { - normalizedTools.unshift(meta) - } else { - normalizedTools.push(meta) - } - } else { - const name = typeof meta === 'object' ? meta.name : meta - if (name === 'vertices') { - normalizedTools.unshift(meta) - } else { - normalizedTools.push(meta) - } - } - }) - - for (let i = 0; i < normalizedTools.length; i += 1) { - const meta = normalizedTools[i] - let tool: ToolsView.ToolItem | undefined - - if (ToolsView.ToolItem.isToolItem(meta)) { - tool = meta - } else { - const name = typeof meta === 'object' ? meta.name : meta - const args = typeof meta === 'object' ? meta.args || {} : {} - if (name) { - if (this.cell.isNode()) { - const ctor = NodeTool.registry.get(name) - if (ctor) { - tool = new ctor(args) // eslint-disable-line - } else { - return NodeTool.registry.onNotFound(name) - } - } else if (this.cell.isEdge()) { - const ctor = EdgeTool.registry.get(name) - if (ctor) { - tool = new ctor(args) // eslint-disable-line - } else { - return EdgeTool.registry.onNotFound(name) - } - } - } - } - - if (tool) { - tool.config(this.cellView, this) - tool.render() - const container = - tool.options.isSVGElement !== false - ? this.svgContainer - : this.htmlContainer - container.appendChild(tool.container) - this.tools.push(tool) - } - } - - return this - } - - update(options: ToolsView.UpdateOptions = {}) { - const tools = this.tools - if (tools) { - tools.forEach((tool) => { - if (options.toolId !== tool.cid && tool.isVisible()) { - tool.update() - } - }) - } - return this - } - - focus(focusedTool: ToolsView.ToolItem | null) { - const tools = this.tools - if (tools) { - tools.forEach((tool) => { - if (focusedTool === tool) { - tool.show() - } else { - tool.hide() - } - }) - } - - return this - } - - blur(blurredTool: ToolsView.ToolItem | null) { - const tools = this.tools - if (tools) { - tools.forEach((tool) => { - if (tool !== blurredTool && !tool.isVisible()) { - tool.show() - tool.update() - } - }) - } - - return this - } - - hide() { - return this.focus(null) - } - - show() { - return this.blur(null) - } - - remove() { - const tools = this.tools - if (tools) { - tools.forEach((tool) => tool.remove()) - this.tools = null - } - - Dom.remove(this.svgContainer) - Dom.remove(this.htmlContainer) - return super.remove() - } - - mount() { - const tools = this.tools - const cellView = this.cellView - if (cellView && tools) { - const hasSVG = tools.some((tool) => tool.options.isSVGElement !== false) - const hasHTML = tools.some((tool) => tool.options.isSVGElement === false) - if (hasSVG) { - const parent = this.options.local - ? cellView.container - : cellView.graph.view.decorator - parent.appendChild(this.svgContainer) - } - - if (hasHTML) { - this.graph.container.appendChild(this.htmlContainer) - } - } - return this - } -} - -export namespace ToolsView { - export interface Options extends ConfigOptions { - className?: string - } - - export interface ConfigOptions { - view?: CellView - name?: string - local?: boolean - items?: - | ( - | ToolItem - | string - | NodeTool.NativeNames - | NodeTool.NativeItem - | NodeTool.ManaualItem - | EdgeTool.NativeNames - | EdgeTool.NativeItem - | EdgeTool.ManaualItem - )[] - | null - } - - export interface UpdateOptions { - toolId?: string - } -} - -export namespace ToolsView { - export const toStringTag = `X6.${ToolsView.name}` - - export function isToolsView(instance: any): instance is ToolsView { - if (instance == null) { - return false - } - - if (instance instanceof ToolsView) { - return true - } - - const tag = instance[Symbol.toStringTag] - const view = instance as ToolsView - - if ( - (tag == null || tag === toStringTag) && - view.graph != null && - view.cell != null && - typeof view.config === 'function' && - typeof view.update === 'function' && - typeof view.focus === 'function' && - typeof view.blur === 'function' && - typeof view.show === 'function' && - typeof view.hide === 'function' - ) { - return true - } - - return false - } -} - -export namespace ToolsView { - export class ToolItem< - TargetView extends CellView = CellView, - Options extends ToolItem.Options = ToolItem.Options, - > extends View { - // #region static - - protected static defaults: ToolItem.Options = { - isSVGElement: true, - tagName: 'g', - } - - public static getDefaults() { - return this.defaults as T - } - - public static config( - options: Partial, - ) { - this.defaults = this.getOptions(options) - } - - public static getOptions( - options: Partial, - ): T { - return ObjectExt.merge( - ObjectExt.cloneDeep(this.getDefaults()), - options, - ) as T - } - - // #endregion - - public readonly options: Options - - public container: HTMLElement | SVGElement - - public parent: ToolsView - - protected cellView: TargetView - - protected visible = true - - protected childNodes: KeyValue - - public get graph() { - return this.cellView.graph - } - - public get cell() { - return this.cellView.cell - } - - public get name() { - return this.options.name - } - - protected get [Symbol.toStringTag]() { - return ToolItem.toStringTag - } - - constructor(options: Partial = {}) { - super() - - this.options = this.getOptions(options) - this.container = View.createElement( - this.options.tagName || 'g', - this.options.isSVGElement !== false, - ) - - Dom.addClass(this.container, this.prefixClassName('cell-tool')) - - if (typeof this.options.className === 'string') { - Dom.addClass(this.container, this.options.className) - } - - this.init() - } - - protected init() {} - - protected getOptions(options: Partial): Options { - const ctor = this.constructor as any as ToolItem - return ctor.getOptions(options) as Options - } - - delegateEvents() { - if (this.options.events) { - super.delegateEvents(this.options.events) - } - return this - } - - config(view: CellView, toolsView: ToolsView) { - this.cellView = view as TargetView - this.parent = toolsView - this.stamp(this.container) - - if (this.cell.isEdge()) { - Dom.addClass(this.container, this.prefixClassName('edge-tool')) - } else if (this.cell.isNode()) { - Dom.addClass(this.container, this.prefixClassName('node-tool')) - } - - if (this.name) { - this.container.setAttribute('data-tool-name', this.name) - } - - this.delegateEvents() - - return this - } - - render() { - this.empty() - - const markup = this.options.markup - if (markup) { - const meta = Markup.parseJSONMarkup(markup) - this.container.appendChild(meta.fragment) - this.childNodes = meta.selectors as KeyValue - } - - this.onRender() - return this - } - - protected onRender() {} - - update() { - return this - } - - protected stamp(elem: Element = this.container) { - if (elem) { - elem.setAttribute('data-cell-id', this.cellView.cell.id) - } - } - - show() { - this.container.style.display = '' - this.visible = true - return this - } - - hide() { - this.container.style.display = 'none' - this.visible = false - return this - } - - isVisible() { - return this.visible - } - - focus() { - const opacity = this.options.focusOpacity - if (opacity != null && Number.isFinite(opacity)) { - this.container.style.opacity = `${opacity}` - } - this.parent.focus(this) - return this - } - - blur() { - this.container.style.opacity = '' - this.parent.blur(this) - return this - } - - protected guard(evt: Dom.EventObject) { - if (this.graph == null || this.cellView == null) { - return true - } - - return this.graph.view.guard(evt, this.cellView) - } - } - - export namespace ToolItem { - export interface Options { - name?: string - tagName?: string - isSVGElement?: boolean - className?: string - markup?: Exclude - events?: View.Events | null - documentEvents?: View.Events | null - focusOpacity?: number - } - } - - export namespace ToolItem { - export type Definition = - | typeof ToolItem - | (new (options: ToolItem.Options) => ToolItem) - - let counter = 0 - function getClassName(name?: string) { - if (name) { - return StringExt.pascalCase(name) - } - counter += 1 - return `CustomTool${counter}` - } - - export function define(options: T) { - const tool = ObjectExt.createClass( - getClassName(options.name), - this as Definition, - ) as typeof ToolItem - - tool.config(options) - return tool - } - } - - export namespace ToolItem { - export const toStringTag = `X6.${ToolItem.name}` - - export function isToolItem(instance: any): instance is ToolItem { - if (instance == null) { - return false - } - - if (instance instanceof ToolItem) { - return true - } - - const tag = instance[Symbol.toStringTag] - const view = instance as ToolItem - - if ( - (tag == null || tag === toStringTag) && - view.graph != null && - view.cell != null && - typeof view.config === 'function' && - typeof view.update === 'function' && - typeof view.focus === 'function' && - typeof view.blur === 'function' && - typeof view.show === 'function' && - typeof view.hide === 'function' && - typeof view.isVisible === 'function' - ) { - return true - } - - return false - } - } -} diff --git a/packages/x6-next/src/view/view.ts b/packages/x6-next/src/view/view.ts deleted file mode 100644 index 1dbbeecf95f..00000000000 --- a/packages/x6-next/src/view/view.ts +++ /dev/null @@ -1,452 +0,0 @@ -import { Dom, KeyValue, Basecoat } from '@antv/x6-common' -import { Config } from '../config' -import { Markup } from './markup' -import { Attr } from '../registry' - -export abstract class View extends Basecoat { - public readonly cid: string - public container: Element - protected selectors: Markup.Selectors - - public get priority() { - return 2 - } - - constructor() { - super() - this.cid = Private.uniqueId() - View.views[this.cid] = this - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - confirmUpdate(flag: number, options: any): number { - return 0 - } - - empty(elem: Element = this.container) { - Dom.empty(elem) - return this - } - - unmount(elem: Element = this.container) { - Dom.remove(elem) - return this - } - - remove(elem: Element = this.container) { - if (elem === this.container) { - this.removeEventListeners(document) - this.onRemove() - delete View.views[this.cid] - } - this.unmount(elem) - return this - } - - protected onRemove() {} - - setClass(className: string | string[], elem: Element = this.container) { - elem.classList.value = Array.isArray(className) - ? className.join(' ') - : className - } - - addClass(className: string | string[], elem: Element = this.container) { - Dom.addClass( - elem, - Array.isArray(className) ? className.join(' ') : className, - ) - return this - } - - removeClass(className: string | string[], elem: Element = this.container) { - Dom.removeClass( - elem, - Array.isArray(className) ? className.join(' ') : className, - ) - return this - } - - setStyle( - style: Record, - elem: Element = this.container, - ) { - Dom.css(elem, style) - return this - } - - setAttrs(attrs?: Attr.SimpleAttrs | null, elem: Element = this.container) { - if (attrs != null && elem != null) { - Dom.attr(elem, attrs) - } - return this - } - - /** - * Returns the value of the specified attribute of `node`. - * - * If the node does not set a value for attribute, start recursing up - * the DOM tree from node to lookup for attribute at the ancestors of - * node. If the recursion reaches CellView's root node and attribute - * is not found even there, return `null`. - */ - findAttr(attrName: string, elem: Element = this.container) { - let current = elem - while (current && current.nodeType === 1) { - const value = current.getAttribute(attrName) - if (value != null) { - return value - } - - if (current === this.container) { - return null - } - - current = current.parentNode as Element - } - - return null - } - - find( - selector?: string, - rootElem: Element = this.container, - selectors: Markup.Selectors = this.selectors, - ) { - return View.find(selector, rootElem, selectors).elems - } - - findOne( - selector?: string, - rootElem: Element = this.container, - selectors: Markup.Selectors = this.selectors, - ) { - const nodes = this.find(selector, rootElem, selectors) - return nodes.length > 0 ? nodes[0] : null - } - - findByAttr(attrName: string, elem: Element = this.container) { - let node = elem - while (node && node.getAttribute) { - const val = node.getAttribute(attrName) - if ((val != null || node === this.container) && val !== 'false') { - return node - } - node = node.parentNode as Element - } - - // If the overall cell has set `magnet === false`, then returns - // `null` to announce there is no magnet found for this cell. - // This is especially useful to set on cells that have 'ports'. - // In this case, only the ports have set `magnet === true` and the - // overall element has `magnet === false`. - return null - } - - getSelector(elem: Element, prevSelector?: string): string | undefined { - let selector - - if (elem === this.container) { - if (typeof prevSelector === 'string') { - selector = `> ${prevSelector}` - } - return selector - } - - if (elem) { - const nth = Dom.index(elem) + 1 - selector = `${elem.tagName.toLowerCase()}:nth-child(${nth})` - if (prevSelector) { - selector += ` > ${prevSelector}` - } - - selector = this.getSelector(elem.parentNode as Element, selector) - } - - return selector - } - - prefixClassName(className: string) { - return Config.prefix(className) - } - - delegateEvents(events: View.Events, append?: boolean) { - if (events == null) { - return this - } - - if (!append) { - this.undelegateEvents() - } - - const splitter = /^(\S+)\s*(.*)$/ - Object.keys(events).forEach((key) => { - const match = key.match(splitter) - if (match == null) { - return - } - - const method = this.getEventHandler(events[key]) - if (typeof method === 'function') { - this.delegateEvent(match[1], match[2], method) - } - }) - - return this - } - - undelegateEvents() { - Dom.Event.off(this.container, this.getEventNamespace()) - return this - } - - delegateDocumentEvents(events: View.Events, data?: KeyValue) { - this.addEventListeners(document, events, data) - return this - } - - undelegateDocumentEvents() { - this.removeEventListeners(document) - return this - } - - protected delegateEvent( - eventName: string, - selector: string | Record, - listener: any, - ) { - Dom.Event.on( - this.container, - eventName + this.getEventNamespace(), - selector, - listener, - ) - return this - } - - protected undelegateEvent( - eventName: string, - selector: string, - listener: any, - ): this - protected undelegateEvent(eventName: string): this - protected undelegateEvent(eventName: string, listener: any): this - protected undelegateEvent( - eventName: string, - selector?: string | any, - listener?: any, - ) { - const name = eventName + this.getEventNamespace() - if (selector == null) { - Dom.Event.off(this.container, name) - } else if (typeof selector === 'string') { - Dom.Event.off(this.container, name, selector, listener) - } else { - Dom.Event.off(this.container, name, selector) - } - return this - } - - protected addEventListeners( - elem: Element | Document, - events: View.Events, - data?: KeyValue, - ) { - if (events == null) { - return this - } - - const ns = this.getEventNamespace() - Object.keys(events).forEach((eventName) => { - const method = this.getEventHandler(events[eventName]) - if (typeof method === 'function') { - Dom.Event.on( - elem as Element, - eventName + ns, - data, - method as any, - ) - } - }) - - return this - } - - protected removeEventListeners(elem: Element | Document) { - if (elem != null) { - Dom.Event.off(elem as Element, this.getEventNamespace()) - } - return this - } - - protected getEventNamespace() { - return `.${Config.prefixCls}-event-${this.cid}` - } - - // eslint-disable-next-line - protected getEventHandler(handler: string | Function) { - // eslint-disable-next-line - let method: Function | undefined - if (typeof handler === 'string') { - const fn = (this as any)[handler] - if (typeof fn === 'function') { - method = (...args: any) => fn.call(this, ...args) - } - } else { - method = (...args: any) => handler.call(this, ...args) - } - - return method - } - - getEventTarget(e: Dom.EventObject, options: { fromPoint?: boolean } = {}) { - // Touchmove/Touchend event's target is not reflecting the element - // under the coordinates as mousemove does. - // It holds the element when a touchstart triggered. - const { target, type, clientX = 0, clientY = 0 } = e - if (options.fromPoint || type === 'touchmove' || type === 'touchend') { - return document.elementFromPoint(clientX, clientY) - } - - return target - } - - stopPropagation(e: Dom.EventObject) { - this.setEventData(e, { propagationStopped: true }) - return this - } - - isPropagationStopped(e: Dom.EventObject) { - return this.getEventData(e).propagationStopped === true - } - - getEventData(e: Dom.EventObject): T { - return this.eventData(e) - } - - setEventData(e: Dom.EventObject, data: T): T { - return this.eventData(e, data) - } - - protected eventData(e: Dom.EventObject, data?: T): T { - if (e == null) { - throw new TypeError('Event object required') - } - - let currentData = e.data - const key = `__${this.cid}__` - - // get - if (data == null) { - if (currentData == null) { - return {} as T - } - return currentData[key] || {} - } - - // set - if (currentData == null) { - currentData = e.data = {} - } - - if (currentData[key] == null) { - currentData[key] = { ...data } - } else { - currentData[key] = { ...currentData[key], ...data } - } - - return currentData[key] - } - - normalizeEvent(evt: T) { - return View.normalizeEvent(evt) - } -} - -export namespace View { - export type Events = KeyValue // eslint-disable-line -} - -export namespace View { - export function createElement(tagName?: string, isSvgElement?: boolean) { - return isSvgElement - ? Dom.createSvgElement(tagName || 'g') - : (Dom.createElementNS(tagName || 'div') as HTMLElement) - } - - export function find( - selector: string | null | undefined, - rootElem: Element, - selectors: Markup.Selectors, - ): { isCSSSelector?: boolean; elems: Element[] } { - if (!selector || selector === '.') { - return { elems: [rootElem] } - } - - if (selectors) { - const nodes = selectors[selector] - if (nodes) { - return { elems: Array.isArray(nodes) ? nodes : [nodes] } - } - } - - if (Config.useCSSSelector) { - return { - isCSSSelector: true, - // $(rootElem).find(selector).toArray() as Element[] todo - elems: Array.prototype.slice.call(rootElem.querySelectorAll(selector)), - } - } - - return { elems: [] } - } - - export function normalizeEvent(evt: T) { - let normalizedEvent = evt - const originalEvent = evt.originalEvent as TouchEvent - const touchEvt: any = - originalEvent && - originalEvent.changedTouches && - originalEvent.changedTouches[0] - - if (touchEvt) { - // eslint-disable-next-line no-restricted-syntax - for (const key in evt) { - // copy all the properties from the input event that are not - // defined on the touch event (functions included). - if (touchEvt[key] === undefined) { - touchEvt[key] = (evt as any)[key] - } - } - normalizedEvent = touchEvt - } - - // IE: evt.target could be set to SVGElementInstance for SVGUseElement - const target = normalizedEvent.target - if (target) { - const useElement = target.correspondingUseElement - if (useElement) { - normalizedEvent.target = useElement - } - } - - return normalizedEvent - } -} - -export namespace View { - export const views: { [cid: string]: View } = {} - - export function getView(cid: string) { - return views[cid] || null - } -} - -namespace Private { - let counter = 0 - export function uniqueId() { - const id = `v${counter}` - counter += 1 - return id - } -} diff --git a/packages/x6-next/tsconfig.json b/packages/x6-next/tsconfig.json deleted file mode 100644 index 4082f16a5d9..00000000000 --- a/packages/x6-next/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../tsconfig.json" -} diff --git a/packages/x6-plugin-keyboard/package.json b/packages/x6-plugin-keyboard/package.json index 1f8ec1fc78c..ee97d5f7386 100644 --- a/packages/x6-plugin-keyboard/package.json +++ b/packages/x6-plugin-keyboard/package.json @@ -51,7 +51,7 @@ "mousetrap": "^1.6.5" }, "peerDependencies": { - "@antv/x6-next": "2.0.6-beta.0" + "@antv/x6": ">=2.0.6-beta.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^20.0.0", diff --git a/packages/x6-plugin-keyboard/rollup.config.js b/packages/x6-plugin-keyboard/rollup.config.js index 8bc9a0a00f3..1624bad17e2 100644 --- a/packages/x6-plugin-keyboard/rollup.config.js +++ b/packages/x6-plugin-keyboard/rollup.config.js @@ -8,10 +8,10 @@ export default config({ file: 'dist/x6-plugin-keyboard.js', sourcemap: true, globals: { - '@antv/x6-next': 'X6', + '@antv/x6': 'X6', '@antv/x6-common': 'X6Common', }, }, ], - external: ['@antv/x6-next', '@antv/x6-common'], + external: ['@antv/x6', '@antv/x6-common'], }) diff --git a/packages/x6-plugin-keyboard/src/index.ts b/packages/x6-plugin-keyboard/src/index.ts index 5da6d85c4b0..1b3d02673ba 100644 --- a/packages/x6-plugin-keyboard/src/index.ts +++ b/packages/x6-plugin-keyboard/src/index.ts @@ -1,5 +1,5 @@ import { Disposable } from '@antv/x6-common' -import { Graph } from '@antv/x6-next' +import { Graph } from '@antv/x6' import { KeyboardImpl } from './keyboard' export class Keyboard extends Disposable { diff --git a/packages/x6-plugin-keyboard/src/keyboard.ts b/packages/x6-plugin-keyboard/src/keyboard.ts index a1a52782f4d..ce75b6c0444 100644 --- a/packages/x6-plugin-keyboard/src/keyboard.ts +++ b/packages/x6-plugin-keyboard/src/keyboard.ts @@ -1,6 +1,6 @@ import Mousetrap from 'mousetrap' import { Dom, FunctionExt, Disposable, IDisablable } from '@antv/x6-common' -import { Graph, EventArgs } from '@antv/x6-next' +import { Graph, EventArgs } from '@antv/x6' export class KeyboardImpl extends Disposable implements IDisablable { public readonly target: HTMLElement | Document @@ -120,7 +120,7 @@ export class KeyboardImpl extends Disposable implements IDisablable { return false } - isInputEvent(e: KeyboardEvent | JQuery.MouseUpEvent) { + isInputEvent(e: KeyboardEvent | Dom.MouseUpEvent) { const target = e.target as Element const tagName = target?.tagName?.toLowerCase() return ['input', 'textarea'].includes(tagName) diff --git a/packages/x6-plugin-scroller/package.json b/packages/x6-plugin-scroller/package.json index 1a42630bb45..041bd4db5de 100644 --- a/packages/x6-plugin-scroller/package.json +++ b/packages/x6-plugin-scroller/package.json @@ -49,7 +49,7 @@ "@antv/x6-package-json/rollup.json" ], "peerDependencies": { - "@antv/x6-next": ">=2.0.6-beta.0" + "@antv/x6": ">=2.0.6-beta.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^20.0.0", diff --git a/packages/x6-plugin-scroller/rollup.config.js b/packages/x6-plugin-scroller/rollup.config.js index b72040b127f..87f8e7468e4 100644 --- a/packages/x6-plugin-scroller/rollup.config.js +++ b/packages/x6-plugin-scroller/rollup.config.js @@ -8,11 +8,11 @@ export default config({ file: 'dist/x6-plugin-scroller.js', sourcemap: true, globals: { - '@antv/x6-next': 'X6', + '@antv/x6': 'X6', '@antv/x6-common': 'X6Common', '@antv/x6-geometry': 'X6Geometry', }, }, ], - external: ['@antv/x6-next', '@antv/x6-common', '@antv/x6-geometry'], + external: ['@antv/x6', '@antv/x6-common', '@antv/x6-geometry'], }) diff --git a/packages/x6-plugin-scroller/src/index.ts b/packages/x6-plugin-scroller/src/index.ts index 91c6ed8aa06..24d8dd9045e 100644 --- a/packages/x6-plugin-scroller/src/index.ts +++ b/packages/x6-plugin-scroller/src/index.ts @@ -5,7 +5,7 @@ import { TransformManager, Cell, BackgroundManager, -} from '@antv/x6-next' +} from '@antv/x6' import { Rectangle, Point } from '@antv/x6-geometry' import { ScrollerImpl } from './scroller' import { content } from './style/raw' diff --git a/packages/x6-plugin-scroller/src/scroller.ts b/packages/x6-plugin-scroller/src/scroller.ts index 56e97ae7ebd..447859b3e36 100644 --- a/packages/x6-plugin-scroller/src/scroller.ts +++ b/packages/x6-plugin-scroller/src/scroller.ts @@ -15,7 +15,7 @@ import { TransformManager, BackgroundManager, Util, -} from '@antv/x6-next' +} from '@antv/x6' export class ScrollerImpl extends View { private readonly content: HTMLDivElement diff --git a/packages/x6-react-shape/package.json b/packages/x6-react-shape/package.json index 401cca222fb..e8d5f213efb 100644 --- a/packages/x6-react-shape/package.json +++ b/packages/x6-react-shape/package.json @@ -46,7 +46,7 @@ ], "peerDependencies": { "@antv/x6-common": ">=2.0.6-beta.0", - "@antv/x6-next": ">=2.0.6-beta.0", + "@antv/x6": ">=2.0.6-beta.0", "react": "^18.0.0", "react-dom": "^18.0.0" }, diff --git a/packages/x6-react-shape/rollup.config.js b/packages/x6-react-shape/rollup.config.js index 02349ae2b5f..da34cfeaab9 100644 --- a/packages/x6-react-shape/rollup.config.js +++ b/packages/x6-react-shape/rollup.config.js @@ -10,10 +10,10 @@ export default config({ globals: { react: 'React', 'react-dom': 'ReactDom', - '@antv/x6-next': 'X6', + '@antv/x6': 'X6', '@antv/x6-common': 'X6Common', }, }, ], - external: ['@antv/x6-next', '@antv/x6-common', 'react', 'react-dom'], + external: ['@antv/x6', '@antv/x6-common', 'react', 'react-dom'], }) diff --git a/packages/x6-react-shape/src/node.ts b/packages/x6-react-shape/src/node.ts index aa688435cc2..3551252b017 100644 --- a/packages/x6-react-shape/src/node.ts +++ b/packages/x6-react-shape/src/node.ts @@ -1,5 +1,5 @@ import { ObjectExt } from '@antv/x6-common' -import { Markup, Node } from '@antv/x6-next' +import { Markup, Node } from '@antv/x6' export class ReactShape< Properties extends ReactShape.Properties = ReactShape.Properties, diff --git a/packages/x6-react-shape/src/registry.ts b/packages/x6-react-shape/src/registry.ts index 42aaf3ca505..c4f9cf13c9b 100644 --- a/packages/x6-react-shape/src/registry.ts +++ b/packages/x6-react-shape/src/registry.ts @@ -1,5 +1,5 @@ import React from 'react' -import { Graph, Node } from '@antv/x6-next' +import { Graph, Node } from '@antv/x6' export type ReactShapeConfig = Node.Properties & { shape: string diff --git a/packages/x6-react-shape/src/view.ts b/packages/x6-react-shape/src/view.ts index 10349f19935..23e290daa17 100644 --- a/packages/x6-react-shape/src/view.ts +++ b/packages/x6-react-shape/src/view.ts @@ -1,7 +1,7 @@ import React, { ReactPortal } from 'react' import { createPortal } from 'react-dom' import { createRoot, Root } from 'react-dom/client' -import { NodeView } from '@antv/x6-next' +import { NodeView } from '@antv/x6' import { Dom } from '@antv/x6-common' import { ReactShape } from './node' import { Portal } from './portal' diff --git a/packages/x6-vector/CHANGELOG.md b/packages/x6-vector/CHANGELOG.md deleted file mode 100644 index ad2bbd642d7..00000000000 --- a/packages/x6-vector/CHANGELOG.md +++ /dev/null @@ -1,212 +0,0 @@ -# @antv/x6-vector [1.3.0](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.3.0) (2021-12-18) - - -### Bug Fixes - -* 🐛 update x6-vector version ([#1656](https://github.com/antvis/x6/issues/1656)) ([d4d2125](https://github.com/antvis/x6/commit/d4d21251cc42d263327ea72edb8a038d7ef71c89)) - - -### Features - -* ✨ update x6 and x6-vector version ([#1659](https://github.com/antvis/x6/issues/1659)) ([a199b59](https://github.com/antvis/x6/commit/a199b590d5f108b51162e276b432fbb3737d2c14)) - -# @antv/x6-vector [1.3.0](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.3.0) (2021-12-17) - - -### Bug Fixes - -* 🐛 update x6-vector version ([#1656](https://github.com/antvis/x6/issues/1656)) ([d4d2125](https://github.com/antvis/x6/commit/d4d21251cc42d263327ea72edb8a038d7ef71c89)) - - -### Features - -* ✨ update x6 and x6-vector version ([#1659](https://github.com/antvis/x6/issues/1659)) ([a199b59](https://github.com/antvis/x6/commit/a199b590d5f108b51162e276b432fbb3737d2c14)) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-12-17) - - -### Bug Fixes - -* 🐛 update x6-vector version ([#1656](https://github.com/antvis/x6/issues/1656)) ([d4d2125](https://github.com/antvis/x6/commit/d4d21251cc42d263327ea72edb8a038d7ef71c89)) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-12-17) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-12-17) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-12-17) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-12-13) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-11-21) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-11-20) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-11-16) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-11-15) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-11-14) - -## @antv/x6-vector [1.2.4](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.3...@antv/x6-vector@1.2.4) (2021-11-08) - -## @antv/x6-vector [1.2.3](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.2...@antv/x6-vector@1.2.3) (2021-09-22) - -## @antv/x6-vector [1.2.3](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.2...@antv/x6-vector@1.2.3) (2021-09-13) - -## @antv/x6-vector [1.2.2](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.1...@antv/x6-vector@1.2.2) (2021-07-05) - -## @antv/x6-vector [1.2.1](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.0...@antv/x6-vector@1.2.1) (2021-06-18) - -## @antv/x6-vector [1.2.1](https://github.com/antvis/x6/compare/@antv/x6-vector@1.2.0...@antv/x6-vector@1.2.1) (2021-06-17) - -# @antv/x6-vector [1.2.0](https://github.com/antvis/x6/compare/@antv/x6-vector@1.1.1...@antv/x6-vector@1.2.0) (2021-06-17) - - -### Bug Fixes - -* 🐛 alerts on lgtm.com ([#1104](https://github.com/antvis/x6/issues/1104)) ([6eb34b1](https://github.com/antvis/x6/commit/6eb34b1d9a25462593ba5e4a69995cca5211bc0c)) -* 🐛 typos ([f7b1720](https://github.com/antvis/x6/commit/f7b1720280910bbfa1360494853fc0a511a22fdd)) -* update dependencies and fix type errors ([#1103](https://github.com/antvis/x6/issues/1103)) ([056b862](https://github.com/antvis/x6/commit/056b862b4efe7dbdc559cac7194c2453996acc07)) - - -### Features - -* ✨ add filter elements ([7770bfe](https://github.com/antvis/x6/commit/7770bfeedd15d6c81fa4b48aaede2118e31295dc)) - -# @antv/x6-vector [1.2.0](https://github.com/antvis/x6/compare/@antv/x6-vector@1.1.1...@antv/x6-vector@1.2.0) (2021-06-16) - - -### Bug Fixes - -* 🐛 typos ([f7b1720](https://github.com/antvis/x6/commit/f7b1720280910bbfa1360494853fc0a511a22fdd)) -* update dependencies and fix type errors ([#1103](https://github.com/antvis/x6/issues/1103)) ([056b862](https://github.com/antvis/x6/commit/056b862b4efe7dbdc559cac7194c2453996acc07)) - - -### Features - -* ✨ add filter elements ([7770bfe](https://github.com/antvis/x6/commit/7770bfeedd15d6c81fa4b48aaede2118e31295dc)) - -## @antv/x6-vector [1.1.2](https://github.com/antvis/x6/compare/@antv/x6-vector@1.1.1...@antv/x6-vector@1.1.2) (2021-06-15) - - -### Bug Fixes - -* 🐛 typos ([f7b1720](https://github.com/antvis/x6/commit/f7b1720280910bbfa1360494853fc0a511a22fdd)) - -## @antv/x6-vector [1.1.1](https://github.com/antvis/x6/compare/@antv/x6-vector@1.1.0...@antv/x6-vector@1.1.1) (2021-06-15) - -## @antv/x6-vector [1.1.1](https://github.com/antvis/x6/compare/@antv/x6-vector@1.1.0...@antv/x6-vector@1.1.1) (2021-06-11) - -# @antv/x6-vector [1.1.0](https://github.com/antvis/x6/compare/@antv/x6-vector@1.0.3...@antv/x6-vector@1.1.0) (2021-06-09) - - -### Bug Fixes - -* 🐛 do not overwrite constructor when apply mixins ([d47267e](https://github.com/antvis/x6/commit/d47267e5a390ac51704b2289b241c9bf0a9c993b)) -* 🐛 fix apply mixins ([7c4dd17](https://github.com/antvis/x6/commit/7c4dd17a5a24312c38b0a3fb0ddf7d84594cc7fd)) -* 🐛 fix cached data type for adopter ([e5ed030](https://github.com/antvis/x6/commit/e5ed030176afd7e5e64c554d4af79a9414e32b07)) -* 🐛 fix extends detection errors by override lib.dom.d.ts ([d5eef84](https://github.com/antvis/x6/commit/d5eef840c0348040d91d4cf791cda54cfda5aa59)) -* 🐛 fix types ([fbfe949](https://github.com/antvis/x6/commit/fbfe949d6ffd0501defb5dfd863ddb5804530da8)) -* 🐛 should cache the instance and restore affix after inited ([8f9a4f6](https://github.com/antvis/x6/commit/8f9a4f66591d238396e8b7575ee21d82d18a2184)) -* 🐛 should trim the html or selector ([ebf3384](https://github.com/antvis/x6/commit/ebf33843127af1cfa6df6a43a1f03a63eea6fe5f)) -* 🐛 typos ([a728cf1](https://github.com/antvis/x6/commit/a728cf175f51d58d971e83f90745006aa40a090f)) - - -### Features - -* ✨ add animating ([9a45e3f](https://github.com/antvis/x6/commit/9a45e3f27467240711190fbb7451861005329174)) -* ✨ add util to test requestAnimationFrame ([739ef34](https://github.com/antvis/x6/commit/739ef34df20c0e5fbdebc3a5d58387d24a5e4afa)) -* ✨ auto detect vector type by tagName ([d6e0537](https://github.com/antvis/x6/commit/d6e053721f5fc3085c2a46831c11dad381ddb412)) - -# @antv/x6-vector [1.1.0](https://github.com/antvis/x6/compare/@antv/x6-vector@1.0.3...@antv/x6-vector@1.1.0) (2021-06-09) - - -### Bug Fixes - -* 🐛 do not overwrite constructor when apply mixins ([d47267e](https://github.com/antvis/x6/commit/d47267e5a390ac51704b2289b241c9bf0a9c993b)) -* 🐛 fix apply mixins ([7c4dd17](https://github.com/antvis/x6/commit/7c4dd17a5a24312c38b0a3fb0ddf7d84594cc7fd)) -* 🐛 fix cached data type for adopter ([e5ed030](https://github.com/antvis/x6/commit/e5ed030176afd7e5e64c554d4af79a9414e32b07)) -* 🐛 fix extends detection errors by override lib.dom.d.ts ([d5eef84](https://github.com/antvis/x6/commit/d5eef840c0348040d91d4cf791cda54cfda5aa59)) -* 🐛 fix types ([fbfe949](https://github.com/antvis/x6/commit/fbfe949d6ffd0501defb5dfd863ddb5804530da8)) -* 🐛 should cache the instance and restore affix after inited ([8f9a4f6](https://github.com/antvis/x6/commit/8f9a4f66591d238396e8b7575ee21d82d18a2184)) -* 🐛 should trim the html or selector ([ebf3384](https://github.com/antvis/x6/commit/ebf33843127af1cfa6df6a43a1f03a63eea6fe5f)) -* 🐛 typos ([a728cf1](https://github.com/antvis/x6/commit/a728cf175f51d58d971e83f90745006aa40a090f)) - - -### Features - -* ✨ add animating ([9a45e3f](https://github.com/antvis/x6/commit/9a45e3f27467240711190fbb7451861005329174)) -* ✨ add util to test requestAnimationFrame ([739ef34](https://github.com/antvis/x6/commit/739ef34df20c0e5fbdebc3a5d58387d24a5e4afa)) -* ✨ auto detect vector type by tagName ([d6e0537](https://github.com/antvis/x6/commit/d6e053721f5fc3085c2a46831c11dad381ddb412)) - -## @antv/x6-vector [1.0.3](https://github.com/antvis/x6/compare/@antv/x6-vector@1.0.2...@antv/x6-vector@1.0.3) (2021-04-01) - -## @antv/x6-vector [1.0.3](https://github.com/antvis/x6/compare/@antv/x6-vector@1.0.2...@antv/x6-vector@1.0.3) (2021-03-30) - -## @antv/x6-vector [1.0.2](https://github.com/antvis/x6/compare/@antv/x6-vector@1.0.1...@antv/x6-vector@1.0.2) (2021-03-30) - -## @antv/x6-vector [1.0.2](https://github.com/antvis/x6/compare/@antv/x6-vector@1.0.1...@antv/x6-vector@1.0.2) (2021-03-30) - -## @antv/x6-vector [1.0.1](https://github.com/antvis/x6/compare/@antv/x6-vector@1.0.0...@antv/x6-vector@1.0.1) (2021-03-28) - -## @antv/x6-vector [1.0.1](https://github.com/antvis/x6/compare/@antv/x6-vector@1.0.0...@antv/x6-vector@1.0.1) (2021-03-25) - -# @antv/x6-vector 1.0.0 (2021-03-24) - - -### Bug Fixes - -* 🐛 fix karma can not process lodash-es ([f7ae6b1](https://github.com/antvis/x6/commit/f7ae6b1f6b961a01c58d8827a9aaa2d5a984a6e0)) - - -### Features - -* ✨ add library for manipulating and animating SVG ([a67b4d2](https://github.com/antvis/x6/commit/a67b4d2e44395d9422664760afa0adaa2635813d)) - -# @antv/x6-vector 1.0.0 (2021-03-23) - - -### Bug Fixes - -* 🐛 fix karma can not process lodash-es ([f7ae6b1](https://github.com/antvis/x6/commit/f7ae6b1f6b961a01c58d8827a9aaa2d5a984a6e0)) - - -### Features - -* ✨ add library for manipulating and animating SVG ([a67b4d2](https://github.com/antvis/x6/commit/a67b4d2e44395d9422664760afa0adaa2635813d)) - -# @antv/x6-vector 1.0.0 (2021-03-23) - - -### Bug Fixes - -* 🐛 fix karma can not process lodash-es ([f7ae6b1](https://github.com/antvis/x6/commit/f7ae6b1f6b961a01c58d8827a9aaa2d5a984a6e0)) - - -### Features - -* ✨ add library for manipulating and animating SVG ([a67b4d2](https://github.com/antvis/x6/commit/a67b4d2e44395d9422664760afa0adaa2635813d)) - -# @antv/x6-vector 1.0.0 (2021-03-23) - - -### Bug Fixes - -* 🐛 fix karma can not process lodash-es ([f7ae6b1](https://github.com/antvis/x6/commit/f7ae6b1f6b961a01c58d8827a9aaa2d5a984a6e0)) - - -### Features - -* ✨ add library for manipulating and animating SVG ([a67b4d2](https://github.com/antvis/x6/commit/a67b4d2e44395d9422664760afa0adaa2635813d)) - -# @antv/x6-vector 1.0.0 (2021-03-23) - - -### Bug Fixes - -* 🐛 fix karma can not process lodash-es ([f7ae6b1](https://github.com/antvis/x6/commit/f7ae6b1f6b961a01c58d8827a9aaa2d5a984a6e0)) - - -### Features - -* ✨ add library for manipulating and animating SVG ([a67b4d2](https://github.com/antvis/x6/commit/a67b4d2e44395d9422664760afa0adaa2635813d)) diff --git a/packages/x6-vector/README.md b/packages/x6-vector/README.md deleted file mode 100644 index 0e0b2256644..00000000000 --- a/packages/x6-vector/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# x6-vector - -Lightweight library for manipulating and animating SVG. - -MIT License -Language -PRs Welcome -website -build -coverage -Language grade: JavaScript - -NPM Package -NPM Downloads -Dependency Status -devDependencies Status diff --git a/packages/x6-vector/karma.conf.js b/packages/x6-vector/karma.conf.js deleted file mode 100644 index 3c2356ff62c..00000000000 --- a/packages/x6-vector/karma.conf.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = (config) => - require('../../configs/karma-config.js')( - config, - { - files: [{ pattern: 'src/**/*.ts' }], - }, - { - include: ['./src/**/*.ts', '../../node_modules/csstype/**/*'], - }, - ) diff --git a/packages/x6-vector/package.json b/packages/x6-vector/package.json deleted file mode 100644 index 593aca52226..00000000000 --- a/packages/x6-vector/package.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "private": true, - "version": "1.3.0", - "name": "@antv/x6-vector", - "description": "Lightweight library for manipulating and animating SVG.", - "main": "lib/index.js", - "module": "es/index.js", - "unpkg": "dist/x6-vector.js", - "jsdelivr": "dist/x6-vector.js", - "types": "lib/index.d.ts", - "files": [ - "dist", - "es", - "lib" - ], - "keywords": [ - "vector", - "svg", - "x6", - "antv" - ], - "scripts": { - "clean:build": "rimraf dist es lib", - "clean:coverage": "rimraf ./test/coverage", - "clean": "run-p clean:build clean:coverage", - "lint": "eslint 'src/**/*.{js,ts}?(x)' --fix", - "build:esm": "tsc --module esnext --target es2015 --outDir ./es", - "build:cjs": "tsc --module commonjs --target es5 --outDir ./lib", - "build:umd": "rollup -c", - "build:version": "node ../../scripts/version.js", - "build:csstype": "node ./scripts/csstype.js", - "build:watch": "yarn build:esm --w", - "build:watch:esm": "yarn build:esm --w", - "build:watch:cjs": "yarn build:cjs --w", - "build:dev": "run-p build:csstype build:cjs build:esm", - "build": "run-p build:version build:dev build:umd", - "prebuild": "run-s lint clean", - "test": "karma start", - "test:watch": "karma start --single-run=false --auto-watch", - "test:debug": "karma start --browsers=Chrome --single-run=false --auto-watch --debug", - "coveralls": "cat ./test/coverage/lcov.info | coveralls", - "pretest": "run-p clean:coverage", - "prepare": "run-s build:version test build", - "precommit": "lint-staged" - }, - "lint-staged": { - "src/**/*.ts": [ - "eslint --fix" - ] - }, - "inherits": [ - "@antv/x6-package-json/cli.json", - "@antv/x6-package-json/karma.json", - "@antv/x6-package-json/eslint.json", - "@antv/x6-package-json/rollup.json" - ], - "devDependencies": { - "@rollup/plugin-commonjs": "^20.0.0", - "@rollup/plugin-node-resolve": "^13.0.4", - "@rollup/plugin-replace": "^3.0.0", - "@rollup/plugin-typescript": "^8.2.5", - "@types/jasmine": "^3.9.0", - "@types/node": "^16.9.1", - "@types/sinon": "^10.0.2", - "@typescript-eslint/eslint-plugin": "^4.31.0", - "@typescript-eslint/parser": "^4.31.0", - "coveralls": "^3.1.1", - "csstype": "^3.0.7", - "eslint": "^7.32.0", - "eslint-config-airbnb-base": "^14.2.1", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-jest": "^24.4.0", - "eslint-plugin-jsx-a11y": "^6.4.1", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-promise": "^5.1.0", - "eslint-plugin-react": "^7.25.1", - "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-unicorn": "^36.0.0", - "fs-extra": "^10.0.0", - "jasmine-core": "^3.9.0", - "karma": "^6.3.4", - "karma-chrome-launcher": "^3.1.0", - "karma-cli": "^2.0.0", - "karma-jasmine": "^4.0.1", - "karma-spec-reporter": "^0.0.32", - "karma-typescript": "5.3.0", - "karma-typescript-es6-transform": "5.3.0", - "lint-staged": "^11.1.2", - "npm-run-all": "^4.1.5", - "postcss": "^8.3.6", - "prettier": "^2.4.0", - "pretty-quick": "^3.1.1", - "rimraf": "^3.0.2", - "rollup": "^2.56.3", - "rollup-plugin-auto-external": "^2.0.0", - "rollup-plugin-filesize": "^9.1.1", - "rollup-plugin-postcss": "^4.0.1", - "rollup-plugin-progress": "^1.1.2", - "rollup-plugin-terser": "^7.0.2", - "sinon": "^11.1.2", - "ts-node": "^10.2.1", - "tslib": "^2.3.1", - "typescript": "^4.4.3" - }, - "author": { - "name": "bubkoo", - "email": "bubkoo.wy@gmail.com" - }, - "contributors": [], - "license": "MIT", - "homepage": "https://github.com/antvis/x6", - "bugs": { - "url": "https://github.com/antvis/x6/issues" - }, - "repository": { - "type": "git", - "url": "ssh://git@github.com/antvis/x6.git", - "directory": "packages/x6" - }, - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org" - } -} diff --git a/packages/x6-vector/rollup.config.js b/packages/x6-vector/rollup.config.js deleted file mode 100644 index 6746d84858f..00000000000 --- a/packages/x6-vector/rollup.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import config from '../../configs/rollup-config' - -export default config({ - output: [ - { - name: 'X6Vector', - format: 'umd', - file: 'dist/x6-vector.js', - sourcemap: true, - }, - ], -}) diff --git a/packages/x6-vector/scripts/csstype.js b/packages/x6-vector/scripts/csstype.js deleted file mode 100644 index 95905b22c94..00000000000 --- a/packages/x6-vector/scripts/csstype.js +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env node - -// copy csstype - -const fse = require('fs-extra') -const path = require('path') - -const cwd = process.cwd() -const src = path.join(cwd, '../../node_modules/csstype/index.d.ts') -const dist = path.join(cwd, 'src/dom/style/csstype.ts') - -const raw = fse.readFileSync(src, { encoding: 'utf8' }) -const prev = fse.existsSync(dist) - ? fse.readFileSync(dist, { encoding: 'utf8' }) - : null - -const next = `/* eslint-disable */ - -/** -* Auto generated file by copying from node_modules, do not modify it! -*/ - -${raw} -` - -if (prev !== next) { - fse.outputFileSync(dist, next, { encoding: 'utf8' }) -} diff --git a/packages/x6-vector/src/dom/attributes/attributes.test.ts b/packages/x6-vector/src/dom/attributes/attributes.test.ts deleted file mode 100644 index 6d61547f287..00000000000 --- a/packages/x6-vector/src/dom/attributes/attributes.test.ts +++ /dev/null @@ -1,376 +0,0 @@ -import sinon from 'sinon' -import { Env } from '../../global/env' -import { Dom } from '../dom' -import { Hook } from './hook' - -describe('Dom', () => { - describe('attributes', () => { - describe('attr()', () => { - it('should set attribute by key-value', () => { - const div = new Dom('div') - div.attr('tabIndex', 1) - div.attr('aria-atomic', 'foo') - - expect(div.attr('tabIndex')).toEqual(1) - expect(div.attr('aria-atomic')).toEqual('foo' as any) - expect(div.attr('ariaAtomic')).toEqual('foo') - }) - - it('should not process custom attribute name', () => { - const div = new Dom('div') - div.attr('fooBar', 'barz') - expect(div.attr('fooBar')).toEqual('barz') - expect(div.attr('foo-bar')).toBeUndefined() - }) - - it('should be case-insensitive for HTMLElement attribute name', () => { - const div = new Dom('div') - div.attr('fooBar', 'barz') - div.attr('tabIndex', 10) - expect(div.attr('fooBar')).toEqual('barz') - expect(div.attr('foobar')).toEqual('barz') - expect(div.attr('Foobar')).toEqual('barz') - expect(div.attr('tabIndex')).toEqual(10) - expect(div.attr('tabindex')).toEqual(10) - }) - - it('should remove attribute when the attribute value is null', () => { - const div = new Dom('div') - div.attr('fooBar', 'barz') - div.attr('tabIndex', 10) - expect(div.attr('fooBar')).toEqual('barz') - expect(div.attr('tabIndex')).toEqual(10) - - div.attr('fooBar', null) - div.attr('tabIndex', null) - - expect(div.attr('fooBar')).toBeUndefined() - expect(div.attr('tabIndex')).toBeUndefined() - }) - - it('should get all attributes', () => { - const div = new Dom('div') - div.attr('fooBar', 'barz') - div.attr('tabIndex', 10) - const ret = div.attr() as any - // attribute name was convert to lowercase - expect(ret.foobar).toEqual('barz') - // special attribute name - expect(ret.tabIndex).toEqual(10) - // unspecified attribute - expect(ret.left).toEqual(undefined) - expect(Object.keys(ret).length).toEqual(2) - }) - - it('should get specified attributes by name', () => { - const div = new Dom('div') - div.attr('fooBar', 'barz') - div.attr('tabIndex', 10) - const ret = div.attr(['fooBar', 'tabIndex']) - // custom attribute name was keeped - expect(ret.fooBar).toEqual('barz') - expect(ret.tabIndex).toEqual(10) - }) - - it('should convert boolean string to boolean', () => { - const div = new Dom('div') - div.attr('foo', 'true') - div.attr('bar', 'false') - div.attr('barz', 'xxx') - expect(div.attr('foo')).toBeTrue() - expect(div.attr('bar')).toBeFalse() - expect(div.attr('barz')).toEqual('xxx') - }) - - it('should convert numeric string to number', () => { - const div = new Dom('div') - div.attr('foo', '1') - div.attr('bar', '-1') - div.attr('barz', '1.5') - expect(div.attr('foo')).toEqual(1) - expect(div.attr('bar')).toEqual(-1) - expect(div.attr('barz')).toEqual(1.5) - }) - - it('should set/get special NUMERIC attributes', () => { - const div = new Dom('div') - div.attr('tabIndex', '1') - div.attr('rowSpan', '-1') - div.attr('colSpan', '2') - div.attr('start', '1.5') - expect(div.attr('tabIndex')).toEqual(1) - expect(div.attr('rowSpan')).toEqual(-1) - expect(div.attr('colSpan')).toEqual(2) - expect(div.attr('start')).toEqual(1.5) - }) - - it('should remove invalid value for NUMERIC attribute', () => { - const div = new Dom('div') - div.attr('tabIndex', '1') - expect(div.attr('tabIndex')).toEqual(1) - div.attr('tabIndex', 'a') - expect(div.attr('tabIndex')).toBeUndefined() - }) - - it('should set/get special POSITIVE_NUMERIC attributes', () => { - const div = new Dom('div') - div.attr('cols', 1) - div.attr('rows', 2) - div.attr('size', 3) - div.attr('span', 4) - expect(div.attr('cols')).toEqual(1) - expect(div.attr('rows')).toEqual(2) - expect(div.attr('size')).toEqual(3) - expect(div.attr('span')).toEqual(4) - }) - - it('should remove invalid value for POSITIVE_NUMERIC attribute', () => { - const div = new Dom('div') - div.attr('cols', 1) - expect(div.attr('cols')).toEqual(1) - div.attr('cols', -1) - expect(div.attr('cols')).toBeUndefined() - }) - - it('should set/get special BOOLEAN attributes', () => { - const div = new Dom('div') - div.attr('autoFocus', true) - div.attr('async', 'true') - expect(div.attr('autoFocus')).toEqual(true) - expect(div.attr('async')).toEqual(true) - }) - - it('should remove invalid value for BOOLEAN attribute', () => { - const div = new Dom('div') - div.attr('autoFocus', true) - expect(div.attr('autoFocus')).toEqual(true) - - div.attr('autoFocus', 1) - expect(div.attr('autoFocus')).toEqual(false) - }) - - it('should convert inline BOOLEAN attributes', () => { - const div = new Dom('div') - div.node.setAttribute('autoFocus', 'true') - expect(div.attr('autoFocus')).toEqual(true) - div.node.setAttribute('autoFocus', 'false') - expect(div.attr('autoFocus')).toEqual(false) - }) - - it('should set/get special OVERLOADED_BOOLEAN attributes', () => { - const div = new Dom('div') - div.attr('capture', true) - expect(div.attr('capture')).toEqual(true) - div.attr('capture', 'demo') - expect(div.attr('capture')).toEqual('demo') - }) - - it('should convert inline OVERLOADED_BOOLEAN attributes', () => { - const div = new Dom('div') - div.node.setAttribute('capture', 'true') - expect(div.attr('capture')).toEqual(true) - div.node.setAttribute('capture', 'false') - expect(div.attr('capture')).toEqual(false) - div.node.setAttribute('capture', 'demo') - expect(div.attr('capture')).toEqual('demo') - }) - - it('should remove invalid value for OVERLOADED_BOOLEAN attribute', () => { - const div = new Dom('div') - div.attr('capture', true) - expect(div.attr('capture')).toEqual(true) - div.attr('capture', 'demo') - expect(div.attr('capture')).toEqual('demo') - div.attr('capture', false) - expect(div.attr('capture')).toEqual(false) - }) - - it('should set/get attributes store on properties', () => { - const div = new Dom('div') - div.attr('checked', true) - expect(div.attr('checked')).toEqual(true) - div.attr('checked', false) - expect(div.attr('checked')).toEqual(false) - - div.attr('checked', true) - expect(div.attr('checked')).toEqual(true) - div.attr('checked', null) - expect(div.attr('checked')).toEqual(false) - }) - - it('should set/get special BOOLEANISH_STRING attributes', () => { - const div = new Dom('div') - expect(div.attr('draggable')).toEqual(false) - div.attr('draggable', true) - expect(div.attr('draggable')).toEqual(true) - div.attr('draggable', 'false') - expect(div.attr('draggable')).toEqual(false) - }) - - it('should return style object', () => { - const div = new Dom('div') - div.css('left', 10) - const style = div.attr('style') - expect(style).toBeInstanceOf(Object) - expect(style.left).toEqual('10px') - }) - - it('should set style object', () => { - const div = new Dom('div') - div.attr('style', { left: 10 }) - expect(div.css('left')).toEqual('10px') - }) - - it('should set style string', () => { - const div = new Dom('div') - div.attr('style', 'left: 10px') - expect(div.css('left')).toEqual('10px') - }) - - it('should sanitize url', () => { - const div = new Dom('div') - div.attr('src', 'http://www.a.com') - expect(div.attr('src')).toEqual('http://www.a.com') - - const spy = sinon.spy(console, 'error') - const env = Env as any - const old = env.isDev - env.isDev = true - - // eslint-disable-next-line no-script-url - div.attr('src', 'javascript:;') - expect(spy.callCount).toEqual(1) - - env.isDev = old - spy.restore() - }) - - it('should auto set attribute with namespace', () => { - const svg = new Dom('svg') - svg.attr('xmlLang', 'foo') - expect(svg.attr('xmlLang')).toEqual('foo') - }) - - it('should remove attribute with function value', () => { - const div = new Dom('div') - div.attr('foo', 'bar') - expect(div.attr('foo')).toEqual('bar') - div.attr('foo', (() => {}) as any) - expect(div.attr('foo')).toBeUndefined() - }) - - it('should remove attribute when not accept boolean values', () => { - const div = new Dom('div') - div.attr('tabIndex', '1') - expect(div.attr('tabIndex')).toEqual(1) - div.attr('tabIndex', false) - expect(div.attr('tabIndex')).toBeUndefined() - }) - - it('should log error message when attribute not accept empty string', () => { - const div = new Dom('div') - - div.attr('src', 'http://www.a.com') - expect(div.attr('src')).toEqual('http://www.a.com') - - const spy = sinon.spy(console, 'error') - const env = Env as any - const old = env.isDev - env.isDev = true - - div.attr('src', '') - expect(div.attr('src')).toBeUndefined() - expect(spy.callCount).toEqual(1) - - div.attr('action', 'foo') - expect(div.attr('action')).toEqual('foo') - - div.attr('action', '') - expect(div.attr('action')).toBeUndefined() - expect(spy.callCount).toEqual(2) - - env.isDev = old - spy.restore() - }) - - it('should log error message when the attribute name is illegal', () => { - const spy = sinon.spy(console, 'error') - const env = Env as any - const old = env.isDev - env.isDev = true - - const div = new Dom('div') - div.attr('1', '2') - expect(spy.callCount).toEqual(1) - div.attr('1', 2) - // do not warn again - expect(spy.callCount).toEqual(1) - - env.isDev = old - spy.restore() - }) - - it('should call get-hook', () => { - const div = new Dom('div') - Hook.register('foo', { - get() { - return 10 - }, - }) - expect(div.attr('foo')).toEqual(10) - div.attr('foo', 100) - expect(div.attr('foo')).toEqual(10) - Hook.unregister('foo') - }) - - it('should call set-hook', () => { - const div = new Dom('div') - Hook.register('foo', { - set(node, value) { - if (typeof value === 'number') { - node.setAttribute('foo', value > 0 ? '1' : '-1') - return false - } - return value - }, - }) - div.attr('foo', 'bar') - expect(div.attr('foo')).toEqual('bar') - div.attr('foo', 10) - expect(div.attr('foo')).toEqual(1) - div.attr('foo', -10) - expect(div.attr('foo')).toEqual(-1) - - Hook.unregister('foo') - }) - }) - - describe('round()', () => { - it('should round the specified numeric values', () => { - const svg = new Dom('svg') - svg.attr({ - x: 1.33333333, - y: 1.66666666, - foo: 'bar', - }) - svg.round(2, ['x', 'foo'] as any) - expect(svg.attr('x')).toEqual(1.33) - expect(svg.attr('y')).toEqual(1.66666666) - expect(svg.attr('foo')).toEqual('bar') - }) - - it('should round all the numeric values', () => { - const svg = new Dom('svg') - svg.attr({ - x: 1.33333333, - y: 1.66666666, - foo: 'bar', - }) - svg.round(2) - expect(svg.attr('x')).toEqual(1.33) - expect(svg.attr('y')).toEqual(1.67) - expect(svg.attr('foo')).toEqual('bar') - }) - }) - }) -}) diff --git a/packages/x6-vector/src/dom/attributes/attributes.ts b/packages/x6-vector/src/dom/attributes/attributes.ts deleted file mode 100644 index 7f896d05fc1..00000000000 --- a/packages/x6-vector/src/dom/attributes/attributes.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Base } from '../common/base' -import { Core } from './core' -import { CSSProperties } from '../style' -import { AttributesMap } from './types' -import { AttributesBase } from './base' - -export class Attributes< - TElement extends Element, - Attributes extends AttributesMap = AttributesMap, - Keys extends keyof Attributes = keyof Attributes, - > - extends Base - implements AttributesBase -{ - attr(): Record & { - style: CSSProperties - } - attr( - names: K[], - ): Record & { - style?: CSSProperties - } - attr(names: string[]): Record< - string, - string | number | boolean | undefined | null - > & { - style?: CSSProperties - } - - attr(attrs: Attributes): this - attr(attrs: { [name: string]: any }): this - attr(name: K, value: Exclude): this - attr(name: K, value: null): this - attr(name: K, value: string | number | boolean): this - attr(name: string, value: null): this - attr(name: string, value: string | number | boolean | null): this - attr(name: 'style', style: CSSProperties | string): this - attr(name: 'style'): CSSProperties - attr(name: K): T - attr(name: string): T - - attr( - name: K, - value?: Attributes[K] | null, - ): T | this - - attr( - attr?: string | string[] | Record, - value?: string | number | boolean | null, - ): Record | string | number | this - attr( - attr?: string | string[] | Record, - val?: string | number | boolean | null | CSSProperties, - ) { - const node = this.node - - // get all attributes - if (attr == null) { - const result: Record = {} - const attrs = node.attributes - if (attrs) { - for (let index = 0, l = attrs.length; index < l; index += 1) { - const item = attrs.item(index) - if (item && item.nodeValue) { - const name = Core.getAttributeNameInResult(item.nodeName) - result[name] = this.attr(item.nodeName) - } - } - } - return result - } - - // get attributes by specified attribute names - if (Array.isArray(attr)) { - return attr.reduce>((memo, name) => { - // keep the given names - memo[name] = this.attr(name) - return memo - }, {}) - } - - if (typeof attr === 'object') { - Object.keys(attr).forEach((key) => this.attr(key, attr[key])) - return this - } - - // get attribute by name - if (val === undefined) { - const attrName = Core.getAttributeNameInElement(attr) - const attrValue = node.getAttribute(attrName) || undefined - return Core.getAttribute( - node, - Core.getAttributeNameInResult(attr), - attrValue, - ) - } - - // remove attribute - if (Core.shouldRemoveAttribute(attr, val)) { - node.removeAttribute(attr) - return this - } - - // set attribute by k-v - Core.setAttribute(node, attr, val) - - return this - } - - round(precision = 2, names?: K[]) { - const factor = 10 ** precision - const attrs = names ? this.attr(names) : this.attr() - Object.keys(attrs).forEach((key) => { - const value = attrs[key] - if (typeof value === 'number') { - attrs[key] = Math.round(value * factor) / factor - } - }) - - this.attr(attrs) - - return this - } -} diff --git a/packages/x6-vector/src/dom/attributes/base.ts b/packages/x6-vector/src/dom/attributes/base.ts deleted file mode 100644 index 054afea5e14..00000000000 --- a/packages/x6-vector/src/dom/attributes/base.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Attributes } from './attributes' - -export abstract class AttributesBase { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - attr(k?: any, v?: any) { - return Attributes.prototype.attr.call(this, k, v) - } -} diff --git a/packages/x6-vector/src/dom/attributes/core.ts b/packages/x6-vector/src/dom/attributes/core.ts deleted file mode 100644 index d7ac295a544..00000000000 --- a/packages/x6-vector/src/dom/attributes/core.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { Env } from '../../global/env' -import { Special } from './special' -import { Hook } from './hook' -import { Util } from './util' - -export namespace Core { - const ATTRIBUTE_NAME_START_CHAR = `:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD` - const ATTRIBUTE_NAME_CHAR = `${ATTRIBUTE_NAME_START_CHAR}\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040` - // eslint-disable-next-line - const VALID_ATTRIBUTE_NAME_REGEX = new RegExp( - `^[${ATTRIBUTE_NAME_START_CHAR}][${ATTRIBUTE_NAME_CHAR}]*$`, - ) - - const illegalNames: { [key: string]: true } = {} - const validedNames: { [key: string]: true } = {} - - export function isAttributeNameSafe(attributeName: string): boolean { - if (attributeName in validedNames) { - return true - } - if (attributeName in illegalNames) { - return false - } - - if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) { - validedNames[attributeName] = true - return true - } - - illegalNames[attributeName] = true - - if (Env.isDev) { - console.error('Invalid attribute name: `%s`', attributeName) - } - - return false - } -} - -export namespace Core { - export function shouldRemoveAttribute(name: string, value: any): boolean { - const special = Special.get(name) - if (special !== null) { - if (special.mustUseProperty) { - return false - } - } - - if (value === null) { - return true - } - - if ( - special == null && - (typeof value === 'function' || typeof value === 'symbol') - ) { - return true - } - - if (typeof value === 'boolean') { - if (special !== null) { - if (!special.acceptsBooleans) { - return true - } - } - } - - if (special !== null) { - if (special.removeEmptyString && value === '') { - if (Env.isDev) { - if (name === 'src' || name === 'href' || name === 'xlinkHref') { - console.error( - 'An empty string ("") was passed to the %s attribute. ' + - 'This may cause the browser to download the whole page again over the network. ' + - 'To fix this, either do not render the element at all ' + - 'or pass null to %s instead of an empty string.', - name, - name, - ) - } else { - console.error( - 'An empty string ("") was passed to the %s attribute. ' + - 'To fix this, either do not render the element at all ' + - 'or pass null to %s instead of an empty string.', - name, - name, - ) - } - return true - } - } - - switch (special.type) { - case Special.Type.BOOLEAN: - return !(value === true || value === 'true') - case Special.Type.OVERLOADED_BOOLEAN: - return value === false - case Special.Type.NUMERIC: - return Number.isNaN(value) - case Special.Type.POSITIVE_NUMERIC: - return Number.isNaN(value) || value < 1 - default: - return false - } - } - - return false - } -} - -export namespace Core { - export function getAttributeNameInElement(name: string) { - const special = Special.get(name) - return special ? special.attributeName : name - } - - export function getAttributeNameInResult(name: string) { - const special = Special.get(name) - const ret = special ? special.propertyName : name - return ret === name ? Util.camelCase(name) : ret - } - - export function getAttribute( - node: TElement, - name: string, - value: string | undefined, - ) { - const hook = Hook.get(name) - if (hook && hook.get) { - const result = hook.get(node) - if (typeof result !== 'undefined') { - return result - } - } - - const special = Special.get(name) - if (special) { - if (special.mustUseProperty) { - const el = node as any - value = el[special.propertyName] // eslint-disable-line - } - - if ( - special.type === Special.Type.BOOLEAN || - special.type === Special.Type.OVERLOADED_BOOLEAN - ) { - if (typeof value === 'string') { - const cased = value.toLowerCase() - if (cased === 'true') { - return true - } - - if (cased === 'false') { - return false - } - } - - if (special.mustUseProperty) { - return value - } - - return value == null ? node.hasAttribute(name) : value - } - - if (special.type === Special.Type.BOOLEANISH_STRING) { - if (typeof value === 'string') { - const cased = value.toLowerCase() - if (cased === 'true') { - return true - } - - if (cased === 'false') { - return false - } - } - - return false - } - - if ( - special.type === Special.Type.NUMERIC || - special.type === Special.Type.POSITIVE_NUMERIC - ) { - const num = Util.tryConvertToNumber(value) - return typeof num === 'number' && Number.isFinite(num) ? num : undefined - } - } - - if (value === 'true') { - return true - } - - if (value === 'false') { - return false - } - - return Util.tryConvertToNumber(value) - } - - export function setAttribute( - node: TElement, - name: string, - value: any, - ) { - const hook = Hook.get(name) - if (hook && hook.set) { - const ret = hook.set(node, value) - if (ret === false) { - return - } - value = ret // eslint-disable-line - } - - const special = Special.get(name) - if (special == null) { - if (isAttributeNameSafe(name)) { - node.setAttribute(name, value) - } - } else { - const { mustUseProperty, propertyName } = special - if (mustUseProperty) { - const el = node as any - if (value === null) { - el[propertyName] = special.type === Special.Type.BOOLEAN ? false : '' - } else { - el[propertyName] = value - } - return - } - - const { attributeName } = special - - let attributeValue: string | undefined - - if ( - (special.type === Special.Type.BOOLEAN || - special.type === Special.Type.OVERLOADED_BOOLEAN) && - (value === true || value === 'true') - ) { - attributeValue = '' - } else { - attributeValue = `${value}` - if (special.sanitizeURL) { - Util.sanitizeURL(attributeName, attributeValue) - } - } - - if (special.attributeNamespace) { - node.setAttributeNS( - special.attributeNamespace, - attributeName, - attributeValue, - ) - } else { - node.setAttribute(attributeName, attributeValue) - } - } - } -} diff --git a/packages/x6-vector/src/dom/attributes/hook.ts b/packages/x6-vector/src/dom/attributes/hook.ts deleted file mode 100644 index 9b60850656b..00000000000 --- a/packages/x6-vector/src/dom/attributes/hook.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Util as Style } from '../style/util' - -export namespace Hook { - export interface Definition { - get?: (node: TElement) => any - set?: (node: TElement, attributeValue: any) => any - } - - const hooks: Record = {} - - export function get(attributeName: string) { - return hooks[attributeName] - } - - export function register(attributeName: string, hook: Definition) { - hooks[attributeName] = hook - } - - export function unregister(attributeName: string) { - delete hooks[attributeName] - } - - register('style', { - get(node) { - return Style.style(node) - }, - set(node, attributeValue) { - if (typeof attributeValue === 'object') { - Object.keys(attributeValue).forEach((key) => - Style.style(node, key, attributeValue[key]), - ) - return false - } - return attributeValue - }, - }) -} diff --git a/packages/x6-vector/src/dom/attributes/index.ts b/packages/x6-vector/src/dom/attributes/index.ts deleted file mode 100644 index 31f07cebcea..00000000000 --- a/packages/x6-vector/src/dom/attributes/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './hook' -export * from './base' -export * from './attributes' -export { AttributesMap } from './types' diff --git a/packages/x6-vector/src/dom/attributes/special.ts b/packages/x6-vector/src/dom/attributes/special.ts deleted file mode 100644 index 551f6006272..00000000000 --- a/packages/x6-vector/src/dom/attributes/special.ts +++ /dev/null @@ -1,544 +0,0 @@ -import { Util } from './util' - -export class Special { - public readonly acceptsBooleans: boolean - - constructor( - public readonly type: Special.Type, - public readonly propertyName: string, - public readonly attributeName: string, - public readonly attributeNamespace: string | null = null, - public readonly mustUseProperty: boolean = false, - public readonly sanitizeURL: boolean = false, - public readonly removeEmptyString: boolean = false, - ) { - this.acceptsBooleans = - type === Special.Type.BOOLEAN || - type === Special.Type.BOOLEANISH_STRING || - type === Special.Type.OVERLOADED_BOOLEAN - } -} - -export namespace Special { - export enum Type { - /** - * A simple string attribute. - * - * Attributes that aren't in the filter are presumed to have this type. - */ - STRING, - /** - * A string attribute that accepts booleans. In HTML, these are called - * "enumerated" attributes with "true" and "false" as possible values. - * - * When true, it should be set to a "true" string. - * - * When false, it should be set to a "false" string. - */ - BOOLEANISH_STRING, - /** - * A real boolean attribute. - * - * When true, it should be present (set either to an empty string or its name). - * - * When false, it should be omitted. - */ - BOOLEAN, - /** - * An attribute that can be used as a flag as well as with a value. - * - * When true, it should be present (set either to an empty string or its name). - * - * When false, it should be omitted. - * - * For any other value, should be present with that value. - */ - OVERLOADED_BOOLEAN, - - /** - * An attribute that must be numeric or parse as a numeric. - * - * When falsy, it should be removed. - */ - NUMERIC, - - /** - * An attribute that must be positive numeric or parse as a positive numeric. - * - * When falsy, it should be removed. - */ - POSITIVE_NUMERIC, - } -} - -export namespace Special { - export const specials: Record = {} - export function get(name: string) { - return specials[name] || null - } -} - -export namespace Special { - const arr = [ - 'vector:data', - 'aria-activedescendant', - 'aria-atomic', - 'aria-autocomplete', - 'aria-busy', - 'aria-checked', - 'aria-colcount', - 'aria-colindex', - 'aria-colspan', - 'aria-controls', - 'aria-current', - 'aria-describedby', - 'aria-details', - 'aria-disabled', - 'aria-dropeffect', - 'aria-errormessage', - 'aria-expanded', - 'aria-flowto', - 'aria-grabbed', - 'aria-haspopup', - 'aria-hidden', - 'aria-invalid', - 'aria-keyshortcuts', - 'aria-label', - 'aria-labelledby', - 'aria-level', - 'aria-live', - 'aria-modal', - 'aria-multiline', - 'aria-multiselectable', - 'aria-orientation', - 'aria-owns', - 'aria-placeholder', - 'aria-posinset', - 'aria-pressed', - 'aria-readonly', - 'aria-relevant', - 'aria-required', - 'aria-roledescription', - 'aria-rowcount', - 'aria-rowindex', - 'aria-rowspan', - 'aria-selected', - 'aria-setsize', - 'aria-sort', - 'aria-valuemax', - 'aria-valuemin', - 'aria-valuenow', - 'aria-valuetext', - ] - arr.forEach((attributeName) => { - const name = Util.camelCase(attributeName) - specials[name] = specials[attributeName] = new Special( - Type.STRING, - name, - attributeName, - null, // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - const arr = [ - 'accessKey', - 'contextMenu', - 'radioGroup', - 'autoCapitalize', - 'autoCorrect', - 'autoSave', - 'itemProp', - 'itemType', - 'itemID', - 'itemRef', - 'input-modalities', - 'inputMode', - 'referrerPolicy', - 'formEncType', - 'formMethod', - 'formTarget', - 'dateTime', - 'autoComplete', - 'encType', - 'allowTransparency', - 'frameBorder', - 'marginHeight', - 'marginWidth', - 'srcDoc', - 'crossOrigin', - 'srcSet', - 'useMap', - 'enterKeyHint', - 'maxLength', - 'minLength', - 'keyType', - 'keyParams', - 'hrefLang', - 'charSet', - 'controlsList', - 'mediaGroup', - 'classID', - 'cellPadding', - 'cellSpacing', - 'dirName', - 'srcLang', - ] - arr.forEach((attributeName) => { - specials[attributeName] = specials[attributeName.toLowerCase()] = - new Special( - Type.STRING, - attributeName, - attributeName.toLowerCase(), // attributeName - null, // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - // A few string attributes have a different name. - const arr = ['accept-charset', 'http-equiv'] - arr.forEach((attributeName) => { - const name = Util.camelCase(attributeName) - specials[name] = specials[attributeName] = new Special( - Type.STRING, - name, - attributeName, // attributeName - null, // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - // These are HTML boolean attributes. - const arr = [ - 'allowFullScreen', - 'async', - // Note: there is a special case that prevents it from being written to the DOM - // on the client side because the browsers are inconsistent. Instead we call focus(). - 'autoFocus', - 'autoPlay', - 'controls', - 'default', - 'defer', - 'disabled', - 'disablePictureInPicture', - 'disableRemotePlayback', - 'formNoValidate', - 'hidden', - 'loop', - 'noModule', - 'noValidate', - 'open', - 'playsInline', - 'readOnly', - 'required', - 'reversed', - 'scoped', - 'seamless', - 'itemScope', - ] - arr.forEach((name) => { - specials[name] = specials[name.toLowerCase()] = new Special( - Type.BOOLEAN, - name, - name.toLowerCase(), // attributeName - null, // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - // These are "enumerated" HTML attributes that accept "true" and "false". - // We can pass `true` and `false` even though technically ese aren't - // boolean attributes (they are coerced to strings). - const arr = ['contentEditable', 'draggable', 'spellCheck', 'value'] - arr.forEach((name) => { - specials[name] = specials[name.toLowerCase()] = new Special( - Type.BOOLEANISH_STRING, - name, - name.toLowerCase(), // attributeName - null, // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - // These are "enumerated" SVG attributes that accept "true" and "false". - // We can pass `true` and `false` even though technically these aren't - // boolean attributes (they are coerced to strings). - // Since these are SVG attributes, their attribute names are case-sensitive. - const arr = [ - 'autoReverse', - 'externalResourcesRequired', - 'focusable', - 'preserveAlpha', - ] - - arr.forEach((name) => { - specials[name] = new Special( - Type.BOOLEANISH_STRING, - name, - name, // attributeName - null, // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - // These are the few props that we set as DOM properties - // rather than attributes. These are all booleans. - const arr = [ - 'checked', - // Note: `option.selected` is not updated if `select.multiple` is - // disabled with `removeAttribute`. We have special logic for handling this. - 'multiple', - 'muted', - 'selected', - ] - arr.forEach((name) => { - specials[name] = new Special( - Type.BOOLEAN, - name, - name.toLowerCase(), // attributeName - null, // attributeNamespace - true, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - // These are HTML attributes that are "overloaded booleans": they behave like - // booleans, but can also accept a string value. - const arr = ['capture', 'download'] - arr.forEach((name) => { - specials[name] = new Special( - Type.OVERLOADED_BOOLEAN, - name, - name.toLowerCase(), // attributeName - null, // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - // These are HTML attributes that must be positive numbers. - const arr = ['cols', 'rows', 'size', 'span'] - arr.forEach((name) => { - specials[name] = new Special( - Type.POSITIVE_NUMERIC, - name, - name.toLowerCase(), // attributeName - null, // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - // These are HTML attributes that must be numbers. - const arr = ['tabIndex', 'rowSpan', 'colSpan', 'start'] - arr.forEach((name) => { - specials[name] = specials[name.toLowerCase()] = new Special( - Type.NUMERIC, - name, - name.toLowerCase(), // attributeName - null, // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - // This is a list of all SVG attributes that need special casing, namespacing, - // or boolean value assignment. Regular attributes that just accept strings - // and have the same names are omitted, just like in the HTML attribute filter. - // Some of these attributes can be hard to find. This list was created by - // scraping the MDN documentation. - const arr1 = [ - 'accent-height', - 'alignment-baseline', - 'arabic-form', - 'baseline-shift', - 'cap-height', - 'clip-path', - 'clip-rule', - 'color-interpolation', - 'color-interpolation-filters', - 'color-profile', - 'color-rendering', - 'dominant-baseline', - 'enable-background', - 'fill-opacity', - 'fill-rule', - 'flood-color', - 'flood-opacity', - 'font-family', - 'font-size', - 'font-size-adjust', - 'font-stretch', - 'font-style', - 'font-variant', - 'font-weight', - 'glyph-name', - 'glyph-orientation-horizontal', - 'glyph-orientation-vertical', - 'horiz-adv-x', - 'horiz-origin-x', - 'image-rendering', - 'letter-spacing', - 'lighting-color', - 'marker-end', - 'marker-mid', - 'marker-start', - 'overline-position', - 'overline-thickness', - 'paint-order', - 'panose-1', - 'pointer-events', - 'rendering-intent', - 'shape-rendering', - 'stop-color', - 'stop-opacity', - 'strikethrough-position', - 'strikethrough-thickness', - 'stroke-dasharray', - 'stroke-dashoffset', - 'stroke-linecap', - 'stroke-linejoin', - 'stroke-miterlimit', - 'stroke-opacity', - 'stroke-width', - 'text-anchor', - 'text-decoration', - 'text-rendering', - 'underline-position', - 'underline-thickness', - 'unicode-bidi', - 'unicode-range', - 'units-per-em', - 'v-alphabetic', - 'v-hanging', - 'v-ideographic', - 'v-mathematical', - 'vector-effect', - 'vert-adv-y', - 'vert-origin-x', - 'vert-origin-y', - 'word-spacing', - 'writing-mode', - 'xmlns:xlink', - 'x-height', - ] - - arr1.forEach((attributeName) => { - const name = Util.camelCase(attributeName) - specials[name] = specials[attributeName] = new Special( - Type.STRING, - name, - attributeName, - null, // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) - - // String SVG attributes with the xlink namespace. - const arr2 = [ - 'xlink:actuate', - 'xlink:arcrole', - 'xlink:role', - 'xlink:show', - 'xlink:title', - 'xlink:type', - ] - arr2.forEach((attributeName) => { - const name = Util.camelCase(attributeName) - specials[attributeName] = specials[name] = new Special( - Type.STRING, - name, - attributeName, - 'http://www.w3.org/1999/xlink', - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) - - // String SVG attributes with the xml namespace. - const arr3 = ['xml:base', 'xml:lang', 'xml:space'] - arr3.forEach((attributeName) => { - const name = Util.camelCase(attributeName) - specials[attributeName] = specials[name] = new Special( - Type.STRING, - name, - attributeName, - 'http://www.w3.org/XML/1998/namespace', // attributeNamespace - false, // mustUseProperty - false, // sanitizeURL - false, // removeEmptyString - ) - }) -} - -export namespace Special { - // These attributes accept URLs. These must not allow javascript: URLS. - // These will also need to accept Trusted Types object in the future. - const arr = ['xlinkHref', 'xlink:href'] - arr.forEach((name) => { - specials[name] = new Special( - Type.STRING, - name, - 'xlink:href', - 'http://www.w3.org/1999/xlink', - false, // mustUseProperty - true, // sanitizeURL - true, // removeEmptyString - ) - }) -} - -export namespace Special { - const arr = ['src', 'href', 'action', 'formAction'] - arr.forEach((attributeName) => { - specials[attributeName] = specials[attributeName.toLowerCase()] = - new Special( - Type.STRING, - attributeName, - attributeName.toLowerCase(), // attributeName - null, // attributeNamespace - false, // mustUseProperty - true, // sanitizeURL - true, // removeEmptyString - ) - }) -} diff --git a/packages/x6-vector/src/dom/attributes/types.ts b/packages/x6-vector/src/dom/attributes/types.ts deleted file mode 100644 index 3f5849983e7..00000000000 --- a/packages/x6-vector/src/dom/attributes/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SVGAttributesMap } from '../../vector/types' -import { HTMLAttributesMap } from '../types' - -export type AttributesMap = T extends SVGElement - ? SVGAttributesMap - : T extends HTMLElement - ? HTMLAttributesMap - : HTMLAttributesMap diff --git a/packages/x6-vector/src/dom/attributes/util.ts b/packages/x6-vector/src/dom/attributes/util.ts deleted file mode 100644 index 2d34b0ffe14..00000000000 --- a/packages/x6-vector/src/dom/attributes/util.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Env } from '../../global/env' - -export namespace Util { - export function tryConvertToNumber(value: string | undefined | null) { - if (value != null) { - const numReg = /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i - return numReg.test(value) ? +value : value - } - return value - } - - export function camelCase(str: string) { - return str.replace(/[-:]([a-z])/g, (input) => input[1].toUpperCase()) - } -} - -export namespace Util { - // A javascript: URL can contain leading C0 control or \u0020 SPACE, - // and any newline or tab are filtered out as if they're not part of the URL. - // https://url.spec.whatwg.org/#url-parsing - // Tab or newline are defined as \r\n\t: - // https://infra.spec.whatwg.org/#ascii-tab-or-newline - // A C0 control is a code point in the range \u0000 NULL to \u001F - // INFORMATION SEPARATOR ONE, inclusive: - // https://infra.spec.whatwg.org/#c0-control-or-space - - const isJavaScriptProtocol = - /^[\u0000-\u001F]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i // eslint-disable-line - - let didWarn = false - - export function sanitizeURL(attributeName: string, url: string) { - if (Env.isDev) { - if (!didWarn && isJavaScriptProtocol.test(url)) { - didWarn = true - console.error( - `Attribute "${attributeName}" with javascript url was blocked for security precaution. ` + - `Check the passed url: ${JSON.stringify(url)}`, - ) - } - } - } -} diff --git a/packages/x6-vector/src/dom/common/adopter.test.ts b/packages/x6-vector/src/dom/common/adopter.test.ts deleted file mode 100644 index 8a4635526c9..00000000000 --- a/packages/x6-vector/src/dom/common/adopter.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { createHTMLNode, createSVGNode } from '../../util/dom' -import { G, SVG } from '../../vector' -import { Dom } from '../dom' -import { Adopter } from './adopter' - -describe('Adopter', () => { - describe('adopt()', () => { - it('should return null when the given node is null', () => { - expect(Adopter.adopt()).toBeNull() - expect(Adopter.adopt(null)).toBeNull() - }) - - it('should create an instance with associated class', () => { - expect(Adopter.adopt(createHTMLNode('div'))).toBeInstanceOf(Dom) - expect(Adopter.adopt(createSVGNode('g'))).toBeInstanceOf(G) - }) - - it('should reuse the cached instance', () => { - const node = createSVGNode('g') - const g = Adopter.adopt(node) - expect(Adopter.adopt(node)).toBe(g) - }) - }) - - describe('cache()', () => { - it('should set cache', () => { - const node = createSVGNode('g') - const g = new G() - expect(Adopter.cache(node)).toBeUndefined() - Adopter.cache(node, g) - expect(Adopter.cache(node)).toBe(g) - }) - - it('should remove cache', () => { - const node = createSVGNode('g') - const g = new G() - expect(Adopter.cache(node)).toBeUndefined() - - Adopter.cache(node, g) - expect(Adopter.cache(node)).toBe(g) - - Adopter.cache(node, null) - expect(Adopter.cache(node)).toBeUndefined() - }) - }) - - describe('makeInstance()', () => { - it('should create a svg instance with nil arg', () => { - expect(Adopter.makeInstance()).toBeInstanceOf(SVG) - expect(Adopter.makeInstance(null)).toBeInstanceOf(SVG) - expect(Adopter.makeInstance(undefined)).toBeInstanceOf(SVG) - }) - }) -}) diff --git a/packages/x6-vector/src/dom/common/adopter.ts b/packages/x6-vector/src/dom/common/adopter.ts deleted file mode 100644 index 3c9342a7b87..00000000000 --- a/packages/x6-vector/src/dom/common/adopter.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { createHTMLNode, createSVGNode } from '../../util/dom' -import { Global } from '../../global' -import { Registry } from './registry' -import type { Base } from './base' -import type { Dom } from '../dom' -import type { SVG } from '../../vector' -import type { ElementMap } from '../../types' -import type { HTMLAttributesTagNameMap } from '../types' -import type { SVGAttributesTagNameMap } from '../../vector/types' - -export namespace Adopter { - const store: WeakMap = new WeakMap() - - export function cache(node: Node, instance?: Base | null) { - if (typeof instance === 'undefined') { - return store.get(node) - } - - if (instance === null) { - store.delete(node) - } else { - store.set(node, instance) - } - } - - export function adopt(node: null): null - export function adopt(node: T): ElementMap - export function adopt(node: Node | ChildNode): T - export function adopt(node: Node | ChildNode | null): T | null - export function adopt(node?: T | null): ElementMap | null - export function adopt(node?: T | null): ElementMap | null { - if (node == null) { - return null - } - - // make sure a node isn't already adopted - const instance = store.get(node) - if (instance != null) { - return instance as ElementMap - } - - const Type = Registry.getClass(node) - return new Type(node) as ElementMap - } - - const adopter = adopt - - export type Target = T | Node | string - - export function makeInstance(): SVG - export function makeInstance(node: undefined | null): SVG - export function makeInstance(instance: T): T - export function makeInstance(target: Target): T - export function makeInstance(node: T): ElementMap - export function makeInstance( - tagName: T, - ): SVGAttributesTagNameMap[T] - export function makeInstance( - tagName: T, - isHTML: false, - ): SVGAttributesTagNameMap[T] - export function makeInstance( - tagName: T, - isHTML: true, - ): HTMLAttributesTagNameMap[T] - export function makeInstance( - tagName: string, - isHTML: boolean, - ): T - export function makeInstance( - node?: T | Node | string, - isHTML = false, - ): T { - if (node == null) { - const Root = Registry.getClass('Svg') - return new Root() as T - } - - if (node instanceof Global.window.Node) { - return adopter(node) as T - } - - if (typeof node === 'object') { - return node - } - - if (typeof node === 'string') { - node = node.trim() // eslint-disable-line - } - - if (node.charAt(0) !== '<') { - return adopter(createNode(node)) as T - } - - // Make sure, that HTML elements are created with the correct namespace - const wrapper = isHTML ? createHTMLNode('div') : createSVGNode('svg') - wrapper.innerHTML = typeof node === 'string' ? unescape(node) : node - - // We can use firstChild here because we know, - // that the first char is < and thus an element - const result = adopter(wrapper.firstChild) - - // make sure, that element doesnt have its wrapper attached - wrapper.firstChild!.remove() - - return result as T - } - - export function createNode(tagName: string) { - const lower = tagName.toLowerCase() - const isSVG = lower !== 'dom' && Registry.isRegisted(lower) - const node = isSVG ? createSVGNode(tagName) : createHTMLNode(tagName) - return node as any as TElement - } - - // export function mock(instance = adopt) { - // adopter = instance - // } - - // export function unmock() { - // adopter = adopt - // } -} diff --git a/packages/x6-vector/src/dom/common/base.ts b/packages/x6-vector/src/dom/common/base.ts deleted file mode 100644 index 123f5566afe..00000000000 --- a/packages/x6-vector/src/dom/common/base.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { applyMixins } from '../../util/mixin' -import { - isDocument, - isSVGSVGElement, - isDocumentFragment, - isInDocument, -} from '../../util/dom' -import { Registry } from './registry' - -export abstract class Base { - public node: TElement - - isDocument() { - return isDocument(this.node) - } - - isSVGSVGElement() { - return isSVGSVGElement(this.node) - } - - isDocumentFragment() { - return isDocumentFragment(this.node) - } - - isInDocument() { - return isInDocument(this.node) - } -} - -export namespace Base { - export function register(name: string) { - return (ctor: Registry.Definition) => { - Registry.register(ctor, name) - } - } - - export function mixin(...source: any[]) { - return (ctor: Registry.Definition) => { - applyMixins(ctor, ...source) - } - } -} diff --git a/packages/x6-vector/src/dom/common/id.ts b/packages/x6-vector/src/dom/common/id.ts deleted file mode 100644 index 982aaf246e7..00000000000 --- a/packages/x6-vector/src/dom/common/id.ts +++ /dev/null @@ -1,24 +0,0 @@ -export namespace ID { - let seed = 0 - - export function generate() { - seed += 1 - return `vector-${seed}` - } - - export function overwrite( - node: TElement, - deep = true, - ) { - if (deep) { - const children = node.children - for (let i = children.length - 1; i >= 0; i -= 1) { - overwrite(children[i], true) - } - } - - if (node.id) { - node.id = generate() - } - } -} diff --git a/packages/x6-vector/src/dom/common/registry.test.ts b/packages/x6-vector/src/dom/common/registry.test.ts deleted file mode 100644 index 97a9e5c8c7d..00000000000 --- a/packages/x6-vector/src/dom/common/registry.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { createSVGNode } from '../../util/dom' -import { Fragment, G, Vector } from '../../vector' -import { Dom } from '../dom' -import { Base } from './base' -import { Registry } from './registry' - -describe('Registry', () => { - class Foo extends Base {} - - beforeEach(() => { - Registry.register(Foo, 'foo') - }) - - afterEach(() => { - Registry.unregister('foo') - }) - - describe('getClass()', () => { - it('should return class by Node name', () => { - expect(Registry.getClass('Foo')).toEqual(Foo) - expect(Registry.getClass('foo')).toEqual(Foo) - expect(Registry.getClass('FOO')).toEqual(Foo) - }) - - it('should return class by HTMLElement instance', () => { - expect(Registry.getClass(document.createElement('div'))).toEqual(Dom) - expect(Registry.getClass(document.createElement('span'))).toEqual(Dom) - }) - - it('should return class by SVGElement instance', () => { - expect(Registry.getClass(createSVGNode('g'))).toEqual(G) - expect(Registry.getClass(createSVGNode('gg'))).toEqual(Vector) - }) - - it('should return Fragment', () => { - expect(Registry.getClass(document.createDocumentFragment())).toEqual( - Fragment, - ) - }) - - it('should fallback to return Dom when can not find the associated class', () => { - expect(Registry.getClass('bar')).toEqual(Dom) - }) - }) - - describe('getTagName()', () => { - it('should return the tagName associate with the class', () => { - expect(Registry.getTagName(Foo)).toEqual('foo') - }) - - it('should return null when class not registered', () => { - class Bar extends Base {} - expect(Registry.getTagName(Bar)).toBeNull() - }) - }) - - describe('isRegisted()', () => { - it('should return true when the given tagName indicated class is registered', () => { - expect(Registry.isRegisted('Foo')).toBeTrue() - expect(Registry.isRegisted('foo')).toBeTrue() - expect(Registry.isRegisted('FOO')).toBeTrue() - }) - }) -}) diff --git a/packages/x6-vector/src/dom/common/registry.ts b/packages/x6-vector/src/dom/common/registry.ts deleted file mode 100644 index f119e8fce33..00000000000 --- a/packages/x6-vector/src/dom/common/registry.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { Base } from './base' - -export namespace Registry { - export type Definition = { new (...args: any[]): Base } - - const cache: Record = {} - - export function register(ctor: Definition, name: string) { - cache[name] = ctor - - return ctor - } - - export function unregister(name: string) { - delete cache[name] - } - - export function getClass( - node: Node, - ): TDefinition - export function getClass( - name: string, - ): TDefinition - export function getClass( - node: string | Node, - ) { - let className: string - if (typeof node === 'string') { - className = node - } else { - const nodeName = node.nodeName - if (nodeName === '#document-fragment') { - className = 'Fragment' - } else { - className = nodeName - } - } - - const keys = Object.keys(cache) - let key = keys.find((k) => k.toLowerCase() === className.toLowerCase()) - if (key == null) { - if (typeof node === 'string') { - key = 'Dom' - } else { - key = node instanceof SVGElement ? 'Vector' : 'Dom' - } - } - - return cache[key] as TDefinition - } - - export function getTagName(cls: Definition) { - const keys = Object.keys(cache) - for (let i = 0, l = keys.length; i < l; i += 1) { - const key = keys[i] - if (cache[key] === cls) { - // lcfirst - return key.charAt(0).toLowerCase() + key.substring(1) - } - } - return null - } - - export function isRegisted(tagName: string) { - const lower = tagName.toLowerCase() - const keys = Object.keys(cache) - return keys.some((key) => key.toLowerCase() === lower) - } -} diff --git a/packages/x6-vector/src/dom/dom.test.ts b/packages/x6-vector/src/dom/dom.test.ts deleted file mode 100644 index 7fae266f994..00000000000 --- a/packages/x6-vector/src/dom/dom.test.ts +++ /dev/null @@ -1,1130 +0,0 @@ -import sinon from 'sinon' -import { createSVGNode, namespaces } from '../util/dom' -import { Circle, G, Rect, SVG, TSpan } from '../vector' -import { Dom } from './dom' -import { Fragment } from '../vector/fragment/fragment' - -describe('Dom', () => { - describe('firstChild()', () => { - it('should return the first child', () => { - const g = new G() - const rect = g.rect() - g.circle(100) - expect(g.firstChild()).toBe(rect) - }) - - it('should return `null` if no first child exists', () => { - expect(new G().firstChild()).toBe(null) - }) - }) - - describe('lastChild()', () => { - it('should return the last child of the element', () => { - const g = new G() - g.rect() - const rect = g.rect() - expect(g.lastChild()).toBe(rect) - }) - - it('should return `null` if no last child exists', () => { - expect(new G().lastChild()).toBe(null) - }) - }) - - describe('get()', () => { - it('should return the child at the given position', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle(100) - expect(g.get(0)).toBe(rect) - expect(g.get(1)).toBe(circle) - expect(g.get(2)).toBe(null) - }) - }) - - describe('findOne()', () => { - it('should return the first element matching the given selector', () => { - const g = new G() - const rect1 = g.rect() - const rect2 = g.rect() - expect(g.findOne('rect') === rect1).toBeTrue() - expect(g.findOne('rect') === rect2).toBeFalse() - }) - - it('should return `null` when the given selector matching nothing', () => { - const g = new G() - expect(g.findOne('rect')).toBeNull() - }) - }) - - describe('find()', () => { - it('should return an empty if find nothing', () => { - const g = new G() - expect(g.find('rect')).toBeInstanceOf(Array) - expect(g.find('rect').length).toEqual(0) - }) - - it('should return an array of elements matching the given selector', () => { - const g = new G() - const rect1 = g.rect() - const rect2 = g.rect() - const circle = g.circle(100) - - const rects = g.find('rect') - const circles = g.find('circle') - - expect(rects.length).toEqual(2) - expect(rects.includes(rect1)).toBeTrue() - expect(rects.includes(rect2)).toBeTrue() - - expect(circles.length).toEqual(1) - expect(circles.includes(circle)).toBeTrue() - }) - }) - - describe('matches()', () => { - it('should return `true` if the element matching the given selector', () => { - const g = new G().addClass('foo') - expect(g.matches('.foo')).toBeTrue() - }) - - it('should return `false` if the element not matching the given selector', () => { - const g = new G().addClass('foo') - expect(g.matches('.bar')).toBeFalse() - }) - - it('should return `false` when the node is invalid', () => { - const node = {} - const dom = new Dom() - const mock = dom as any - mock.node = node - expect(dom.matches('foo')).toBeFalse() - }) - }) - - describe('children()', () => { - it('should return an array of children elements', () => { - const g = new G() - - expect(g.children()).toBeInstanceOf(Array) - expect(g.children().length).toEqual(0) - - const rect1 = g.rect() - const rect2 = g.rect() - const circle = g.circle(100) - - const children = g.children() - expect(children.length).toEqual(3) - expect(children.includes(rect1)).toBeTrue() - expect(children.includes(rect2)).toBeTrue() - expect(children.includes(circle)).toBeTrue() - }) - }) - - describe('clear()', () => { - it('should removes all elements from the element', () => { - const g = new G() - g.rect() - g.rect() - g.circle(100) - expect(g.children().length).toEqual(3) - g.clear() - expect(g.children().length).toEqual(0) - }) - }) - - describe('clone()', () => { - it('should clone the current element and returns it', () => { - const rect = new Rect() - const clone = rect.clone() - expect(rect).not.toBe(clone) - expect(clone.type).toBe(rect.type) - }) - - it('should clone the children by default', () => { - const group = new G() - const rect = group.rect() - const clone = group.clone() - expect(clone.get(0)).not.toBe(rect) - expect(clone.get(0)?.type).toEqual(rect.type) - }) - - it('should not clone the children when passing false', () => { - const group = new G() - group.rect() - const clone = group.clone(false) - expect(clone.children()).toEqual([]) - }) - - it('should assign a new id to the element and to child elements', () => { - const group = new G().id('group') - const rect = group.rect().id('rect') - const clone = group.clone() - expect(clone.get(0)!.id()).not.toBe(rect.id()) - expect(clone.id()).not.toBe(group.id()) - }) - - it('should return an instance of the same class the method was called on', () => { - const rect = new Dom('rect') - expect(rect.constructor).toBe(Dom) - expect(rect.clone().constructor).toBe(Dom) - }) - }) - - describe('eachChild()', () => { - it('should iterate over all the children of the element', () => { - const group = new G() - const group2 = group.group() - const circle = group.circle(100) - const spy = sinon.spy() - - group.eachChild(spy) - - expect(spy.callCount).toEqual(2) - expect(spy.args[0]).toEqual([group2, 0, [group2, circle]]) - expect(spy.args[1]).toEqual([circle, 1, [group2, circle]]) - }) - - it('should iterate over all children recursively and executes the passed function on then when deep is true', () => { - const group = new G() - const group2 = group.group() - const rect = group2.rect() - const circle = group.circle(100) - const spy = sinon.spy() - - group.eachChild(spy, true) - - expect(spy.callCount).toEqual(3) - expect(spy.args[0]).toEqual([group2, 0, [group2, circle]]) - expect(spy.args[1]).toEqual([rect, 0, [rect]]) - expect(spy.args[2]).toEqual([circle, 1, [group2, circle]]) - }) - }) - - describe('indexOf()', () => { - it('should return the position of the passed child', () => { - const g = new G() - g.rect() - const rect = g.rect() - expect(g.indexOf(rect)).toBe(1) - expect(g.indexOf(rect.node)).toBe(1) - }) - - it('should return `-1` if element is no child', () => { - const g = new G() - const rect = new Rect() - expect(g.indexOf(rect)).toBe(-1) - }) - }) - - describe('has()', () => { - it('should return `true` if the element has the passed element as child', () => { - const g = new G() - const rect = g.rect() - expect(g.has(rect)).toBe(true) - }) - - it("should return `false` if the element hasn't the passed element as child", () => { - const g = new G() - const rect = new Rect() - expect(g.has(rect)).toBe(false) - }) - }) - - describe('index()', () => { - it('should return the position in the parent of the element', () => { - const g = new G() - g.rect() - const rect = g.rect() - expect(rect.index()).toBe(1) - }) - - it('should return `-1` if the element do not have a parent', () => { - expect(new G().index()).toBe(-1) - }) - }) - - describe('contains()', () => { - it('should return `true` if the element is ancestor of the given element', () => { - const svg = new SVG() - const group1 = svg.group().addClass('test') - const group2 = group1.group() - const rect = group2.rect() - - expect(svg.contains(group1)).toBeTrue() - expect(svg.contains(group1.node)).toBeTrue() - - expect(svg.contains(group2)).toBeTrue() - expect(svg.contains(group2.node)).toBeTrue() - - expect(svg.contains(rect)).toBeTrue() - expect(svg.contains(rect.node)).toBeTrue() - }) - - it('should return `false` if the element is ancestor of the given element', () => { - expect(new SVG().contains(new G())).toBeFalse() - }) - }) - - describe('id()', () => { - it('should return current element when called as setter', () => { - const g = new G() - expect(g.id('asd')).toBe(g) - }) - - it('should set the id with argument given', () => { - expect(new G().id('foo').node.id).toBe('foo') - }) - - it('should get the id when no argument given', () => { - const g = new G({ id: 'foo' }) - expect(g.id()).toBe('foo') - }) - - it('should generate an id on getting if none is set', () => { - const g = new G() - expect(g.node.id).toBe('') - g.id() - expect(g.node.id).not.toBe('') - }) - }) - - describe('parent()', () => { - let svg: SVG - let rect: Rect - let group1: G - let group2: G - - beforeEach(() => { - svg = new SVG().addTo(document.body) - group1 = svg.group().addClass('test') - group2 = group1.group() - rect = group2.rect() - }) - - afterEach(() => { - svg.remove() - }) - - it('should return the svg parent with no argument given', () => { - expect(rect.parent()).toBe(group2) - }) - - it('should return the closest parent with the correct type', () => { - expect(rect.parent(SVG)).toBe(svg) - }) - - it('should return the closest parent matching the selector', () => { - expect(rect.parent('.test')).toBe(group1) - }) - - it('should return `null` if the element do not have a parent', () => { - expect(new SVG().parent()).toBe(null) - }) - - it('should return `null` if it cannot find a parent matching the argument', () => { - expect(rect.parent('.not-there')).toBe(null) - }) - - it('should return `null` if it cannot find a parent matching the argument in a #document-fragment', () => { - const fragment = document.createDocumentFragment() - const svg = new SVG().addTo(fragment) - const rect = svg.rect() - expect(rect.parent('.not-there')).toBe(null) - }) - - it('should return Dom if parent is #document-fragment', () => { - const fragment = document.createDocumentFragment() - const svg = new SVG().addTo(fragment) - expect(svg.parent()).toBeInstanceOf(Dom) - }) - - it('should return html parents, too', () => { - expect(svg.parent()!.node).toBe(document.body) - }) - }) - - describe('parents()', () => { - let div1: Dom - let div2: Dom - let svg: SVG - let rect: Rect - let group1: G - let group2: G - - beforeEach(() => { - div1 = new Dom().appendTo(document.body) - div2 = new Dom().appendTo(div1) - svg = new SVG().appendTo(div2) - group1 = svg.group().addClass('test') - group2 = group1.group() - rect = group2.rect() - }) - - afterEach(() => { - div2.remove() - }) - - it('should return all the ancestors', () => { - expect(div2.parents().length).toEqual(3) - }) - - it('should return all the ancestors until SVGSVGElement', () => { - expect(rect.parents()).toEqual([group2, group1]) - }) - - it('should return all the ancestors until the specified type', () => { - expect(rect.parents(SVG)).toEqual([group2, group1]) - }) - - it('should return all the ancestors until the specified selector', () => { - expect(rect.parents('.test')).toEqual([group2]) - }) - - it('should return all the ancestors until the specified element', () => { - expect(rect.parents(group1)).toEqual([group2]) - }) - - it('should return all the ancestors until the specified node', () => { - expect(rect.parents(group1.node)).toEqual([group2]) - }) - - it('should return all the ancestors until fragment', () => { - const fragment = document.createDocumentFragment() - const div1 = new Dom().appendTo(fragment) - const div2 = new Dom().appendTo(div1) - expect(div2.parents()).toEqual([div1]) - }) - }) - - describe('add()', () => { - it('should add an element as child to the end with no second argument given', () => { - const g = new G() - g.add(new Rect()) - const rect = new Rect() - g.add(rect) - expect(g.children().length).toBe(2) - expect(g.get(1)).toBe(rect) - }) - - it('should add an element at the specified position with second argument given', () => { - const g = new G() - g.add(new Rect()) - g.add(new Rect()) - const rect = new Rect() - g.add(rect, 1) - expect(g.children().length).toBe(3) - expect(g.get(1)).toBe(rect) - }) - - it('should do nothing if element is already the element at that position', () => { - const g = new G() - g.rect() - const rect = g.rect() - g.add(rect, 1) - expect(g.get(1)).toBe(rect) - }) - - it('should handle svg string', () => { - const g = new G() - g.add('') - expect(g.children().length).toBe(1) - expect(g.get(0)).toBeInstanceOf(Rect) - }) - - it('should handle tagName', () => { - const g = new G() - g.add('rect') - expect(g.children().length).toBe(1) - expect(g.get(0)).toBeInstanceOf(Rect) - }) - - it('should add a node', () => { - const g = new G() - const node = createSVGNode('rect') - g.add(node) - expect(g.children().length).toBe(1) - expect(g.get(0)).toBeInstanceOf(Rect) - }) - - it('should add a SVGSVGElement', () => { - const g = new G() - const node = createSVGNode('svg') - g.add(node) - expect(g.children().length).toBe(1) - expect(g.get(0)).toBeInstanceOf(SVG) - }) - }) - - describe('append()', () => { - it('should append the given element as a child', () => { - const g = new G() - const rect = new Rect() - g.rect() - g.append(rect) - expect(rect.index()).toEqual(1) - }) - }) - - describe('prepend()', () => { - it('should prepend the given element as a child', () => { - const g = new G() - const rect = new Rect() - g.rect() - g.prepend(rect) - expect(rect.index()).toEqual(0) - }) - }) - - describe('addTo()', () => { - it('should return the current element', () => { - const g = new G() - const rect = new Rect() - expect(rect.addTo(g)).toBe(rect) - }) - - it('should put an element innto another element', () => { - const g = new G() - const rect = new Rect() - const spy = spyOn(g, 'put') - rect.addTo(g, 0) - expect(spy).toHaveBeenCalledWith(rect, 0) - }) - - it('should work with svg strings', () => { - const rect = new Rect() - rect.addTo('') - expect(rect.parent()).toBeInstanceOf(G) - }) - }) - - describe('appendTo()', () => {}) - - describe('put()', () => { - it('should call `add()` but returns the added element instead', () => { - const g = new G() - const rect = new Rect() - const spy = spyOn(g, 'add').and.callThrough() - expect(g.put(rect, 0)).toBe(rect) - expect(spy).toHaveBeenCalledWith(rect, 0) - }) - - it('should create element from svg string', () => { - const g = new G() - const rect = '' - const spy = sinon.spy(g, 'add') - const ret = g.put(rect, 0) - expect(ret).toBeInstanceOf(Rect) - expect(spy.callCount).toEqual(1) - }) - }) - - describe('putIn()', () => { - it('should call add on the given parent', () => { - const g = new G() - const rect = new Rect() - const spy = sinon.spy(g, 'add') - rect.putIn(g, 0) - expect(spy.callCount).toEqual(1) - }) - - it('should return the passed element', () => { - const g = new G() - const rect = new Rect() - expect(rect.putIn(g, 0)).toBe(g) - }) - }) - - describe('replace()', () => { - it('should return the new element', () => { - const g = new G() - const rect = g.rect() - const circle = new Circle() - expect(rect.replace(circle)).toBe(circle) - }) - - it('should replace the child at the correct position', () => { - const g = new G() - const rect1 = g.rect() - const rect2 = g.rect() - const rect3 = g.rect() - const circle = new Circle() - rect2.replace(circle) - expect(g.children()).toEqual([rect1, circle, rect3]) - }) - - it('should also work without a parent', () => { - const rect = new Rect() - const circle = new Circle() - expect(rect.replace(circle)).toBe(circle) - }) - }) - - describe('element()', () => { - it('should creates an element of given tagName and appends it to the current element', () => { - const g = new G() - const rect = g.element('rect') - expect(rect.type).toBe('rect') - }) - - it('should set the specified attributes passed as second argument', () => { - const g = new G() - const rect = g.element('rect', { id: 'foo' }) - expect(rect.id()).toBe('foo') - }) - }) - - describe('remove()', () => { - it('should return the removed element', () => { - const g = new G() - const rect = g.rect() - expect(rect.remove()).toBe(rect) - }) - - it('should remove the element from the parent', () => { - const g = new G() - const rect = g.rect() - expect(g.children()).toEqual([rect]) - rect.remove() - expect(g.children()).toEqual([]) - }) - - it('should do nothing when element is not attached to the dom', () => { - const rect = new Rect() - expect(rect.remove()).toBe(rect) - }) - - it('should also works when direct child of document-fragment', () => { - const fragment = new Fragment() - const rect = new Rect().appendTo(fragment) - expect(fragment.children()).toEqual([rect]) - expect(rect.remove()).toBe(rect) - expect(fragment.children()).toEqual([]) - }) - }) - - describe('removeChild()', () => { - it('should return the element itself', () => { - const g = new G() - const rect = g.rect() - expect(g.removeChild(rect)).toBe(g) - }) - - it('should remove the given child element', () => { - const g = new G() - const rect = g.rect() - expect(g.removeChild(rect).children()).toEqual([]) - }) - - it('should remove the given child node', () => { - const g = new G() - const rect = g.rect() - expect(g.removeChild(rect.node).children()).toEqual([]) - }) - - it('should throw error if the given element is not a child', () => { - const g = new G() - const rect = new Rect() - expect(() => g.removeChild(rect)).toThrowError() - }) - }) - - describe('before()', () => { - it('should add element before me', () => { - const g = new G() - g.rect() - const circle = g.circle() - g.ellipse() - const rect = new Rect() - circle.before(rect) - expect(rect.parent()).toBe(g) - expect(rect.index()).toEqual(1) - }) - }) - - describe('after()', () => { - it('should add element after me', () => { - const g = new G() - g.rect() - const circle = g.circle() - g.ellipse() - const rect = new Rect() - circle.after(rect) - expect(rect.parent()).toBe(g) - expect(rect.index()).toEqual(2) - }) - }) - - describe('insertBefore()', () => { - it('should insert element before me', () => { - const g = new G() - g.rect() - const circle = g.circle() - g.ellipse() - const rect = new Rect() - rect.insertBefore(circle) - expect(rect.parent()).toBe(g) - expect(rect.index()).toEqual(1) - }) - }) - - describe('insertAfter()', () => { - it('should add element after me', () => { - const g = new G() - g.rect() - const circle = g.circle() - g.ellipse() - const rect = new Rect() - rect.insertAfter(circle) - expect(rect.parent()).toBe(g) - expect(rect.index()).toEqual(2) - }) - }) - - describe('siblings()', () => { - it('should return an empty array when element has no parent', () => { - expect(new G().siblings()).toEqual([]) - }) - - it('should return an empty array when element has no siblings', () => { - const rect = new G().rect() - expect(rect.siblings()).toEqual([]) - }) - - it('should return all siblings', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse() - - expect(circle.siblings()).toEqual([rect, ellipse]) - expect(circle.siblings(false)).toEqual([rect, ellipse]) - }) - - it('should return all siblings include me', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse() - - expect(circle.siblings(true)).toEqual([rect, circle, ellipse]) - }) - - it('should return all siblings matching the given selector', () => { - const g = new G() - g.rect() - const circle = g.circle().addClass('foo') - const ellipse = g.ellipse().addClass('foo') - - expect(circle.siblings('.foo')).toEqual([ellipse]) - }) - - it('should return all siblings matching the given selector include me', () => { - const g = new G() - g.rect() - const circle = g.circle().addClass('foo') - const ellipse = g.ellipse().addClass('foo') - - expect(circle.siblings('.foo', true)).toEqual([circle, ellipse]) - }) - }) - - describe('next()', () => { - it('should return the first next sibling', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse() - - expect(g.next()).toBe(null) - expect(rect.next()).toBe(circle) - expect(circle.next()).toBe(ellipse) - expect(ellipse.next()).toBe(null) - }) - - it('should return the first next sibling matching the selector', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse().addClass('foo') - - expect(g.next()).toBe(null) - expect(rect.next('.foo')).toBe(ellipse) - expect(circle.next('.foo')).toBe(ellipse) - expect(ellipse.next()).toBe(null) - }) - }) - - describe('nextAll()', () => { - it('should return the all the next siblings', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse() - - expect(g.nextAll()).toEqual([]) - expect(rect.nextAll()).toEqual([circle, ellipse]) - expect(circle.nextAll()).toEqual([ellipse]) - expect(ellipse.nextAll()).toEqual([]) - }) - - it('should return all the next siblings matching the selector', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse1 = g.ellipse().addClass('foo') - const ellipse2 = g.ellipse().addClass('foo') - - expect(g.nextAll()).toEqual([]) - expect(rect.nextAll('.foo')).toEqual([ellipse1, ellipse2]) - expect(circle.nextAll('.foo')).toEqual([ellipse1, ellipse2]) - }) - }) - - describe('prev()', () => { - it('should return the first previous sibling', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse() - - expect(g.prev()).toBe(null) - expect(rect.prev()).toBe(null) - expect(circle.prev()).toBe(rect) - expect(ellipse.prev()).toBe(circle) - }) - - it('should return the first previous sibling matching the selector', () => { - const g = new G() - const rect = g.rect().addClass('foo') - const circle = g.circle() - const ellipse = g.ellipse() - - expect(g.prev()).toBe(null) - expect(rect.prev()).toBe(null) - expect(circle.prev('.foo')).toBe(rect) - expect(ellipse.prev('.foo')).toBe(rect) - }) - }) - - describe('prevAll()', () => { - it('should return the all the previous siblings', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse() - - expect(g.prevAll()).toEqual([]) - expect(rect.prevAll()).toEqual([]) - expect(circle.prevAll()).toEqual([rect]) - expect(ellipse.prevAll()).toEqual([circle, rect]) - }) - - it('should return all the previous siblings matching the selector', () => { - const g = new G() - const rect1 = g.rect().addClass('foo') - const rect2 = g.rect().addClass('foo') - const circle = g.circle() - const ellipse = g.ellipse() - - expect(g.prevAll()).toEqual([]) - expect(circle.prevAll('.foo')).toEqual([rect2, rect1]) - expect(ellipse.prevAll('.foo')).toEqual([rect2, rect1]) - }) - }) - - describe('forward()', () => { - it('should move forward the element', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse() - - expect(rect.index()).toEqual(0) - expect(circle.index()).toEqual(1) - expect(ellipse.index()).toEqual(2) - - circle.forward() - - expect(rect.index()).toEqual(0) - expect(circle.index()).toEqual(2) - expect(ellipse.index()).toEqual(1) - }) - - it('should do nothing when the element do not has a parent', () => { - const g = new G() - expect(g.index()).toEqual(-1) - g.forward() - expect(g.index()).toEqual(-1) - }) - }) - - describe('backward()', () => { - it('should move backward the element', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse() - - expect(rect.index()).toEqual(0) - expect(circle.index()).toEqual(1) - expect(ellipse.index()).toEqual(2) - - circle.backward() - - expect(rect.index()).toEqual(1) - expect(circle.index()).toEqual(0) - expect(ellipse.index()).toEqual(2) - }) - - it('should do nothing when the element do not has a parent', () => { - const g = new G() - expect(g.index()).toEqual(-1) - g.forward() - expect(g.index()).toEqual(-1) - }) - }) - - describe('front()', () => { - it('should move element to front', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse() - - expect(rect.index()).toEqual(0) - expect(circle.index()).toEqual(1) - expect(ellipse.index()).toEqual(2) - - circle.front() - - expect(rect.index()).toEqual(0) - expect(circle.index()).toEqual(2) - expect(ellipse.index()).toEqual(1) - }) - - it('should do nothing when the element do not has a parent', () => { - const g = new G() - expect(g.index()).toEqual(-1) - g.forward() - expect(g.index()).toEqual(-1) - }) - }) - - describe('back()', () => { - it('should move element to back', () => { - const g = new G() - const rect = g.rect() - const circle = g.circle() - const ellipse = g.ellipse() - - expect(rect.index()).toEqual(0) - expect(circle.index()).toEqual(1) - expect(ellipse.index()).toEqual(2) - - circle.back() - - expect(rect.index()).toEqual(1) - expect(circle.index()).toEqual(0) - expect(ellipse.index()).toEqual(2) - }) - - it('should do nothing when the element do not has a parent', () => { - const g = new G() - expect(g.index()).toEqual(-1) - g.forward() - expect(g.index()).toEqual(-1) - }) - }) - - describe('wrap()', () => { - let svg: SVG - let rect: Rect - - beforeEach(function () { - svg = new SVG() - rect = svg.rect() - }) - - it('should return the current element', function () { - expect(rect.wrap(new G())).toBe(rect) - }) - - it('should wrap the passed element around the current element', function () { - const g = new G() - expect(rect.wrap(g).parent()).toBe(g) - expect(g.parent()).toBe(svg) - }) - - it('should wrap also when element is not in the dom', () => { - const g = new G() - const rect = new Rect() - expect(rect.wrap(g).parent()).toBe(g) - expect(g.parent()).toBe(null) - }) - - it('should insert at the correct position', () => { - svg.rect() - rect = svg.rect() - const position = rect.index() - const g = new G() - expect(rect.wrap(g).parent()?.index()).toBe(position) - }) - - it('should allow to pass an svg string as element', () => { - rect.wrap('') - expect(rect.parent()).toBeInstanceOf(G) - expect(rect.parent()!.parent()).toBe(svg) - }) - - it('should allow to pass an svg node as element', () => { - const node = createSVGNode('g') - rect.wrap(node) - expect(rect.parent()).toBeInstanceOf(G) - expect(rect.parent()!.node).toBe(node) - expect(rect.parent()!.parent()).toBe(svg) - }) - }) - - describe('words()', () => { - it('should set the nodes textContent to the given value', () => { - const tspan = new TSpan().words('Hello World') - expect(tspan.text()).toBe('Hello World') - }) - }) - - describe('toString()', () => { - it('should call `id()` and returns its result', () => { - const rect = new Rect({ id: 'foo' }) - const spy = sinon.spy(rect, 'id') - expect(rect.toString()).toBe('foo') - expect(spy.callCount).toEqual(1) - }) - }) - - describe('html()', () => { - it('should call xml with the html namespace', () => { - const group = new G() - const spy = sinon.spy(group, 'xml') - group.html('') - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual(['', undefined, namespaces.html]) - }) - }) - - describe('xml()', () => { - describe('setter', () => { - it('should return itself', () => { - const g = new G() - expect(g.xml('', undefined, namespaces.svg)).toBe(g) - }) - - it('should import a single element', () => { - const g = new G().xml('', undefined, namespaces.svg) - expect(g.children()[0]).toBeInstanceOf(Rect) - expect(g.children()[0].node.namespaceURI).toBe(namespaces.svg) - }) - - it('should import multiple elements', () => { - const g = new G().xml('', undefined, namespaces.svg) - expect(g.children()[0]).toBeInstanceOf(Rect) - expect(g.children()[1]).toBeInstanceOf(Circle) - }) - - it('should replace the current element with the imported elements with `outerXML` is `true`', () => { - const svg = new SVG() - const g = svg.group() - g.xml('', true, namespaces.svg) - expect(svg.children()[0]).toBeInstanceOf(Rect) - expect(svg.children()[1]).toBeInstanceOf(Circle) - }) - - it('should return the parent when `outerXML` is `true`', () => { - const svg = new SVG() - const g = svg.group() - expect(g.xml('', true, namespaces.svg)).toBe(svg) - expect(svg.children()[0]).toBeInstanceOf(Rect) - expect(svg.children()[1]).toBeInstanceOf(Circle) - }) - - it('should work without a parent', () => { - const svg = new SVG() - expect(svg.xml('', undefined, namespaces.svg)).toBe( - svg, - ) - }) - - it('should use html namesapce by default', () => { - const div = new Dom() - div.xml('') - expect(div.children()[0].node.namespaceURI).toBe(namespaces.html) - }) - }) - - describe('getter', () => { - let svg: SVG - let group: G - let rect: Rect - - beforeEach(() => { - svg = new SVG().removeNamespace() - group = svg.group() - rect = group.rect(123.456, 234.567) - }) - - it('should return the svg string of the element by default', () => { - expect(rect.xml()).toBe( - '', - ) - expect(svg.xml()).toBe( - '', - ) - }) - - it('should return the innerHtml when outerHtml = false', () => { - expect(rect.xml(false)).toBe('') - expect(svg.xml(false)).toBe( - '', - ) - }) - - it('should run a function on every exported node', () => { - expect(rect.xml((el) => el.round(1))).toBe( - '', - ) - }) - - it('should run a function on every exported node and replaces node with returned node if return value is not falsy', () => { - expect(rect.xml(() => new Circle())).toBe('') - expect(svg.xml(() => new G())).toBe('') - expect( - svg.xml((el) => { - if (el instanceof Rect) return new Circle() - if (el instanceof SVG) el.removeNamespace() - }), - ).toBe('') - }) - - it('should run a function on every exported node and removes node if return value is false', () => { - expect(group.xml(() => false)).toBe('') - expect(svg.xml(() => false)).toBe('') - expect( - svg.xml((el) => { - if (el instanceof SVG) { - el.removeNamespace() - } else { - return false - } - }), - ).toBe('') - }) - - it('should run a function on every inner node and exports it when `outerXML` is `false`', () => { - expect(svg.xml(() => false, false)).toBe('') - expect(svg.xml(() => undefined, false)).toBe( - '', - ) - }) - }) - }) -}) diff --git a/packages/x6-vector/src/dom/dom.ts b/packages/x6-vector/src/dom/dom.ts deleted file mode 100644 index ba8a9f3e08f..00000000000 --- a/packages/x6-vector/src/dom/dom.ts +++ /dev/null @@ -1,750 +0,0 @@ -import { Global } from '../global' -import { ID } from './common/id' -import * as Util from '../util/dom' -import { Adopter } from './common/adopter' -import { Registry } from './common/registry' -import { Primer } from './primer' -import { Transform } from './transform/transform' -import type { AttributesMap } from './attributes' -import type { ElementMap } from '../types' - -@Dom.register('Dom') -@Dom.mixin(Transform) -export class Dom extends Primer { - /** - * Returns the first child of the element. - */ - firstChild(): T | null { - return Dom.adopt(this.node.firstChild) - } - - /** - * Returns the last child of the element. - */ - lastChild(): T | null { - return Dom.adopt(this.node.lastChild) - } - - /** - * Returns an element on a given position in the element's children array. - */ - get(index: number): T | null { - return Dom.adopt(this.node.childNodes[index]) - } - - /** - * Returns an array of elements matching the given selector. - */ - find(selector: string): T[] { - return Dom.find(selector, this.node) - } - - /** - * Returns the first element matching the given selector. - */ - findOne(selector: string): T | null { - return Dom.findOne(selector, this.node) - } - - /** - * Returns `true` if the element matching the given selector. - */ - matches(selector: string): boolean { - const elem = this.node - const node = this.node as any - const matcher = - elem.matches || - node.matchesSelector || - node.webkitMatchesSelector || - node.msMatchesSelector || - node.mozMatchesSelector || - node.oMatchesSelector || - null - return matcher ? matcher.call(elem, selector) : false - } - - /** - * Returns an array of children elements. - */ - children(): T[] { - const elems: T[] = [] - this.node.childNodes.forEach((node) => { - elems.push(Dom.adopt(node)) - }) - return elems - } - - /** - * Removes all elements from the element. - */ - clear() { - while (this.node.lastChild) { - this.node.lastChild.remove() - } - return this - } - - /** - * Returns an exact copy of the element. - */ - clone(deep = true) { - // write dom data to the dom so the clone can pickup the data - this.storeAffix(deep) - // clone element and assign new id - const Ctor = this.constructor as new (node: Element) => ElementMap - const cloned = this.node.cloneNode(deep) as Element - ID.overwrite(cloned, true) - return new Ctor(cloned) - } - - /** - * Iterates over all the children of the element. - * Deep traversing is also possible by passing `true` as the second argument. - * @returns - */ - eachChild( - iterator: (this: T, child: T, index: number, children: T[]) => void, - deep?: boolean, - ) { - const children = this.children() - for (let i = 0, l = children.length; i < l; i += 1) { - const child = children[i] - iterator.call(child, child, i, children) - if (deep) { - child.eachChild(iterator, deep) - } - } - - return this - } - - /** - * Returns the index of given node. - * Returns `-1` when it is not a child. - */ - indexOf(node: Node): number - /** - * Returns the index of given element. - * Returns `-1` when it is not a child. - */ - indexOf(element: Dom): number - /** - * Returns the index of given element or node. - * Returns `-1` when it is not a child. - */ - indexOf(element: Dom | Node): number - indexOf(element: Dom | Node): number { - const children = Array.prototype.slice.call(this.node.childNodes) as Node[] - return children.indexOf(element instanceof Node ? element : element.node) - } - - /** - * Returns `true` when the given node is a child of the element. - */ - has(node: Node): boolean - /** - * Returns `true` when the given element is a child of the element. - */ - has(element: Dom): boolean - /** - * Returns `true` when the given element or node is a child of the element. - */ - has(element: Dom | Node): boolean - has(element: Dom | Node): boolean { - return this.indexOf(element) !== -1 - } - - /** - * Returns the index of the element in it's parent. - * Returns `-1` when the element do not have a parent. - */ - index(): number { - const parent: Dom | null = this.parent() - return parent ? parent.indexOf(this) : -1 - } - - contains(node: Node): boolean - contains(element: Dom): boolean - contains(element: Dom | Node): boolean { - return Util.isAncestorOf( - this.node, - element instanceof Node ? element : element.node, - ) - } - - /** - * Returns the element's id, generate new id if no id set. - */ - id(): string - /** - * Set id of the element. - */ - id(id: string | null): this - id(id?: string | null) { - // generate new id if no id set - if (typeof id === 'undefined' && !this.node.id) { - this.node.id = ID.generate() - } - - // dont't set directly with this.node.id to make `null` work correctly - return typeof id === 'undefined' ? this.attr('id') : this.attr('id', id) - } - - /** - * Returns the parent element if exist. - */ - parent(): T | null - /** - * Iterates over the ancestors and returns the ancestor mathcing the selector. - */ - parent(selector: string): T | null - /** - * Iterates over the ancestors and returns the ancestor mathcing the type. - */ - parent(parentType: Registry.Definition): T | null - parent(selector?: string | Registry.Definition): T | null - parent( - selector?: string | Registry.Definition, - ): T | null { - if (this.node.parentNode == null) { - return null - } - - let parent: T | null = Dom.adopt(this.node.parentNode) - - if (selector == null) { - return parent - } - - // loop trough ancestors if type is given - do { - if ( - typeof selector === 'string' - ? parent.matches(selector) - : parent instanceof selector - ) { - return parent - } - } while ((parent = Dom.adopt(parent.node.parentNode))) - - return null - } - - parents(): T[] - parents(until: null): T[] - parents(untilSelector: string): T[] - parents(untilType: Registry.Definition): T[] - parents(untilNode: Node): T[] - parents(untilElement: Dom): T[] - parents( - until: string | Registry.Definition | Node | Dom | null, - ): T[] - parents( - until?: string | Registry.Definition | Node | Dom | null, - ) { - if (until == null && this.node instanceof Global.window.SVGElement) { - until = Registry.getClass('Svg') // eslint-disable-line - } - - const match = (elem: Dom) => { - if (until == null) { - return true - } - - if (typeof until === 'string') { - return !elem.matches(until) - } - - if (until instanceof Primer) { - return elem !== until - } - - if (until instanceof Global.window.Node) { - return elem.node !== until - } - - return !(elem instanceof until) - } - - const parents: T[] = [] - let parent = this.parent() - while ( - parent && - !parent.isDocument() && - !parent.isDocumentFragment() && - match(parent) - ) { - parents.push(parent as T) - parent = parent.parent() - } - return parents - } - - add(element: T, index?: number): this - add(node: T, index?: number): this - add(selector: string, index?: number): this - add(element: Adopter.Target, index?: number): this - add(element: Adopter.Target, index?: number): this { - const instance = Adopter.makeInstance(element) - - // If non-root svg nodes are added we have to remove their namespaces - if (instance.isSVGSVGElement()) { - const svg = Dom.adopt(instance.node as SVGSVGElement) - svg.removeNamespace() - } - - if (index == null) { - this.node.appendChild(instance.node) - } else if (instance.node !== this.node.childNodes[index]) { - this.node.insertBefore(instance.node, this.node.childNodes[index]) - } - - return this - } - - append(element: T): this - append(node: T): this - append(selector: string): this - append(element: Adopter.Target): this - append(element: Adopter.Target): this { - return this.add(element) - } - - prepend(element: T): this - prepend(node: T): this - prepend(selector: string): this - prepend(element: Adopter.Target): this - prepend(element: Adopter.Target): this { - return this.add(element, 0) - } - - addTo(parentElement: T, index?: number): this - addTo(parentNode: T, index?: number): this - addTo(selector: string, index?: number): this - addTo(parent: Adopter.Target, index?: number): this - addTo(parent: Adopter.Target, index?: number): this { - return Adopter.makeInstance(parent).put(this, index) - } - - appendTo(parentElement: T): this - appendTo(parentNode: T): this - appendTo(selector: string): this - appendTo(parent: Adopter.Target): this - appendTo(parent: Adopter.Target): this { - return this.addTo(parent) - } - - /** - * Adds the given element to the end fo child list or the optional child - * position and returns the added element. - */ - put(element: T, index?: number): T - /** - * Adds the given node to the end fo child list or the optional child position - * and returns the added element. - */ - put(node: T, index?: number): ElementMap - /** - * Adds the node matching the selector to end fo child list or the optional - * child position and returns the added element. - */ - put(selector: string, index?: number): T - put(element: Adopter.Target, index?: number): T - put(element: Adopter.Target, index?: number): T { - const instance = Adopter.makeInstance(element) - this.add(instance, index) - return instance - } - - putIn(parentElement: T, index?: number): T - putIn(parentNode: T, index?: number): ElementMap - putIn(selector: string, index?: number): T - putIn(parent: Adopter.Target, index?: number): T - putIn(parent: Adopter.Target, index?: number): T { - return Adopter.makeInstance(parent).add(this, index) - } - - replace(element: T, index?: number): T - replace(node: T, index?: number): ElementMap - replace(selector: string, index?: number): T - replace(element: Adopter.Target, index?: number): T - replace(element: Adopter.Target): T { - const instance = Adopter.makeInstance(element) - - if (this.node.parentNode) { - this.node.parentNode.replaceChild(instance.node, this.node) - } - - return instance - } - - /** - * Creates an element of given tagName and appends it to the current element. - */ - element( - tagName: T, - attrs?: AttributesMap | null, - ): ElementMap - element( - tagName: T, - attrs?: AttributesMap | null, - ): ElementMap - element( - tagName: string, - attrs?: AttributesMap | null, - ): T { - const registed = - tagName.toLowerCase() !== 'dom' && Registry.isRegisted(tagName) - const elem = Adopter.makeInstance(tagName, !registed) - if (attrs) { - elem.attr(attrs) - } - return this.put(elem) - } - - remove() { - const parent = this.parent() - if (parent) { - parent.removeChild(this) - } - - return this - } - - removeChild(node: Node): this - removeChild(element: Dom): this - removeChild(element: Dom | Node) { - this.node.removeChild(element instanceof Node ? element : element.node) - return this - } - - before(element: T): this - before(node: T): this - before(tagName: string): this - before(element: Adopter.Target): this - before(element: Adopter.Target) { - const parent = this.parent() - if (parent) { - const index = this.index() - const instance = Adopter.makeInstance(element) - instance.remove() - parent.add(instance, index) - } - - return this - } - - after(element: T): this - after(node: T): this - after(tagName: string): this - after(element: Adopter.Target): this - after(element: Adopter.Target) { - const parent = this.parent() - if (parent) { - const index = this.index() - const instance = Adopter.makeInstance(element) - instance.remove() - parent.add(element, index + 1) - } - return this - } - - insertBefore(element: T): this - insertBefore(node: T): this - insertBefore(element: Adopter.Target): this - insertBefore(element: Adopter.Target) { - Adopter.makeInstance(element).before(this) - return this - } - - insertAfter(element: T): this - insertAfter(node: T): this - insertAfter(element: Adopter.Target): this - insertAfter(element: Adopter.Target) { - Adopter.makeInstance(element).after(this) - return this - } - - siblings(): T[] - siblings(selfInclued?: boolean): T[] - siblings(selector: string, selfInclued?: boolean): T[] - siblings(selector?: string | boolean, selfInclued?: boolean) { - const parent = this.parent() - const children = parent ? parent.children() : [] - - if (selector == null) { - return children.filter((child) => child !== this) - } - - if (typeof selector === 'boolean') { - return selector ? children : children.filter((child) => child !== this) - } - - return children.filter( - (child) => child.matches(selector) && (selfInclued || child !== this), - ) - } - - next(): T | null - next(selector: string): T | null - next(selector?: string): T | null { - const parent = this.parent() - if (parent) { - const index = this.index() - const children = parent.children() - for (let i = index + 1, l = children.length; i < l; i += 1) { - const next = children[i] - if (selector == null || next.matches(selector)) { - return next - } - } - } - return null - } - - nextAll(): T[] - nextAll(selector: string): T[] - nextAll(selector?: string): T[] { - const result: T[] = [] - const parent = this.parent() - if (parent) { - const index = this.index() - const children = parent.children() - for (let i = index + 1, l = children.length; i < l; i += 1) { - const next = children[i] - if (selector == null || next.matches(selector)) { - result.push(next) - } - } - } - return result - } - - prev(): T | null - prev(selector: string): T | null - prev(selector?: string): T | null { - const parent = this.parent() - if (parent) { - const index = this.index() - const children = parent.children() - for (let i = index - 1; i >= 0; i -= 1) { - const previous = children[i] - if (selector == null || previous.matches(selector)) { - return previous - } - } - } - - return null - } - - prevAll(): T[] - prevAll(selector: string): T[] - prevAll(selector?: string): T[] { - const result: T[] = [] - const parent = this.parent() - if (parent) { - const index = this.index() - const children = parent.children() - for (let i = index - 1; i >= 0; i -= 1) { - const previous = children[i] - if (selector == null || previous.matches(selector)) { - result.push(previous) - } - } - } - return result - } - - forward() { - const parent = this.parent() - if (parent) { - const index = this.index() - parent.add(this.remove(), index + 1) - } - return this - } - - backward() { - const parent = this.parent() - if (parent) { - const index = this.index() - parent.add(this.remove(), index ? index - 1 : 0) - } - return this - } - - front() { - const parent = this.parent() - if (parent) { - parent.add(this.remove()) - } - return this - } - - back() { - const parent = this.parent() - if (parent) { - parent.add(this.remove(), 0) - } - return this - } - - wrap(element: T): this - wrap(node: T): this - wrap(selector: string): this - wrap(element: Adopter.Target): this - wrap(node: Adopter.Target): this { - const parent = this.parent() - if (!parent) { - return this.addTo(node) - } - - const index = parent.indexOf(this) - return parent.put(node, index).put(this) - } - - words(text: string) { - this.node.textContent = text - return this - } - - /** - * Returns the is of the node. - */ - toString() { - return this.id() - } - - html(): string - html(outerHTML: boolean): string - html( - process: (dom: Dom) => boolean | undefined | Dom, - outerHTML?: boolean, - ): string - html(content: string, outerHTML?: boolean): string - html( - arg1?: boolean | string | ((dom: Dom) => boolean | undefined | Dom), - arg2?: boolean, - ) { - return this.xml(arg1, arg2, Util.namespaces.html) - } - - xml(): string - xml(outerXML: boolean): string - xml( - process: (dom: Dom) => boolean | undefined | Dom, - outerXML?: boolean, - ): string - xml(content: string, outerXML?: boolean, ns?: string): this - xml(content: string, outerXML: true, ns?: string): T - xml( - arg1?: boolean | string | ((dom: Dom) => boolean | undefined | Dom), - arg2?: boolean, - arg3?: string, - ): string - xml( - arg1?: boolean | string | ((dom: Dom) => boolean | undefined | Dom), - arg2?: boolean, - arg3?: string, - ) { - const content = typeof arg1 === 'boolean' ? null : arg1 - let isOuterXML = typeof arg1 === 'boolean' ? arg1 : arg2 - const ns = arg3 - - // getter - // ------ - if (content == null || typeof content === 'function') { - // The default for exports is, that the outerNode is included - isOuterXML = isOuterXML == null ? true : isOuterXML - - this.storeAffix(true) - - let current: Dom = this // eslint-disable-line - - // An export modifier was passed - if (typeof content === 'function') { - current = Dom.adopt(current.node.cloneNode(true)) - - // If the user wants outerHTML we need to process this node, too - if (isOuterXML) { - const result = content(current) - if (result && typeof result !== 'boolean') { - current = result - } - - // The user does not want this node? Well, then he gets nothing - if (result === false) { - return '' - } - } - - // Deep loop through all children and apply modifier - current.eachChild((child) => { - const result = content(child) - const next = result && typeof result !== 'boolean' ? result : child - - if (result === false) { - // If modifier returns false, discard node - child.remove() - } else if (child !== next) { - // If modifier returns new node, use it - child.replace(next) - } - }, true) - } - - const element = current.node as Element - return isOuterXML ? element.outerHTML : element.innerHTML - } - - // setter - // ------ - { - // The default for import is, that the current node is not replaced - isOuterXML = isOuterXML == null ? false : isOuterXML - - const wrapper = Util.createNode('wrapper', ns || Util.namespaces.html) - const fragment = Global.document.createDocumentFragment() - - wrapper.innerHTML = content - - for (let i = wrapper.children.length; i > 0; i -= 1) { - fragment.appendChild(wrapper.firstElementChild!) - } - - if (isOuterXML) { - const parent = this.parent() - this.replace(fragment) - return parent - } - - return this.add(fragment) - } - } -} - -export interface Dom - extends Transform {} - -export namespace Dom { - export const adopt = Adopter.adopt - - export function find( - selectors: string, - parent: Element | Document = Global.document, - ) { - const elems: T[] = [] - parent - .querySelectorAll(selectors) - .forEach((node) => elems.push(adopt(node))) - return elems - } - - export function findOne( - selectors: string, - parent: Element | Document = Global.document, - ) { - return adopt(parent.querySelector(selectors)) - } -} diff --git a/packages/x6-vector/src/dom/events/alias.ts b/packages/x6-vector/src/dom/events/alias.ts deleted file mode 100644 index 26c1c672a5d..00000000000 --- a/packages/x6-vector/src/dom/events/alias.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type EventRaw = Event -export type UIEventRaw = UIEvent -export type MouseEventRaw = MouseEvent -export type DragEventRaw = DragEvent -export type KeyboardEventRaw = KeyboardEvent -export type TouchEventRaw = TouchEvent -export type FocusEventRaw = FocusEvent diff --git a/packages/x6-vector/src/dom/events/core.ts b/packages/x6-vector/src/dom/events/core.ts deleted file mode 100644 index 5f815371c6c..00000000000 --- a/packages/x6-vector/src/dom/events/core.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { isWindow } from '../../util/dom' -import { Global } from '../../global' -import { Util } from './util' -import { Hook } from './hook' -import { Store } from './store' -import { EventObject } from './object' -import { EventHandler } from './types' - -export namespace Core { - let triggered: string | undefined - - export function on( - elem: Store.EventTarget, - types: string, - handler: - | EventHandler - | ({ - handler: EventHandler - selector?: string - } & Partial), - data?: any, - selector?: string, - ) { - if (!Util.isValidTarget(elem)) { - return - } - - // Caller can pass in an object of custom data in lieu of the handler - let handlerData: any - if (typeof handler !== 'function') { - const { handler: h, selector: s, ...others } = handler - handler = h // eslint-disable-line - selector = s // eslint-disable-line - handlerData = others - } - - // Ensure that invalid selectors throw exceptions at attach time - if (!Util.isValidSelector(elem, selector)) { - throw new Error('Delegate event with invalid selector.') - } - - const store = Store.ensure(elem) - - // Ensure the main handle - let mainHandler = store.handler - if (mainHandler == null) { - mainHandler = store.handler = function (e, ...args: any[]) { - return triggered !== e.type ? dispatch(elem, e, ...args) : undefined - } - } - - // Make sure that the handler has a unique ID, used to find/remove it later - const guid = Util.ensureHandlerId(handler) - - // Handle multiple events separated by a space - Util.splitType(types).forEach((item) => { - const { originType, namespaces } = Util.normalizeType(item) - - // There *must* be a type, no attaching namespace-only handlers - if (!originType) { - return - } - - let type = originType - let hook = Hook.get(type) - - // If selector defined, determine special event type, otherwise given type - type = (selector ? hook.delegateType : hook.bindType) || type - - // Update hook based on newly reset type - hook = Hook.get(type) - - // handleObj is passed to all event handlers - const handleObj: Store.HandlerObject = { - type, - originType, - data, - selector, - guid, - handler: handler as EventHandler, - namespace: namespaces.join('.'), - ...handlerData, - } - - // Init the event handler queue if we're the first - const events = store.events - let bag = events[type] - if (!bag) { - bag = events[type] = { handlers: [], delegateCount: 0 } - - // Only use addEventListener if the `hook.steup` returns false - if ( - !hook.setup || - hook.setup(elem, data, namespaces, mainHandler!) === false - ) { - Util.addEventListener( - elem as Element, - type, - mainHandler as any as EventListener, - ) - } - } - - if (hook.add) { - Util.removeHandlerId(handleObj.handler) - hook.add(elem, handleObj) - Util.setHandlerId(handleObj.handler, guid) - } - - // Add to the element's handler list, delegates in front - if (selector) { - bag.handlers.splice(bag.delegateCount, 0, handleObj) - bag.delegateCount += 1 - } else { - bag.handlers.push(handleObj) - } - }) - } - - export function off( - elem: Store.EventTarget, - types: string, - handler?: EventHandler, - selector?: string, - mappedTypes?: boolean, - ) { - const store = Store.get(elem) - if (!store) { - return - } - - const events = store.events - if (!events) { - return - } - - // Once for each type.namespace in types; type may be omitted - Util.splitType(types).forEach((item) => { - const { originType, namespaces } = Util.normalizeType(item) - - // Unbind all events (on this namespace, if provided) for the element - if (!originType) { - Object.keys(events).forEach((key) => { - off(elem, key + item, handler, selector, true) - }) - return - } - - let type = originType - const hook = Hook.get(type) - type = (selector ? hook.delegateType : hook.bindType) || type - const bag = events[type] || {} - const rns = - namespaces.length > 0 - ? new RegExp(`(^|\\.)${namespaces.join('\\.(?:.*\\.|)')}(\\.|$)`) - : null - - // Remove matching events - const originHandlerCount = bag.handlers.length - for (let i = bag.handlers.length - 1; i >= 0; i -= 1) { - const handleObj = bag.handlers[i] - if ( - (mappedTypes || originType === handleObj.originType) && - (!handler || Util.getHandlerId(handler) === handleObj.guid) && - (rns == null || - (handleObj.namespace && rns.test(handleObj.namespace))) && - (selector == null || - selector === handleObj.selector || - (selector === '**' && handleObj.selector)) - ) { - bag.handlers.splice(i, 1) - - if (handleObj.selector) { - bag.delegateCount -= 1 - } - - if (hook.remove) { - hook.remove(elem, handleObj) - } - } - } - - if (originHandlerCount && bag.handlers.length === 0) { - if ( - !hook.teardown || - hook.teardown(elem, namespaces, store.handler!) === false - ) { - Util.removeEventListener( - elem as Element, - type, - store.handler as any as EventListener, - ) - } - - delete events[type] - } - }) - - // Remove data and the expando if it's no longer used - if (Object.keys(events).length === 0) { - Store.remove(elem) - } - } - - export function dispatch( - elem: Store.EventTarget, - evt: Event | EventObject | string, - ...args: any[] - ) { - const event = EventObject.create(evt) - event.delegateTarget = elem as Element - - const hook = Hook.get(event.type) - if (hook.preDispatch && hook.preDispatch(elem, event) === false) { - return - } - - const handlerQueue = Util.getHandlerQueue(elem, event) - - // Run delegates first; they may want to stop propagation beneath us - for ( - let i = 0, l = handlerQueue.length; - i < l && !event.isPropagationStopped(); - i += 1 - ) { - const matched = handlerQueue[i] - event.currentTarget = matched.elem - - for ( - let j = 0, k = matched.handlers.length; - j < k && !event.isImmediatePropagationStopped(); - j += 1 - ) { - const handleObj = matched.handlers[j] - // If event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( - event.rnamespace == null || - (handleObj.namespace && event.rnamespace.test(handleObj.namespace)) - ) { - event.handleObj = handleObj - event.data = handleObj.data - - const hookHandle = Hook.get(handleObj.originType).handle - - const result = hookHandle - ? hookHandle(matched.elem as Store.EventTarget, event, ...args) - : handleObj.handler.call(matched.elem, event, ...args) - if (result !== undefined) { - event.result = result - if (result === false) { - event.preventDefault() - event.stopPropagation() - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if (hook.postDispatch) { - hook.postDispatch(elem, event) - } - - return event.result - } - - export function trigger( - event: - | (Partial & { type: string }) - | EventObject - | string, - eventArgs: any, - elem: Store.EventTarget, - onlyHandlers?: boolean, - ) { - let eventObj = event as EventObject - let type = typeof event === 'string' ? event : event.type - let namespaces = - typeof event === 'string' || eventObj.namespace == null - ? [] - : eventObj.namespace.split('.') - - const node = elem as HTMLElement - - // Don't do events on text and comment nodes - if (node.nodeType === 3 || node.nodeType === 8) { - return - } - - if (type.indexOf('.') > -1) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split('.') - type = namespaces.shift()! - namespaces.sort() - } - const ontype = type.indexOf(':') < 0 && (`on${type}` as 'onclick') - - // Caller can pass in a EventObject, Object, or just an event type string - eventObj = - event instanceof EventObject - ? event - : new EventObject(type, typeof event === 'object' ? event : null) - - eventObj.namespace = namespaces.join('.') - eventObj.rnamespace = eventObj.namespace - ? new RegExp(`(^|\\.)${namespaces.join('\\.(?:.*\\.|)')}(\\.|$)`) - : null - - // Clean up the event in case it is being reused - eventObj.result = undefined - if (!eventObj.target) { - eventObj.target = node - } - - const args: [EventObject, ...any[]] = [eventObj] - if (Array.isArray(eventArgs)) { - args.push(...eventArgs) - } else { - args.push(eventArgs) - } - - const hook = Hook.get(type) - if ( - !onlyHandlers && - hook.trigger && - hook.trigger(node, eventObj, eventArgs) === false - ) { - return - } - - let bubbleType - - // Determine event propagation path in advance, per W3C events spec. - // Bubble up to document, then to window; watch for a global ownerDocument - const eventPath = [node] - if (!onlyHandlers && !hook.noBubble && !isWindow(node)) { - bubbleType = hook.delegateType || type - - let last: Document | HTMLElement = node - let curr = node.parentNode as HTMLElement - - while (curr != null) { - eventPath.push(curr) - last = curr - curr = curr.parentNode as HTMLElement - } - - // Only add window if we got to document - const doc = node.ownerDocument || Global.document - if ((last as any) === doc) { - const win = - (last as any).defaultView || - (last as any).parentWindow || - Global.window - eventPath.push(win) - } - } - - let lastElement = node - // Fire handlers on the event path - for ( - let i = 0, l = eventPath.length; - i < l && !eventObj.isPropagationStopped(); - i += 1 - ) { - const currElement = eventPath[i] - lastElement = currElement - - eventObj.type = i > 1 ? (bubbleType as string) : hook.bindType || type - - // Custom handler - const store = Store.get(currElement as Element) - if (store) { - if (store.events[eventObj.type] && store.handler) { - store.handler.call(currElement, ...args) - } - } - - // Native handler - const handle = (ontype && currElement[ontype]) || null - if (handle && handle.apply && Util.isValidTarget(currElement)) { - eventObj.result = handle.call(currElement, ...args) - if (eventObj.result === false) { - eventObj.preventDefault() - } - } - } - - eventObj.type = type - - // If nobody prevented the default action, do it now - if (!onlyHandlers && !eventObj.isDefaultPrevented()) { - const preventDefault = hook.preventDefault - if ( - (preventDefault == null || - preventDefault(eventPath.pop()!, eventObj, eventArgs) === false) && - Util.isValidTarget(node) - ) { - // Call a native DOM method on the target with the same name as the - // event. Don't do default actions on window. - if ( - ontype && - typeof node[type as 'click'] === 'function' && - !isWindow(node) - ) { - // Don't re-trigger an onFOO event when we call its FOO() method - const tmp = node[ontype] - if (tmp) { - node[ontype] = null - } - - // Prevent re-triggering of the same event, since we already bubbled it above - triggered = type - - if (eventObj.isPropagationStopped()) { - lastElement.addEventListener(type, Util.stopPropagationCallback) - } - - node[type as 'click']() - - if (eventObj.isPropagationStopped()) { - lastElement.removeEventListener(type, Util.stopPropagationCallback) - } - - triggered = undefined - - if (tmp) { - node[ontype] = tmp - } - } - } - } - - return eventObj.result - } -} diff --git a/packages/x6-vector/src/dom/events/emitter.test.ts b/packages/x6-vector/src/dom/events/emitter.test.ts deleted file mode 100644 index 354d5197cfa..00000000000 --- a/packages/x6-vector/src/dom/events/emitter.test.ts +++ /dev/null @@ -1,644 +0,0 @@ -import sinon from 'sinon' -import { Adopter } from '../common/adopter' -import { Dom } from '../dom' -import { Core } from './core' -import { Hook } from './hook' -import { EventObject } from './object' - -describe('Dom', () => { - describe('events', () => { - const tree = ` -
-
-
-
-
-
-
- ` - - describe('on()', () => { - it('should bind single event', () => { - const div = new Dom() - const spy = sinon.spy() - div.on('click', spy) - div.trigger('click') - expect(spy.callCount).toEqual(1) - div.trigger('click') - expect(spy.callCount).toEqual(2) - }) - - it('should bind events with event-handler object', () => { - const div = new Dom() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - div.on({ - click: spy1, - dblclick: spy2, - }) - div.trigger('click') - div.trigger('dblclick') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - div.trigger('click') - div.trigger('dblclick') - expect(spy1.callCount).toEqual(2) - expect(spy2.callCount).toEqual(2) - }) - - it('should bind event with handler object', () => { - const div = new Dom() - const spy = sinon.spy() - div.on('click', { handler: spy }) - div.trigger('click') - expect(spy.callCount).toEqual(1) - div.trigger('click') - expect(spy.callCount).toEqual(2) - }) - - it('should not bind event on invalid target', () => { - const text = new Dom(document.createTextNode('foo') as any) - const spy = sinon.spy() - text.on('click', spy) - text.trigger('click') - expect(spy.callCount).toEqual(0) - }) - - it('should delegate event', () => { - const container = Adopter.makeInstance(tree, true) - const spy = sinon.spy() - container.on('click', '.one', spy) - const child = container.findOne('.one')! - - child.trigger('click') - expect(spy.callCount).toEqual(1) - - child.trigger('click') - expect(spy.callCount).toEqual(2) - }) - - it('should throw an error when delegating with invalid selector', () => { - const container = Adopter.makeInstance(tree, true) - const spy = sinon.spy() - expect(() => container.on('click', '.unknown', spy)).toThrowError() - }) - - it('should support data', () => { - const div = new Dom() - const spy = sinon.spy() - div.on('click', { foo: 'bar' }, spy) - div.trigger('click') - expect(spy.callCount).toEqual(1) - div.trigger('click') - expect(spy.callCount).toEqual(2) - - const e1 = spy.args[0][0] - const e2 = spy.args[1][0] - expect(e1.data).toEqual({ foo: 'bar' }) - expect(e2.data).toEqual({ foo: 'bar' }) - }) - - it('should bind false as event handler', () => { - const div = new Dom() - expect(() => div.on('click', false)).not.toThrow() - }) - - it('should ignore invalid handler', () => { - const div = new Dom() - expect(() => div.on('click', null as any)).not.toThrow() - }) - - it('should not no attaching namespace-only handlers', () => { - const div = new Dom() - const spy = sinon.spy() - expect(() => div.on('.ns', spy)).not.toThrow() - }) - }) - - describe('once()', () => { - it('should bind single event', () => { - const div = new Dom() - const spy = sinon.spy() - div.once('click', spy) - div.trigger('click') - expect(spy.callCount).toEqual(1) - div.trigger('click') - expect(spy.callCount).toEqual(1) - }) - - it('should bind event with namespace', () => { - const div = new Dom() - const spy = sinon.spy() - div.once('click.ns', spy) - div.trigger('click') - expect(spy.callCount).toEqual(1) - div.trigger('click') - expect(spy.callCount).toEqual(1) - }) - }) - - describe('off()', () => { - it('should unbind single event', () => { - const div = new Dom() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - div.on('click', spy1) - div.on('click', spy2) - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - div.off('click', spy1) - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(2) - }) - - it('should unbind events by the given event type', () => { - const div = new Dom() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - div.on('click', spy1) - div.on('click', spy2) - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - div.off('click') - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - }) - - it('should unbind events by the given namespace', () => { - const div = new Dom() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const spy3 = sinon.spy() - div.on('click.ns', spy1) - div.on('click.ns', spy2) - div.on('click', spy3) - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(1) - div.off('.ns') - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(2) - }) - - it('should unbind events with event-handler object', () => { - const div = new Dom() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const spy3 = sinon.spy() - div.on('click.ns', spy1) - div.on('click.ns', spy2) - div.on('click', spy3) - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(1) - div.off({ 'click.ns': spy1, click: spy3 }) - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(2) - expect(spy3.callCount).toEqual(1) - }) - - it('should unbind delegated events', () => { - const container = Adopter.makeInstance(tree, true) - const spy = sinon.spy() - container.on('click', '.one', spy) - const child = container.findOne('.one')! - - child.trigger('click') - expect(spy.callCount).toEqual(1) - - container.off('click', '.one') - child.trigger('click') - expect(spy.callCount).toEqual(1) - }) - - it('should unbind delegated events with "**" selector', () => { - const container = Adopter.makeInstance(tree, true) - const spy = sinon.spy() - container.on('click', '.one', spy) - const child = container.findOne('.one')! - - child.trigger('click') - expect(spy.callCount).toEqual(1) - - container.off('click', '**') - child.trigger('click') - expect(spy.callCount).toEqual(1) - }) - - it('should unbind "false" event handler', () => { - const div = new Dom() - expect(() => div.off('click', false)).not.toThrowError() - }) - - it('should do noting for elem which do not bind any events', () => { - const div = new Dom() - expect(() => div.off()).not.toThrowError() - }) - }) - - describe('trigger()', () => { - it('should trigger event with namespace', () => { - const div = new Dom().appendTo(document.body) - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const spy3 = sinon.spy() - div.on('click.ns', spy1) - div.on('click.ns', spy2) - div.on('click', spy3) - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(1) - div.trigger('click.ns') - expect(spy1.callCount).toEqual(2) - expect(spy2.callCount).toEqual(2) - expect(spy3.callCount).toEqual(1) - div.remove() - }) - - it('should also trigger inline binded event', () => { - const div = new Dom() - const spy1 = sinon.spy() - const spy2 = sinon.spy(() => false) - div.on('click', spy1) - const node = div.node as HTMLDivElement - node.onclick = spy2 - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - }) - - it('should trigger event with EventObject', () => { - const div = new Dom() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const spy3 = sinon.spy() - div.on('click.ns', spy1) - div.on('click.ns', spy2) - div.on('click', spy3) - - div.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(1) - - div.trigger(new EventObject('click', { namespace: 'ns' })) - expect(spy1.callCount).toEqual(2) - expect(spy2.callCount).toEqual(2) - expect(spy3.callCount).toEqual(1) - }) - - it('should trigger event with EventObject created with native event', () => { - const div = new Dom() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const spy3 = sinon.spy() - div.on('click.ns', spy1) - div.on('click.ns', spy2) - div.on('click', spy3) - - const evt = document.createEvent('MouseEvents') - evt.initEvent('click', true, true) - evt.preventDefault() - div.trigger(new EventObject(evt)) - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(1) - }) - - it('should trigger event with EventLikeObject', () => { - const div = new Dom() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const spy3 = sinon.spy() - div.on('click.ns', spy1) - div.on('click.ns', spy2) - div.on('click', spy3) - - div.trigger({ type: 'click' }) - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(1) - }) - - it('should trigger custom event', () => { - const div = new Dom() - const spy = sinon.spy() - div.on('foo', spy) - div.trigger('foo') - expect(spy.callCount).toEqual(1) - }) - - it('should bind and trigger event on any object', () => { - const obj = {} - const spy = sinon.spy() - Core.on(obj, 'foo', spy) - Core.trigger('foo', [], obj) - expect(spy.callCount).toEqual(1) - }) - - it('should trggier event with the given args', () => { - const div = new Dom() - const spy = sinon.spy() - div.on('click', spy) - - div.trigger('click') - expect(spy.callCount).toEqual(1) - - div.trigger('click', 1) - expect(spy.callCount).toEqual(2) - expect(spy.args[1][1]).toEqual(1) - - div.trigger('click', [1, { foo: 'bar' }]) - expect(spy.callCount).toEqual(3) - expect(spy.args[2][1]).toEqual(1) - expect(spy.args[2][2]).toEqual({ foo: 'bar' }) - }) - - it('should stop propagation when handler return `false`', () => { - const container = Adopter.makeInstance(tree, true) - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const spy3 = sinon.spy(() => false) - container.on('click', spy1) - container.on('click', '.one', spy2) - const child = container.findOne('.one')! - - child.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(0) - - container.off('click', '.one', spy2) - container.on('click', '.one', spy3) - child.trigger('click') - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(1) - }) - - it('should stopImmediatePropagation 1', () => { - const container = Adopter.makeInstance(tree, true) - const spy1 = sinon.spy() - const spy2 = sinon.spy((e: EventObject) => { - e.stopImmediatePropagation() - }) - const spy3 = sinon.spy() - const spy4 = sinon.spy() - container.on('click', spy1) - container.on('click', '.one', spy2) - container.on('click', '.two', spy3) - container.on('click', '.three', spy4) - - container.findOne('.one')!.trigger('click') - - expect(spy1.callCount).toEqual(0) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(0) - expect(spy4.callCount).toEqual(0) - }) - - it('should stopImmediatePropagation 2', () => { - const container = Adopter.makeInstance(tree, true) - const spy1 = sinon.spy() - const spy2 = sinon.spy((e: EventObject) => { - e.stopImmediatePropagation() - }) - const spy3 = sinon.spy() - const spy4 = sinon.spy() - container.on('click', spy1) - container.on('click', '.one', spy2) - container.on('click', '.two', spy3) - container.on('click', '.three', spy4) - - const evt = document.createEvent('MouseEvents') - evt.initEvent('click', true, true) - const node = container.findOne('.one')!.node as HTMLDivElement - node.dispatchEvent(evt) - - expect(spy1.callCount).toEqual(0) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(0) - expect(spy4.callCount).toEqual(0) - }) - - it('should prevent default action', (done) => { - const container = Adopter.makeInstance(tree, true) - container - .on('click', (e) => { - expect(e.isDefaultPrevented()).toBeTrue() - done() - }) - .findOne('.three')! - .on('click', (e) => { - e.preventDefault() - }) - - container.findOne('.four')?.trigger('click') - }) - - it('should do the default action', () => { - const container = Adopter.makeInstance(tree, true) - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const spy3 = sinon.spy() - const spy4 = sinon.spy((e: EventObject) => { - e.stopPropagation() - }) - - container.on('click', spy1) - container.on('click', '.one', spy2) - const child = container.findOne('.one')! - const node = child.node as HTMLDivElement - node.onclick = spy3 - child.on('click', spy4) - - node.dispatchEvent(new Event('click')) - expect(spy1.callCount).toEqual(0) - expect(spy2.callCount).toEqual(0) - expect(spy3.callCount).toEqual(1) - expect(spy4.callCount).toEqual(1) - }) - - it('should not propagation when `onlyHandlers` is `true`', () => { - const container = Adopter.makeInstance(tree, true) - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const spy3 = sinon.spy() - container.on('click', spy1) - container.on('click', '.one', spy2) - const child = container.findOne('.one')! - child.on('click', spy3) - - child.trigger('click', [], true) - expect(spy1.callCount).toEqual(0) - expect(spy2.callCount).toEqual(0) - expect(spy3.callCount).toEqual(1) - }) - }) - - describe('hooks', () => { - it('should get event properties on natively-triggered event', (done) => { - const a = document.createElement('a') - const lk = new Dom(a).appendTo(document.body).on('click', function (e) { - expect('detail' in e).toBeTrue() - expect('cancelable' in e).toBeTrue() - expect('bubbles' in e).toBeTrue() - expect(e.clientX).toEqual(10) - done() - }) - const evt = document.createEvent('MouseEvents') - evt.initEvent('click', true, true) - lk.trigger(new EventObject(evt, { clientX: 10 })) - lk.remove() - }) - - it('should get event properties added by `addProperty`', (done) => { - const div = new Dom().on('click', (e) => { - expect(typeof e.clientX === 'number').toBeTrue() - done() - }) - const node = div.node as HTMLDivElement - const evt = document.createEvent('MouseEvents') - evt.initEvent('click') - node.dispatchEvent(evt) - }) - - it('shoud add custom event property with `addProperty`', (done) => { - EventObject.addProperty('testProperty', () => 42) - - const div = new Dom().on('click', (e: any) => { - expect(e.testProperty).toEqual(42) - done() - }) - const node = div.node as HTMLDivElement - const evt = document.createEvent('MouseEvents') - evt.initEvent('click') - node.dispatchEvent(evt) - }) - - it('should apply hook to prevent triggered `image.load` events from bubbling to `window.load`', () => { - const div = new Dom() - const win = new Dom(window as any) - const spy1 = sinon.spy() - const spy2 = sinon.spy() - div.on('load', spy1) - win.on('load', spy2) - - const node = div.node as HTMLElement - node.dispatchEvent(new Event('load')) - - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(0) - }) - - it('should apply hook to prevent window to unload', () => { - const win = new Dom(window as any) - const spy1 = sinon.spy(() => { - return false - }) - const spy2 = sinon.spy() - win.on('beforeunload', spy1) - win.on('unload', spy2) - const node = win.node as HTMLElement - node.dispatchEvent(new Event('beforeunload')) - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(0) - }) - - it('should call hooks', () => { - const addHook = sinon.spy() - const removeHook = sinon.spy() - const setupHook = sinon.spy() - const teardownHook = sinon.spy() - const handleHook = sinon.spy() - const triggerHook = sinon.spy() - const preDispatchHook = sinon.spy() - const postDispatchHook = sinon.spy() - - Hook.register('dblclick', { - add: addHook, - remove: removeHook, - setup: setupHook, - teardown: teardownHook, - handle: handleHook, - trigger: triggerHook, - preDispatch: preDispatchHook, - postDispatch: postDispatchHook, - }) - const div = new Dom() - const spyHandler = sinon.spy() - div.on('dblclick', spyHandler) - div.trigger('dblclick') - div.off('dblclick') - - expect(addHook.callCount).toEqual(1) - expect(removeHook.callCount).toEqual(1) - expect(setupHook.callCount).toEqual(1) - expect(teardownHook.callCount).toEqual(1) - expect(handleHook.callCount).toEqual(1) - expect(triggerHook.callCount).toEqual(1) - expect(preDispatchHook.callCount).toEqual(1) - expect(postDispatchHook.callCount).toEqual(1) - Hook.unregister('dblclick') - }) - - it('should not trigger event when `preDispatch` hook return `false`', () => { - const preDispatchHook = sinon.spy(() => false) - Hook.register('dblclick', { - preDispatch: preDispatchHook as any, - }) - const div = new Dom() - const spyHandler = sinon.spy() - div.on('dblclick', spyHandler) - div.trigger('dblclick') - Hook.unregister('dblclick') - expect(spyHandler.callCount).toEqual(0) - }) - - it('should not trigger event when `trigger` hook return `false`', () => { - const hook = sinon.spy(() => false) - Hook.register('dblclick', { - trigger: hook as any, - }) - const div = new Dom() - const spyHandler = sinon.spy() - div.on('dblclick', spyHandler) - div.trigger('dblclick') - Hook.unregister('dblclick') - expect(spyHandler.callCount).toEqual(0) - }) - - it('should not prevent default when `preventDefault` hook return `false`', () => { - const hook = sinon.spy(() => false) - Hook.register('click', { - preventDefault: hook as any, - }) - const div = new Dom() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const spy3 = sinon.spy() - const node = div.node as HTMLDivElement - - node.click = spy1 - node.onclick = spy2 - div.on('click', spy3) - div.trigger('click') - Hook.unregister('click') - - expect(spy1.callCount).toEqual(1) - expect(spy2.callCount).toEqual(1) - expect(spy3.callCount).toEqual(1) - }) - }) - }) -}) diff --git a/packages/x6-vector/src/dom/events/emitter.ts b/packages/x6-vector/src/dom/events/emitter.ts deleted file mode 100644 index b4b1e0e07ff..00000000000 --- a/packages/x6-vector/src/dom/events/emitter.ts +++ /dev/null @@ -1,301 +0,0 @@ -/* eslint-disable no-param-reassign */ - -import { Core } from './core' -import { Util } from './util' -import { EventObject } from './object' -import { TypeEventHandler, TypeEventHandlers } from './types' -import { Base } from '../common/base' - -export class EventEmitter extends Base { - on( - events: TType, - selector: string, - handler: TypeEventHandler | false, - ): this - on( - events: TType, - selector: string | null | undefined, - data: TData, - handler: - | TypeEventHandler - | false, - ): this - on( - events: TType, - data: TData, - handler: - | TypeEventHandler - | false, - ): this - on( - events: TType, - data: TData, - handlerObject: { - handler: TypeEventHandler - selector?: string - [key: string]: any - }, - ): this - on( - events: TType, - handler: - | TypeEventHandler - | false, - ): this - on( - events: TType, - handlerObject: { - handler: TypeEventHandler - selector?: string - [key: string]: any - }, - ): this - on( - events: TypeEventHandlers, - selector: string | null | undefined, - data: TData, - ): this - on( - events: TypeEventHandlers, - selector: string, - ): this - on( - events: TypeEventHandlers, - data: TData, - ): this - on(events: TypeEventHandlers): this - on(events: any, selector?: any, data?: any, handler?: any) { - EventEmitter.on(this.node as any, events, selector, data, handler) - return this - } - - once( - events: TType, - selector: string, - handler: TypeEventHandler | false, - ): this - once( - events: TType, - selector: string | null | undefined, - data: TData, - handler: - | TypeEventHandler - | false, - ): this - once( - events: TType, - data: TData, - handler: - | TypeEventHandler - | false, - ): this - once( - events: TType, - data: TData, - handlerObject: { - handler: TypeEventHandler - selector?: string - [key: string]: any - }, - ): this - once( - events: TType, - handler: - | TypeEventHandler - | false, - ): this - once( - events: TType, - handlerObject: { - handler: TypeEventHandler - selector?: string - [key: string]: any - }, - ): this - once( - events: TypeEventHandlers, - selector: string | null | undefined, - data: TData, - ): this - once( - events: TypeEventHandlers, - selector: string, - ): this - once( - events: TypeEventHandlers, - data: TData, - ): this - once(events: TypeEventHandlers): this - once(events: any, selector?: any, data?: any, handler?: any) { - EventEmitter.on(this.node as any, events, selector, data, handler, true) - return this - } - - off( - events: TType, - selector: string, - handler: TypeEventHandler | false, - ): this - off( - events: TType, - handler: TypeEventHandler | false, - ): this - off( - events: TType, - selector_handler?: - | string - | TypeEventHandler - | false, - ): this - off( - events: TypeEventHandlers, - selector?: string, - ): this - off(event?: EventObject): this - off( - events?: - | TType - | TypeEventHandlers - | EventObject, - selector?: - | string - | TypeEventHandler - | false, - handler?: TypeEventHandler | false, - ) { - EventEmitter.off(this.node, events, selector, handler) - return this - } - - trigger( - event: - | string - | EventObject - | (Partial & { type: string }), - args?: any[] | Record | string | number | boolean, - /** - * When onlyHandlers is `true` - * - Will not call `.event()` on the element it is triggered on. This means - * `.trigger('submit', [], true)` on a form will not call `.submit()` on - * the form. - * - Events will not bubble up the DOM hierarchy; if they are not handled - * by the target element directly, they do nothing. - */ - onlyHandlers?: boolean, - ) { - Core.trigger(event, args, this.node as any, onlyHandlers) - return this - } -} - -export namespace EventEmitter { - type EventHandler = false | ((...args: any[]) => any) - - export function on( - elem: Element, - types: any, - selector?: string | EventHandler | null, - data?: any | EventHandler | null, - fn?: EventHandler | null, - once?: boolean, - ) { - // Types can be a map of types/handlers - if (typeof types === 'object') { - // ( types-Object, selector, data ) - if (typeof selector !== 'string') { - // ( types-Object, data ) - data = data || selector - selector = undefined - } - - Object.keys(types).forEach((type) => - on(elem, type, selector, data, types[type], once), - ) - return - } - - if (data == null && fn == null) { - // ( types, fn ) - fn = selector as EventHandler - data = selector = undefined - } else if (fn == null) { - if (typeof selector === 'string') { - // ( types, selector, fn ) - fn = data - data = undefined - } else { - // ( types, data, fn ) - fn = data - data = selector - selector = undefined - } - } - - if (fn === false) { - fn = Util.returnFalse - } else if (!fn) { - return - } - - if (once) { - const originHandler = fn - fn = function (event, ...args: any[]) { - // Can use an empty set, since event contains the info - EventEmitter.off(elem, event) - return originHandler.call(this, event, ...args) - } - - // Use same guid so caller can remove using origFn - Util.setHandlerId(fn, Util.ensureHandlerId(originHandler)) - } - - Core.on(elem, types as string, fn, data, selector as string) - } - - export function off( - elem: TElement, - events?: - | TType - | TypeEventHandlers - | EventObject, - selector?: - | string - | TypeEventHandler - | false, - fn?: TypeEventHandler | false, - ) { - const evt = events as EventObject - if (evt && evt.preventDefault != null && evt.handleObj != null) { - const obj = evt.handleObj - off( - evt.delegateTarget, - obj.namespace ? `${obj.originType}.${obj.namespace}` : obj.originType, - obj.selector, - obj.handler, - ) - - return - } - - if (typeof events === 'object') { - // ( types-object [, selector] ) - const types = events as TypeEventHandlers - Object.keys(types).forEach((type) => - off(elem, type, selector, types[type] as any), - ) - return - } - - if (selector === false || typeof selector === 'function') { - // ( types [, fn] ) - fn = selector - selector = undefined - } - - if (fn === false) { - fn = Util.returnFalse - } - - Core.off(elem as any, events as string, fn, selector) - } -} diff --git a/packages/x6-vector/src/dom/events/hook.ts b/packages/x6-vector/src/dom/events/hook.ts deleted file mode 100644 index b525478c254..00000000000 --- a/packages/x6-vector/src/dom/events/hook.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { Store } from './store' -import { EventObject } from './object' -import { EventHandler } from './types' - -export namespace Hook { - const cache: { [type: string]: Hook } = {} - - export function get(type: string) { - return cache[type] || {} - } - - export function register(type: string, hook: Hook) { - cache[type] = hook - } - - export function unregister(type: string) { - delete cache[type] - } -} - -export interface Hook { - /** - * Indicates whether this event type should be bubbled when the `.trigger()` - * method is called; by default it is `false`, meaning that a triggered event - * will bubble to the element's parents up to the document (if attached to a - * document) and then to the window. Note that defining `noBubble` on an event - * will effectively prevent that event from being used for delegated events - * with `.trigger()`. - */ - noBubble?: boolean - - /** - * When defined, these string properties specify that a special event should - * be handled like another event type until the event is delivered. - * - * The `bindType` is used if the event is attached directly, and the - * `delegateType` is used for delegated events. These types are generally DOM - * event types, and should not be a special event themselves. - */ - bindType?: string - - /** - * When defined, these string properties specify that a special event should - * be handled like another event type until the event is delivered. - * - * The `bindType` is used if the event is attached directly, and the - * `delegateType` is used for delegated events. These types are generally DOM - * event types, and should not be a special event themselves. - */ - delegateType?: string - - /** - * The setup hook is called the first time an event of a particular type is - * attached to an element; this provides the hook an opportunity to do - * processing that will apply to all events of this type on the element. - * - * The `elem` is the reference to the element where the event is being - * attached and `eventHandle` is the event handler function. In most cases - * the `namespaces` argument should not be used, since it only represents the - * namespaces of the first event being attached; subsequent events may not - * have this same namespaces. - * - * This hook can perform whatever processing it desires, including attaching - * its own event handlers to the element or to other elements and recording - * setup information on the element using the `.data()` method. If the - * setup hook wants me to add a browser event (via `addEventListener` or - * `attachEvent`, depending on browser) it should return `false`. In all - * other cases, me will not add the browser event, but will continue all its - * other bookkeeping for the event. This would be appropriate, for example, - * if the event was never fired by the browser but invoked by `.trigger()`. - * To attach the me event handler in the setup hook, use the `eventHandle` - * argument. - * - */ - setup?: ( - elem: Store.EventTarget, - data: any, - namespaces: string[], - eventHandle: EventHandler, - ) => any | false - - /** - * The teardown hook is called when the final event of a particular type is - * removed from an element. The `elem` is the reference to the element where - * the event is being cleaned up. This hook should return `false` if it wants - * me to remove the event from the browser's event system (via - * `removeEventListener` or `detachEvent`). In most cases, the setup and - * teardown hooks should return the same value. - * - * If the setup hook attached event handlers or added data to an element - * through a mechanism such as `.data()`, the teardown hook should reverse - * the process and remove them. me will generally remove the data and events - * when an element is totally removed from the document, but failing to remove - * data or events on teardown will cause a memory leak if the element stays in - * the document. - * - */ - teardown?: ( - elem: Store.EventTarget, - namespaces: string[], - eventHandle: EventHandler, - ) => any | false - - /** - * Each time an event handler is added to an element through an API such as - * `.on()`, me calls this hook. The `elem` is the element to which the event - * handler is being added, and the `handleObj` argument is as described in the - * section above. The return value of this hook is ignored. - */ - add?: (elem: Store.EventTarget, handleObj: Store.HandlerObject) => void - - /** - * When an event handler is removed from an element using an API such as - * `.off()`, this hook is called. The `elem` is the element where the handler - * is being removed, and the `handleObj` argument is as described in the - * section above. The return value of this hook is ignored. - * - */ - remove?: (elem: Store.EventTarget, handleObj: Store.HandlerObject) => void - - /** - * The handle hook is called when the event has occurred and me would - * normally call the user's event handler specified by `.on()` or another - * event binding method. If the hook exists, me calls it instead of that - * event handler, passing it the event and any data passed from `.trigger()` - * if it was not a native event. The `elem` argument is the DOM element being - * handled, and `event.handleObj` property has the detailed event information. - * - */ - handle?: (elem: Store.EventTarget, event: EventObject, ...args: any[]) => void - - /** - * Called when the `.trigger()` method is used to trigger an event for the - * special type from code, as opposed to events that originate from within - * the browser. The `elem` argument will be the element being triggered, and - * the `event` argument will be a `EventObject` object constructed from the - * caller's input. At minimum, the event type, data, namespace, and target - * properties are set on the event. The data argument represents additional - * data passed by `.trigger()` if present. - * - */ - trigger?: ( - elem: Store.EventTarget, - event: EventObject, - data: any, - ) => any | false - - /** - * When the `.trigger()` method finishes running all the event handlers for - * an event, it also looks for and runs any method on the target object by - * the same name unless of the handlers called `event.preventDefault()`. So, - * `.trigger("submit")` will execute the `submit()` method on the element if - * one exists. When a `preventDefault` hook is specified, the hook is called - * just prior to checking for and executing the element's default method. If - * this hook returns the value `false` the element's default method will be - * called; otherwise it is not. - */ - preventDefault?: ( - elem: Store.EventTarget, - event: EventObject, - data: any, - ) => any | false - - preDispatch?: (elem: Store.EventTarget, event: EventObject) => void | false - - postDispatch?: (elem: Store.EventTarget, event: EventObject) => void -} diff --git a/packages/x6-vector/src/dom/events/index.ts b/packages/x6-vector/src/dom/events/index.ts deleted file mode 100644 index 8b88d62bce4..00000000000 --- a/packages/x6-vector/src/dom/events/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './hook' -export * from './emitter' -export * from './listener' diff --git a/packages/x6-vector/src/dom/events/listener.ts b/packages/x6-vector/src/dom/events/listener.ts deleted file mode 100644 index db82765b5ea..00000000000 --- a/packages/x6-vector/src/dom/events/listener.ts +++ /dev/null @@ -1,433 +0,0 @@ -import { EventEmitter } from './emitter' -import { TypeEventHandler } from './types' - -export class EventListener< - TElement extends Element, -> extends EventEmitter {} - -export interface EventListener - extends EventListener.Methods {} - -export namespace EventListener { - export interface Methods { - blur(): this - blur( - handler: - | TypeEventHandler - | false, - ): this - blur( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - focus(): this - focus( - handler: - | TypeEventHandler - | false, - ): this - focus( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - focusin(): this - focusin( - handler: - | TypeEventHandler - | false, - ): this - focusin( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - focusout(): this - focusout( - handler: - | TypeEventHandler - | false, - ): this - focusout( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - resize(): this - resize( - handler: - | TypeEventHandler - | false, - ): this - resize( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - scroll(): this - scroll( - handler: - | TypeEventHandler - | false, - ): this - scroll( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - click(): this - click( - handler: - | TypeEventHandler - | false, - ): this - click( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - dblclick(): this - dblclick( - handler: - | TypeEventHandler - | false, - ): this - dblclick( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - mousedown(): this - mousedown( - handler: - | TypeEventHandler - | false, - ): this - mousedown( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - mouseup(): this - mouseup( - handler: - | TypeEventHandler - | false, - ): this - mouseup( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - mousemove(): this - mousemove( - handler: - | TypeEventHandler - | false, - ): this - mousemove( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - mouseover(): this - mouseover( - handler: - | TypeEventHandler - | false, - ): this - mouseover( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - mouseout(): this - mouseout( - handler: - | TypeEventHandler - | false, - ): this - mouseout( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - mouseenter(): this - mouseenter( - handler: - | TypeEventHandler - | false, - ): this - mouseenter( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - mouseleave(): this - mouseleave( - handler: - | TypeEventHandler - | false, - ): this - mouseleave( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - change(): this - change( - handler: - | TypeEventHandler - | false, - ): this - change( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - select(): this - select( - handler: - | TypeEventHandler - | false, - ): this - select( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - submit(): this - submit( - handler: - | TypeEventHandler - | false, - ): this - submit( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - keydown(): this - keydown( - handler: - | TypeEventHandler - | false, - ): this - keydown( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - keypress(): this - keypress( - handler: - | TypeEventHandler - | false, - ): this - keypress( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - keyup(): this - keyup( - handler: - | TypeEventHandler - | false, - ): this - keyup( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - contextmenu(): this - contextmenu( - handler: - | TypeEventHandler - | false, - ): this - contextmenu( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - touchstart(): this - touchstart( - handler: - | TypeEventHandler - | false, - ): this - touchstart( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - touchmove(): this - touchmove( - handler: - | TypeEventHandler - | false, - ): this - touchmove( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - touchleave(): this - touchleave( - handler: - | TypeEventHandler - | false, - ): this - touchleave( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - touchend(): this - touchend( - handler: - | TypeEventHandler - | false, - ): this - touchend( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - - touchcancel(): this - touchcancel( - handler: - | TypeEventHandler - | false, - ): this - touchcancel( - eventData: TData, - handler: - | TypeEventHandler - | false, - ): this - } -} - -export namespace EventListener { - // Generate interface - // eslint-disable-next-line no-constant-condition - // if (false) { - // const events = [ - // 'blur', - // 'focus', - // 'focusin', - // 'focusout', - // 'resize', - // 'scroll', - // 'click', - // 'dblclick', - // 'mousedown', - // 'mouseup', - // 'mousemove', - // 'mouseover', - // 'mouseout', - // 'mouseenter', - // 'mouseleave', - // 'change', - // 'select', - // 'submit', - // 'keydown', - // 'keypress', - // 'keyup', - // 'contextmenu', - // 'touchstart', - // 'touchmove', - // 'touchleave', - // 'touchend', - // 'touchcancel', - // ] as const - // events.forEach((event) => { - // EventListener.prototype[event] = function ( - // this: EventListener, - // eventData?: TData | TypeEventHandler | false, - // handler?: TypeEventHandler | false, - // ) { - // if (eventData == null) { - // this.trigger(event) - // } else { - // this.on(event, null, eventData, handler!) - // } - // return this - // } - // }) - // const methods = events.map( - // (event) => - // ` - // ${event}(): this - // ${event}( - // handler: - // | TypeEventHandler - // | false, - // ): this - // ${event}( - // eventData: TData, - // handler: - // | TypeEventHandler - // | false, - // ): this - // `, - // ) - // // eslint-disable-next-line no-console - // console.log(methods.join('\n')) - // } -} diff --git a/packages/x6-vector/src/dom/events/object.ts b/packages/x6-vector/src/dom/events/object.ts deleted file mode 100644 index f4b69563bcb..00000000000 --- a/packages/x6-vector/src/dom/events/object.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { Util } from './util' -import { Store } from './store' -import { EventRaw } from './alias' - -export class EventObject< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, - TEvent extends Event = Event, -> implements EventObject.Event -{ - isDefaultPrevented: () => boolean = Util.returnFalse - isPropagationStopped: () => boolean = Util.returnFalse - isImmediatePropagationStopped: () => boolean = Util.returnFalse - - type: string - originalEvent: TEvent - - target: TTarget | null - currentTarget: TCurrentTarget | null - delegateTarget: TDelegateTarget | null - relatedTarget?: EventTarget | null - - data: TData - result: any - - timeStamp: number - handleObj: Store.HandlerObject - namespace?: string - rnamespace?: RegExp | null - isSimulated = false - - constructor(e: TEvent | string, props?: Record | null) { - if (typeof e === 'string') { - this.type = e - } else if (e.type) { - this.originalEvent = e - this.type = e.type - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = e.defaultPrevented - ? Util.returnTrue - : Util.returnFalse - - // Create target properties - this.target = e.target as any as TTarget - this.currentTarget = e.currentTarget as any as TCurrentTarget - this.relatedTarget = (e as any as MouseEvent).relatedTarget - this.timeStamp = e.timeStamp - } - - // Put explicitly provided properties onto the event object - if (props) { - Object.assign(this, props) - } - - // Create a timestamp if incoming event doesn't have one - if (!this.timeStamp) { - this.timeStamp = Date.now() - } - } - - preventDefault() { - const e = this.originalEvent - - this.isDefaultPrevented = Util.returnTrue - - if (e && !this.isSimulated) { - e.preventDefault() - } - } - - stopPropagation() { - const e = this.originalEvent - - this.isPropagationStopped = Util.returnTrue - - if (e && !this.isSimulated) { - e.stopPropagation() - } - } - - stopImmediatePropagation() { - const e = this.originalEvent - - this.isImmediatePropagationStopped = Util.returnTrue - - if (e && !this.isSimulated) { - e.stopImmediatePropagation() - } - - this.stopPropagation() - } -} - -export interface EventObject extends EventObject.Event {} - -export namespace EventObject { - export function create(originalEvent: EventRaw | EventObject | string) { - return originalEvent instanceof EventObject - ? originalEvent - : new EventObject(originalEvent) - } -} - -export namespace EventObject { - export function addProperty( - name: string, - hook?: any | ((e: EventRaw) => any), - ) { - Object.defineProperty(EventObject.prototype, name, { - enumerable: true, - configurable: true, - get: - typeof hook === 'function' - ? // eslint-disable-next-line - function (this: EventObject) { - if (this.originalEvent) { - return (hook as any)(this.originalEvent) - } - } - : // eslint-disable-next-line - function (this: EventObject) { - if (this.originalEvent) { - return this.originalEvent[name as 'type'] - } - }, - set(value) { - Object.defineProperty(this, name, { - enumerable: true, - configurable: true, - writable: true, - value, - }) - }, - }) - } -} - -export namespace EventObject { - // Common event props including KeyEvent and MouseEvent specific props. - const commonProps = { - bubbles: true, - cancelable: true, - eventPhase: true, - - detail: true, - view: true, - - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pageX: true, - pageY: true, - screenX: true, - screenY: true, - toElement: true, - - pointerId: true, - pointerType: true, - - char: true, - code: true, - charCode: true, - key: true, - keyCode: true, - - touches: true, - changedTouches: true, - targetTouches: true, - - which: true, - altKey: true, - ctrlKey: true, - metaKey: true, - shiftKey: true, - } - - Object.keys(commonProps).forEach((name: keyof typeof commonProps) => - EventObject.addProperty(name, commonProps[name]), - ) -} - -export namespace EventObject { - export interface Event { - // Event - - bubbles: boolean | undefined - cancelable: boolean | undefined - eventPhase: number | undefined - - // UIEvent - - detail: number | undefined - view: Window | undefined - - // MouseEvent - - button: number | undefined - buttons: number | undefined - clientX: number | undefined - clientY: number | undefined - offsetX: number | undefined - offsetY: number | undefined - pageX: number | undefined - pageY: number | undefined - screenX: number | undefined - screenY: number | undefined - /** @deprecated */ - toElement: Element | undefined - - // PointerEvent - - pointerId: number | undefined - pointerType: string | undefined - - // KeyboardEvent - - /** @deprecated */ - char: string | undefined - /** @deprecated */ - charCode: number | undefined - key: string | undefined - /** @deprecated */ - keyCode: number | undefined - - // TouchEvent - - touches: TouchList | undefined - targetTouches: TouchList | undefined - changedTouches: TouchList | undefined - - // MouseEvent, KeyboardEvent - - which: number | undefined - - // MouseEvent, KeyboardEvent, TouchEvent - - altKey: boolean | undefined - ctrlKey: boolean | undefined - metaKey: boolean | undefined - shiftKey: boolean | undefined - - type: string - timeStamp: number - - isDefaultPrevented(): boolean - isImmediatePropagationStopped(): boolean - isPropagationStopped(): boolean - preventDefault(): void - stopImmediatePropagation(): void - stopPropagation(): void - } -} diff --git a/packages/x6-vector/src/dom/events/special.ts b/packages/x6-vector/src/dom/events/special.ts deleted file mode 100644 index ecfd969c25f..00000000000 --- a/packages/x6-vector/src/dom/events/special.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Hook } from './hook' - -// Prevent triggered image.load events from bubbling to window.load -export namespace Special { - Hook.register('load', { - noBubble: true, - }) -} - -// Support: Chrome <=73+ -// Chrome doesn't alert on `event.preventDefault()` -// as the standard mandates. -export namespace Special { - Hook.register('beforeunload', { - postDispatch(elem, event) { - if (event.result !== undefined && event.originalEvent) { - event.originalEvent.returnValue = event.result - } - }, - }) -} diff --git a/packages/x6-vector/src/dom/events/store.ts b/packages/x6-vector/src/dom/events/store.ts deleted file mode 100644 index 515ead168a8..00000000000 --- a/packages/x6-vector/src/dom/events/store.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { EventHandler } from './types' - -export namespace Store { - export type EventTarget = Element | Record - - export interface HandlerObject { - guid: number - type: string - originType: string - handler: EventHandler - data?: any - selector?: string - namespace?: string - } - - export interface Data { - handler?: EventHandler - events: { - [type: string]: { - handlers: HandlerObject[] - delegateCount: number - } - } - } - - const cache: WeakMap = new WeakMap() - - export function ensure(target: EventTarget) { - if (!cache.has(target)) { - cache.set(target, { events: Object.create(null) }) - } - return cache.get(target)! - } - - export function get(target: EventTarget) { - return cache.get(target) - } - - export function remove(target: EventTarget) { - return cache.delete(target) - } -} diff --git a/packages/x6-vector/src/dom/events/types.ts b/packages/x6-vector/src/dom/events/types.ts deleted file mode 100644 index 70d04196b8a..00000000000 --- a/packages/x6-vector/src/dom/events/types.ts +++ /dev/null @@ -1,735 +0,0 @@ -import { EventObject } from './object' -import { - EventRaw, - UIEventRaw, - DragEventRaw, - FocusEventRaw, - MouseEventRaw, - TouchEventRaw, - KeyboardEventRaw, -} from './alias' - -interface EventBase< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, - TEvent extends EventRaw = any, -> extends EventObject { - relatedTarget?: undefined - - bubbles: boolean - cancelable: boolean - eventPhase: number - - detail: undefined - view: undefined - - button: undefined - buttons: undefined - clientX: undefined - clientY: undefined - offsetX: undefined - offsetY: undefined - pageX: undefined - pageY: undefined - screenX: undefined - screenY: undefined - - /** @deprecated */ - toElement: undefined - - pointerId: undefined - pointerType: undefined - - /** @deprecated */ - char: undefined - /** @deprecated */ - charCode: undefined - key: undefined - /** @deprecated */ - keyCode: undefined - - changedTouches: undefined - targetTouches: undefined - touches: undefined - - which: undefined - - altKey: undefined - ctrlKey: undefined - metaKey: undefined - shiftKey: undefined -} - -interface ChangeEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends EventBase { - type: 'change' -} - -interface ResizeEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends EventBase { - type: 'resize' -} - -interface ScrollEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends EventBase { - type: 'scroll' -} - -interface SelectEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends EventBase { - type: 'select' -} - -interface SubmitEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends EventBase { - type: 'submit' -} - -interface UIEventBase< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, - TEvent extends UIEventRaw = any, -> extends EventObject { - bubbles: boolean - cancelable: boolean - eventPhase: number - - detail: number - view: Window -} - -interface MouseEventBase< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends UIEventBase< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget, - MouseEventRaw - > { - relatedTarget?: EventTarget | null - - button: number - buttons: number - clientX: number - clientY: number - offsetX: number - offsetY: number - pageX: number - pageY: number - screenX: number - screenY: number - /** @deprecated */ - toElement: Element - - pointerId: undefined - pointerType: undefined - - /** @deprecated */ - char: undefined - /** @deprecated */ - charCode: undefined - key: undefined - /** @deprecated */ - keyCode: undefined - - changedTouches: undefined - targetTouches: undefined - touches: undefined - - which: number - - altKey: boolean - ctrlKey: boolean - - metaKey: boolean - shiftKey: boolean -} - -interface ClickEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends MouseEventBase { - relatedTarget?: null - type: 'click' -} - -interface ContextMenuEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends MouseEventBase { - relatedTarget?: null - type: 'contextmenu' -} - -interface DoubleClickEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends MouseEventBase { - relatedTarget?: null - type: 'dblclick' -} - -interface MouseDownEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends MouseEventBase { - relatedTarget?: null - type: 'mousedown' -} - -interface MouseEnterEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends MouseEventBase { - type: 'mouseenter' -} - -interface MouseLeaveEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends MouseEventBase { - type: 'mouseleave' -} - -interface MouseMoveEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends MouseEventBase { - relatedTarget?: null - type: 'mousemove' -} - -interface MouseOutEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends MouseEventBase { - type: 'mouseout' -} - -interface MouseOverEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends MouseEventBase { - type: 'mouseover' -} - -interface MouseUpEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends MouseEventBase { - relatedTarget?: null - type: 'mouseup' -} - -interface DragEventBase< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends UIEventBase< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget, - DragEventRaw - > {} - -interface DragEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends DragEventBase { - type: 'drag' -} - -interface DragEndEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends DragEventBase { - type: 'dragend' -} - -interface DragEnterEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends DragEventBase { - type: 'dragenter' -} - -interface DragExitEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends DragEventBase { - type: 'dragexit' -} - -interface DragLeaveEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends DragEventBase { - type: 'dragleave' -} - -interface DragOverEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends DragEventBase { - type: 'dragover' -} - -interface DragStartEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends DragEventBase { - type: 'dragstart' -} - -interface DropEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends DragEventBase { - type: 'drop' -} - -interface KeyboardEventBase< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends UIEventBase< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget, - KeyboardEventRaw - > { - relatedTarget?: undefined - - button: undefined - buttons: undefined - clientX: undefined - clientY: undefined - offsetX: undefined - offsetY: undefined - pageX: undefined - pageY: undefined - screenX: undefined - screenY: undefined - /** @deprecated */ - toElement: undefined - - pointerId: undefined - pointerType: undefined - - /** @deprecated */ - char: string | undefined - /** @deprecated */ - charCode: number - code: string - key: string - /** @deprecated */ - keyCode: number - - changedTouches: undefined - targetTouches: undefined - touches: undefined - - which: number - - altKey: boolean - ctrlKey: boolean - - metaKey: boolean - shiftKey: boolean -} - -interface KeyDownEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends KeyboardEventBase { - type: 'keydown' -} - -interface KeyPressEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends KeyboardEventBase { - type: 'keypress' -} - -interface KeyUpEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends KeyboardEventBase { - type: 'keyup' -} - -interface TouchEventBase< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends UIEventBase< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget, - TouchEventRaw - > { - relatedTarget?: undefined - - button: undefined - buttons: undefined - clientX: undefined - clientY: undefined - offsetX: undefined - offsetY: undefined - - pageY: undefined - screenX: undefined - screenY: undefined - /** @deprecated */ - toElement: undefined - - pointerId: undefined - pointerType: undefined - - /** @deprecated */ - char: undefined - /** @deprecated */ - charCode: undefined - key: undefined - /** @deprecated */ - keyCode: undefined - - changedTouches: TouchList - targetTouches: TouchList - touches: TouchList - - which: undefined - - altKey: boolean - ctrlKey: boolean - - metaKey: boolean - shiftKey: boolean -} - -interface TouchCancelEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends TouchEventBase { - type: 'touchcancel' -} - -interface TouchEndEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends TouchEventBase { - type: 'touchend' -} - -interface TouchMoveEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends TouchEventBase { - type: 'touchmove' -} - -interface TouchStartEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends TouchEventBase { - type: 'touchstart' -} - -interface FocusEventBase< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends UIEventBase< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget, - FocusEventRaw - > { - relatedTarget?: EventTarget | null - - button: undefined - buttons: undefined - clientX: undefined - clientY: undefined - offsetX: undefined - offsetY: undefined - pageX: undefined - pageY: undefined - screenX: undefined - screenY: undefined - - /** @deprecated */ - toElement: undefined - - pointerId: undefined - pointerType: undefined - - /** @deprecated */ - char: undefined - /** @deprecated */ - charCode: undefined - key: undefined - /** @deprecated */ - keyCode: undefined - - changedTouches: undefined - targetTouches: undefined - touches: undefined - - which: undefined - - altKey: undefined - ctrlKey: undefined - metaKey: undefined - shiftKey: undefined -} - -interface BlurEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends FocusEventBase { - type: 'blur' -} - -interface FocusEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends FocusEventBase { - type: 'focus' -} - -interface FocusInEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends FocusEventBase { - type: 'focusin' -} - -interface FocusOutEvent< - TDelegateTarget = any, - TData = any, - TCurrentTarget = any, - TTarget = any, -> extends FocusEventBase { - type: 'focusout' -} - -interface TypeToTriggeredEventMap< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget, -> { - // Event - - change: ChangeEvent - resize: ResizeEvent - scroll: ScrollEvent - select: SelectEvent - submit: SubmitEvent - - // UIEvent - - // MouseEvent - - click: ClickEvent - contextmenu: ContextMenuEvent - dblclick: DoubleClickEvent - mousedown: MouseDownEvent - mouseenter: MouseEnterEvent - mouseleave: MouseLeaveEvent - mousemove: MouseMoveEvent - mouseout: MouseOutEvent - mouseover: MouseOverEvent - mouseup: MouseUpEvent - - // DragEvent - - drag: DragEvent - dragend: DragEndEvent - dragenter: DragEnterEvent - dragexit: DragExitEvent - dragleave: DragLeaveEvent - dragover: DragOverEvent - dragstart: DragStartEvent - drop: DropEvent - - // KeyboardEvent - - keydown: KeyDownEvent - keypress: KeyPressEvent - keyup: KeyUpEvent - - // TouchEvent - - touchcancel: TouchCancelEvent - touchend: TouchEndEvent - touchmove: TouchMoveEvent - touchstart: TouchStartEvent - - // FocusEvent - - blur: BlurEvent - focus: FocusEvent - focusin: FocusInEvent - focusout: FocusOutEvent - - [type: string]: EventObject -} - -export type EventHandlerBase = ( - this: TContext, - e: T, - ...args: any[] -) => any - -export type EventHandler = EventHandlerBase< - TCurrentTarget, - EventObject -> - -export type TypeEventHandler< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget, - TType extends keyof TypeToTriggeredEventMap< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget - >, -> = EventHandlerBase< - TCurrentTarget, - TypeToTriggeredEventMap< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget - >[TType] -> - -export interface TypeEventHandlers< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget, -> extends TypeEventHandlersBase< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget - > { - // No idea why it's necessary to include `object` in the union but otherwise TypeScript complains that - // derived types of Event are not assignable to Event. - [type: string]: - | TypeEventHandler - | false - | undefined - | Record -} - -type TypeEventHandlersBase = { - [TType in keyof TypeToTriggeredEventMap< - TDelegateTarget, - TData, - TCurrentTarget, - TTarget - >]?: - | TypeEventHandler - | false - | Record -} diff --git a/packages/x6-vector/src/dom/events/util.ts b/packages/x6-vector/src/dom/events/util.ts deleted file mode 100644 index 495acf45d34..00000000000 --- a/packages/x6-vector/src/dom/events/util.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { Store } from './store' -import type { EventObject } from './object' - -export namespace Util { - export const returnTrue = () => true - export const returnFalse = () => false - export function stopPropagationCallback(e: Event) { - e.stopPropagation() - } - - export function addEventListener( - elem: TElement, - type: string, - handler: EventListener, - ) { - if (elem.addEventListener != null) { - elem.addEventListener(type, handler as any) - } - } - - export function removeEventListener( - elem: TElement, - type: string, - handler: EventListener, - ) { - if (elem.removeEventListener != null) { - elem.removeEventListener(type, handler as any) - } - } -} - -export namespace Util { - const rNotHTMLWhite = /[^\x20\t\r\n\f]+/g - const rNamespace = /^([^.]*)(?:\.(.+)|)/ - - export function splitType(types: string) { - return (types || '').match(rNotHTMLWhite) || [''] - } - - export function normalizeType(type: string) { - const parts = rNamespace.exec(type) || [] - return { - originType: parts[1] ? parts[1].trim() : parts[1], - namespaces: parts[2] - ? parts[2] - .split('.') - .map((ns) => ns.trim()) - .sort() - : [], - } - } - - export function isValidTarget(target: Element | Record) { - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return target.nodeType === 1 || target.nodeType === 9 || !+target.nodeType - } - - export function isValidSelector(elem: Store.EventTarget, selector?: string) { - if (selector) { - const node = elem as Element - return node.querySelector != null && node.querySelector(selector) != null - } - return true - } -} - -export namespace Util { - type Handler = (...args: any[]) => void - - let seed = 0 - const cache: WeakMap = new WeakMap() - - export function ensureHandlerId(handler: Handler) { - if (!cache.has(handler)) { - cache.set(handler, seed) - seed += 1 - } - - return cache.get(handler)! - } - - export function getHandlerId(handler: Handler) { - return cache.get(handler) - } - - export function removeHandlerId(handler: Handler) { - return cache.delete(handler) - } - - export function setHandlerId(handler: Handler, id: number) { - return cache.set(handler, id) - } -} - -export namespace Util { - export function getHandlerQueue(elem: Store.EventTarget, event: EventObject) { - const queue = [] - const store = Store.get(elem) - const bag = store && store.events && store.events[event.type] - const handlers = (bag && bag.handlers) || [] - const delegateCount = bag ? bag.delegateCount : 0 - - if ( - delegateCount > 0 && - // Support: Firefox <=42 - 66+ - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11+ - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( - event.type === 'click' && - typeof event.button === 'number' && - event.button >= 1 - ) - ) { - for ( - let curr = event.target as Node; - curr !== elem; - curr = curr.parentNode || (elem as Node) - ) { - // Don't check non-elements - // Don't process clicks on disabled elements - if ( - curr.nodeType === 1 && - !(event.type === 'click' && (curr as any).disabled === true) - ) { - const matchedHandlers: Store.HandlerObject[] = [] - const matchedSelectors: { [selector: string]: boolean } = {} - - for (let i = 0; i < delegateCount; i += 1) { - const handleObj = handlers[i] - const selector = handleObj.selector! - - if (selector != null && matchedSelectors[selector] == null) { - const node = elem as Element - const nodes: Element[] = [] - - node.querySelectorAll(selector).forEach((child) => { - nodes.push(child) - }) - - matchedSelectors[selector] = nodes.includes(curr as Element) - } - - if (matchedSelectors[selector]) { - matchedHandlers.push(handleObj) - } - } - - if (matchedHandlers.length) { - queue.push({ elem: curr, handlers: matchedHandlers }) - } - } - } - } - - // Add the remaining (directly-bound) handlers - if (delegateCount < handlers.length) { - queue.push({ elem, handlers: handlers.slice(delegateCount) }) - } - - return queue - } -} diff --git a/packages/x6-vector/src/dom/index.ts b/packages/x6-vector/src/dom/index.ts deleted file mode 100644 index 436c9b729f8..00000000000 --- a/packages/x6-vector/src/dom/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './dom' diff --git a/packages/x6-vector/src/dom/primer/affix.test.ts b/packages/x6-vector/src/dom/primer/affix.test.ts deleted file mode 100644 index 5bace9c6d13..00000000000 --- a/packages/x6-vector/src/dom/primer/affix.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Dom } from '..' -import { Affix } from './affix' - -describe('Dom', () => { - describe('affix', () => { - describe('affix()', () => { - it('shoule return an empty object when inited', () => { - const div = new Dom('div') - expect(div.affix()).toEqual({}) - expect(div.affix('foo')).toBeUndefined() - }) - - it('shoule set affix with the given object', () => { - const div = new Dom('div') - div.affix({ foo: 'bar' }) - expect(div.affix()).toEqual({ foo: 'bar' }) - expect(div.affix('foo')).toEqual('bar') - }) - - it('shoule set affix by key-value', () => { - const div = new Dom('div') - div.affix('foo', 'bar') - expect(div.affix()).toEqual({ foo: 'bar' }) - expect(div.affix('foo')).toEqual('bar') - }) - - it('shoule reset affix when pass null as first argument', () => { - const div = new Dom('div') - div.affix('foo', 'bar') - expect(div.affix()).toEqual({ foo: 'bar' }) - expect(div.affix('foo')).toEqual('bar') - - div.affix(null) - expect(div.affix()).toEqual({}) - expect(div.affix('foo')).toBeUndefined() - }) - - it('shoule remove key when the given value is null', () => { - const div = new Dom('div') - div.affix('foo', 'bar') - div.affix('foo', null) - expect(div.affix()).toEqual({}) - expect(div.affix('foo')).toBeUndefined() - }) - }) - - describe('storeAffix()', () => { - it('shoule store affix to dom', () => { - const div = new Dom('div') - div.affix({ foo: 'bar' }) - div.storeAffix() - expect(div.attr(Affix.PERSIST_ATTR_NAME)).toEqual('{"foo":"bar"}') - }) - - it('shoule store affix to dom deeply', () => { - const div = new Dom('div') - const span = new Dom('span') - - div.affix({ foo: 'bar' }) - span.affix({ a: 1 }) - - div.add(span) - div.storeAffix(true) - - expect(div.attr(Affix.PERSIST_ATTR_NAME)).toEqual('{"foo":"bar"}') - expect(span.attr(Affix.PERSIST_ATTR_NAME)).toEqual('{"a":1}') - }) - }) - - describe('restoreAffix()', () => { - it('shoule restore affix to dom', () => { - const div = new Dom('div') - const span = new Dom('span') - - div.affix({ foo: 'bar' }) - span.affix({ a: 1 }) - - div.add(span) - const div2 = div.clone(true) - const span2 = div2.firstChild()! - - expect(div2.attr(Affix.PERSIST_ATTR_NAME)).toEqual('{"foo":"bar"}') - expect(span2.attr(Affix.PERSIST_ATTR_NAME)).toEqual('{"a":1}') - }) - }) - }) -}) diff --git a/packages/x6-vector/src/dom/primer/affix.ts b/packages/x6-vector/src/dom/primer/affix.ts deleted file mode 100644 index f2258475a0a..00000000000 --- a/packages/x6-vector/src/dom/primer/affix.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Adopter } from '../common/adopter' -import { Base } from '../common/base' - -export class Affix extends Base { - protected affixes: Record | undefined - - affix>(): T - affix(key: string): T - affix(data: Record): this - affix(data: null): this - affix(key: string, value: any): this - affix(key: string, value: null): this - affix(key?: string | Record | null, value?: any) { - const bag = Affix.get(this) - - if (typeof key === 'undefined') { - return bag - } - - if (key == null) { - Affix.set(this, {}) - return this - } - - if (typeof key === 'string') { - if (typeof value === 'undefined') { - return bag[key] - } - - if (value == null) { - delete bag[key] - } else { - bag[key] = value - } - - return this - } - - Affix.set(this, key) - return this - } - - storeAffix(deep = false) { - Affix.store(this.node, deep) - return this - } - - restoreAffix() { - return this.affix(Affix.restore(this.node)) - } -} - -export namespace Affix { - const cache: WeakMap> = new WeakMap() - - export function get(instance: T): Record { - return cache.get(instance)! - } - - export function set(instance: T, item: Record) { - cache.set(instance, item) - } -} - -export namespace Affix { - export const PERSIST_ATTR_NAME = 'vector:data' - - export function store( - node: TElement, - deep: boolean, - ) { - if (node.removeAttribute) { - node.removeAttribute(PERSIST_ATTR_NAME) - const instance = Adopter.adopt(node) - const affixes = instance.affix() - if (affixes && Object.keys(affixes).length) { - node.setAttribute(PERSIST_ATTR_NAME, JSON.stringify(affixes)) - } - } - - if (deep) { - node.childNodes.forEach((child) => store(child as Element, deep)) - } - } - - export function restore( - node: TElement, - ): Record { - try { - const raw = node.getAttribute(PERSIST_ATTR_NAME) - if (raw) { - return JSON.parse(raw) - } - } catch (error) { - // pass - } - - return {} - } -} diff --git a/packages/x6-vector/src/dom/primer/classname.test.ts b/packages/x6-vector/src/dom/primer/classname.test.ts deleted file mode 100644 index 1175cf0d190..00000000000 --- a/packages/x6-vector/src/dom/primer/classname.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { Dom } from '../dom' -import { ClassName } from './classname' - -describe('Dom', () => { - describe('classname', () => { - let elem: Dom - - beforeEach(() => { - elem = new Dom('div').addClass('foo bar') - }) - - describe('classes()', () => { - it('should return sorted classnames', () => { - expect(elem.classes()).toEqual(['bar', 'foo']) - }) - }) - - describe('hasClass()', () => { - it('should return `false` when given classname is null or empty', () => { - expect(elem.hasClass('')).toBeFalse() - expect(elem.hasClass(null as any)).toBeFalse() - expect(elem.hasClass(undefined as any)).toBeFalse() - expect(ClassName.has(null, null)).toBeFalse() - expect(ClassName.has(null, undefined)).toBeFalse() - }) - - it('should return `false` when the node is invalid', () => { - const text = document.createTextNode('text') - ClassName.add(text as any, 'test') - expect(ClassName.has(text as any, 'test')).toBeFalse() - }) - - it('should return `true` when contains the given classname', () => { - expect(elem.hasClass('foo')).toBeTrue() - expect(elem.hasClass('bar')).toBeTrue() - }) - - it('should return `false` when do not contains the given classname', () => { - expect(elem.hasClass('fo')).toBeFalse() - expect(elem.hasClass('ba')).toBeFalse() - }) - - it('should return `true` when contains the given classnames', () => { - expect(elem.hasClass('foo bar')).toBeTrue() - expect(elem.hasClass('bar foo')).toBeTrue() - }) - - it('should return `false` when do not contains the given classnames', () => { - expect(elem.hasClass('foo bar 0')).toBeFalse() - expect(elem.hasClass('bar foo 1')).toBeFalse() - }) - }) - - describe('addClass()', () => { - it('should do nothing for invalid class', () => { - elem.addClass(null as any) - elem.addClass(undefined as any) - expect(elem.attr('class')).toEqual('foo bar') - }) - - it('should add single class', () => { - elem.addClass('test') - expect(elem.hasClass('test')).toBeTrue() - }) - - it('should add an array of classes', () => { - elem.addClass(['test1', 'test2']) - expect(elem.hasClass('foo')).toBeTrue() - expect(elem.hasClass('bar')).toBeTrue() - expect(elem.hasClass('test1')).toBeTrue() - expect(elem.hasClass('test2')).toBeTrue() - }) - - it('should add an multi classes with string', () => { - elem.addClass('test1 test2') - expect(elem.hasClass('bar')).toBeTrue() - expect(elem.hasClass('foo')).toBeTrue() - expect(elem.hasClass('test1')).toBeTrue() - expect(elem.hasClass('test2')).toBeTrue() - }) - - it('should not add the same class twice in same element', () => { - elem.addClass('foo').addClass('foo') - expect(elem.attr('class')).toEqual('foo bar') - elem.addClass('foo foo') - expect(elem.attr('class')).toEqual('foo bar') - }) - - it('should not add empty string', () => { - elem.addClass('test') - elem.addClass(' ') - expect(elem.attr('class')).toEqual('foo bar test') - }) - - it('should call hook', () => { - elem.removeClass().addClass('test') - elem.addClass((cls) => `${cls} foo`) - expect(elem.attr('class')).toEqual('test foo') - }) - }) - - describe('removeClass()', () => { - it('should do nothing for invalid node', () => { - ClassName.remove(null) - }) - - it('should remove one', () => { - elem.removeClass('foo test') - expect(elem.attr('class')).toEqual('bar') - }) - - it('should remove an array of classes', () => { - elem.addClass('test').removeClass(['foo', 'test']) - expect(elem.attr('class')).toEqual('bar') - }) - - it('should remove all', () => { - elem.removeClass() - expect(elem.attr('class')).toBeUndefined() - - elem.addClass('test foo') - elem.removeClass(null as any) - expect(elem.attr('class')).toBeUndefined() - }) - - it('should call hook', () => { - elem.removeClass((cls) => cls.split(' ')[1]) - expect(elem.attr('class')).toEqual('foo') - }) - }) - - describe('toggleClass()', () => { - it('should do nothing for invalid class', () => { - elem.toggleClass(null as any) - elem.toggleClass(undefined as any) - expect(elem.attr('class')).toEqual('foo bar') - }) - - it('should toggle class', () => { - elem.removeClass() - - elem.toggleClass('foo bar') - expect(elem.attr('class')).toEqual('foo bar') - - elem.toggleClass('foo') - expect(elem.attr('class')).toEqual('bar') - - elem.toggleClass('foo') - expect(elem.attr('class')).toEqual('bar foo') - }) - - it('should not toggle empty strings', () => { - elem.removeClass() - - elem.toggleClass('foo bar') - expect(elem.attr('class')).toEqual('foo bar') - - elem.toggleClass(' ') - expect(elem.attr('class')).toEqual('foo bar') - - elem.toggleClass(' ') - expect(elem.attr('class')).toEqual('foo bar') - }) - - it('should work with the specified next state', () => { - elem.removeClass() - - elem.toggleClass('foo bar') - expect(elem.attr('class')).toEqual('foo bar') - - elem.toggleClass('foo', true) - expect(elem.attr('class')).toEqual('foo bar') - - elem.toggleClass('foo', true) - expect(elem.attr('class')).toEqual('foo bar') - - elem.toggleClass('foo', false) - expect(elem.attr('class')).toEqual('bar') - }) - - it('should call hook', () => { - elem.removeClass() - - elem.toggleClass(() => 'foo bar') - expect(elem.attr('class')).toEqual('foo bar') - - elem.toggleClass(() => 'foo', false) - expect(elem.attr('class')).toEqual('bar') - }) - }) - }) -}) diff --git a/packages/x6-vector/src/dom/primer/classname.ts b/packages/x6-vector/src/dom/primer/classname.ts deleted file mode 100644 index fd8cc013ed8..00000000000 --- a/packages/x6-vector/src/dom/primer/classname.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { Base } from '../common/base' - -export class ClassName extends Base { - classes() { - const raw = ClassName.get(this.node) - return ClassName.split(raw) - } - - hasClass(name: string) { - if (name == null || name.length === 0) { - return false - } - - return ClassName.split(name).every((name) => ClassName.has(this.node, name)) - } - - addClass(name: string): this - addClass(names: string[]): this - addClass(hook: (old: string) => string): this - addClass(name: string | string[] | ((old: string) => string)) { - ClassName.add(this.node, Array.isArray(name) ? name.join(' ') : name) - return this - } - - removeClass(): this - removeClass(name: string): this - removeClass(names: string[]): this - removeClass(hook: (old: string) => string): this - removeClass(name?: string | string[] | ((old: string) => string)) { - ClassName.remove(this.node, Array.isArray(name) ? name.join(' ') : name) - return this - } - - toggleClass(name: string): this - toggleClass(name: string, stateValue: boolean): this - toggleClass( - hook: (old: string, status?: boolean) => string, - stateValue?: boolean, - ): this - toggleClass( - name: string | ((old: string, status?: boolean) => string), - stateValue?: boolean, - ) { - ClassName.toggle(this.node, name, stateValue) - return this - } -} - -export namespace ClassName { - const rclass = /[\t\n\f\r]/g - const rnotwhite = /\S+/g - - const fillSpaces = (string: string) => ` ${string} ` - - export function split(name: string) { - return name - .split(/\s+/) - .map((name) => name.trim()) - .filter((name) => name.length > 0) - .sort() - } - - export function get(node: TElement) { - return (node && node.getAttribute && node.getAttribute('class')) || '' - } - - export function has( - node: TElement | null | undefined, - selector: string | null | undefined, - ) { - if (node == null || selector == null) { - return false - } - - const classNames = fillSpaces(get(node)) - const className = fillSpaces(selector) - - return node.nodeType === 1 - ? classNames.replace(rclass, ' ').includes(className) - : false - } - - export function add( - node: TElement | null | undefined, - selector: ((cls: string) => string) | string | null | undefined, - ): void { - if (node == null || selector == null) { - return - } - - if (typeof selector === 'function') { - add(node, selector(get(node))) - return - } - - if (typeof selector === 'string' && node.nodeType === 1) { - const classes = selector.match(rnotwhite) || [] - const oldValue = fillSpaces(get(node)).replace(rclass, ' ') - let newValue = classes.reduce((memo, cls) => { - if (!memo.includes(fillSpaces(cls))) { - return `${memo}${cls} ` - } - return memo - }, oldValue) - - newValue = newValue.trim() - - if (oldValue !== newValue) { - node.setAttribute('class', newValue) - } - } - } - - export function remove( - node: TElement | null | undefined, - selector?: ((cls: string) => string) | string | null | undefined, - ): void { - if (node == null) { - return - } - - if (typeof selector === 'function') { - remove(node, selector(get(node))) - return - } - - if ((!selector || typeof selector === 'string') && node.nodeType === 1) { - const classes = (selector || '').match(rnotwhite) || [] - const oldValue = fillSpaces(get(node)).replace(rclass, ' ') - let newValue = classes.reduce((memo, cls) => { - const className = fillSpaces(cls) - if (memo.includes(className)) { - return memo.replace(className, ' ') - } - - return memo - }, oldValue) - - newValue = selector ? newValue.trim() : '' - - if (oldValue !== newValue) { - node.setAttribute('class', newValue) - } - } - } - - export function toggle( - node: TElement | null | undefined, - selector: - | ((cls: string, status?: boolean) => string) - | string - | null - | undefined, - state?: boolean, - ): void { - if (node == null || selector == null) { - return - } - - if (state != null && typeof selector === 'string') { - if (state) { - add(node, selector) - } else { - remove(node, selector) - } - return - } - - if (typeof selector === 'function') { - toggle(node, selector(get(node), state), state) - return - } - - if (typeof selector === 'string') { - const metches = selector.match(rnotwhite) || [] - metches.forEach((cls) => { - if (has(node, cls)) { - remove(node, cls) - } else { - add(node, cls) - } - }) - } - } -} diff --git a/packages/x6-vector/src/dom/primer/data.test.ts b/packages/x6-vector/src/dom/primer/data.test.ts deleted file mode 100644 index 728fc60a38f..00000000000 --- a/packages/x6-vector/src/dom/primer/data.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Dom } from '../dom' - -describe('Dom', () => { - describe('data', () => { - describe('data()', () => { - it('should set data attribute by key-value', () => { - const div = new Dom('div') - div.data('key1', 'foo') - div.data('key2', 1) - div.data('key3', true) - div.data('key4', false) - div.data('key5', null) - div.data('key6', { a: 1 }) - - expect(div.node.getAttribute('data-key1')).toEqual('foo') - expect(div.node.getAttribute('data-key2')).toEqual('1') - expect(div.node.getAttribute('data-key3')).toEqual('true') - expect(div.node.getAttribute('data-key4')).toEqual('false') - expect(div.node.getAttribute('data-key5')).toEqual('null') - expect(div.node.getAttribute('data-key6')).toEqual('{"a":1}') - }) - - it('should get data attribute by key', () => { - const div = new Dom('div') - div.data('key1', 'foo') - div.data('key2', 1) - div.data('key3', true) - div.data('key4', false) - div.data('key5', null) - div.data('key6', { a: 1 }) - - expect(div.data('key1')).toEqual('foo') - expect(div.data('key2')).toEqual(1) - expect(div.data('key3')).toEqual(true) - expect(div.data('key4')).toEqual(false) - expect(div.data('key5')).toBeNull() - expect(div.data('key6')).toEqual({ a: 1 }) - }) - - it('should set data attributes by object', () => { - const div = new Dom('div') - div.data({ - key1: 'foo', - key2: 1, - key3: true, - key4: false, - key5: null, - key6: { a: 1 }, - }) - expect(div.data('key1')).toEqual('foo') - expect(div.data('key2')).toEqual(1) - expect(div.data('key3')).toEqual(true) - expect(div.data('key4')).toEqual(false) - expect(div.data('key5')).toBeNull() - expect(div.data('key6')).toEqual({ a: 1 }) - }) - - it('should return all data attributes', () => { - const div = new Dom('div') - div.data('key1', 'foo') - div.data('key2', 1) - div.data('key3', true) - div.data('key4', false) - div.data('key5', null) - div.data('key6', { a: 1 }) - expect(div.data()).toEqual({ - key1: 'foo', - key2: 1, - key3: true, - key4: false, - key5: null, - key6: { a: 1 }, - }) - }) - - it('should return specified data attributes', () => { - const div = new Dom('div') - div.data('key1', 'foo') - div.data('key2', 1) - div.data('key3', true) - div.data('key4', false) - div.data('key5', null) - div.data('key6', { a: 1 }) - expect(div.data(['key1', 'key6', 'key'])).toEqual({ - key1: 'foo', - key6: { a: 1 }, - key: null, - }) - }) - }) - }) -}) diff --git a/packages/x6-vector/src/dom/primer/data.ts b/packages/x6-vector/src/dom/primer/data.ts deleted file mode 100644 index ae946425e59..00000000000 --- a/packages/x6-vector/src/dom/primer/data.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Base } from '../common/base' - -export class Data extends Base { - data(): Record - data(key: string): T - data(keys: string[]): Record - data(data: Record): this - data(key: string, value: T, raw?: boolean): this - data( - key?: string | string[] | Record, - val?: any, - raw?: boolean, - ) { - // Get all data - if (key == null) { - const attrs = this.node.attributes - const keys: string[] = [] - for (let i = 0, l = attrs.length; i < l; i += 1) { - const item = attrs.item(i) - if (item && item.nodeName.startsWith('data-')) { - keys.push(item.nodeName.slice(5)) - } - } - return this.data(keys) - } - - // Get specified data with keys - if (Array.isArray(key)) { - const data: Record = {} - key.forEach((k) => { - // Return the camelCased key - data[Data.camelCase(k)] = this.data(k) - }) - return data - } - - // Set with data object - if (typeof key === 'object') { - Object.keys(key).forEach((k) => this.data(k, key[k])) - return this - } - - const dataKey = Data.parseKey(key) - const node = this.node - - // Get by key - if (typeof val === 'undefined') { - return Data.parseValue(node.getAttribute(dataKey)) - } - - // Set with key-value - { - const dataValue = - val === null - ? null - : raw === true || typeof val === 'string' || typeof val === 'number' - ? val - : JSON.stringify(val) - - node.setAttribute(dataKey, dataValue) - } - - return this - } -} - -export namespace Data { - const rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/ - - export function camelCase(str: string) { - return str.replace(/-([a-z])/g, (input) => input[1].toUpperCase()) - } - - export function parseKey(key: string) { - return `data-${key.replace(/[A-Z]/g, '-$&').toLowerCase()}` - } - - export function parseValue(val: string | null) { - if (val && typeof val === 'string') { - if (val === 'true') { - return true - } - - if (val === 'false') { - return false - } - - if (val === 'null') { - return null - } - - // Only convert to a number if it doesn't change the string - if (val === `${+val}`) { - return +val - } - - if (rbrace.test(val)) { - try { - return JSON.parse(val) - } catch { - // pass - } - } - } - - return val - } -} diff --git a/packages/x6-vector/src/dom/primer/index.ts b/packages/x6-vector/src/dom/primer/index.ts deleted file mode 100644 index de6aeac5293..00000000000 --- a/packages/x6-vector/src/dom/primer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './primer' diff --git a/packages/x6-vector/src/dom/primer/memory.test.ts b/packages/x6-vector/src/dom/primer/memory.test.ts deleted file mode 100644 index 534282616d6..00000000000 --- a/packages/x6-vector/src/dom/primer/memory.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Dom } from '../dom' - -describe('Dom', () => { - describe('memory', () => { - describe('memory()', () => { - it('should return an empty object after inited', () => { - expect(new Dom('div').memory()).toEqual({}) - }) - }) - - describe('remember()', () => { - it('should remember the given key-value', () => { - const div = new Dom('div') - div.remember('key1', 'foo') - div.remember('key2', 1) - div.remember('key3', true) - div.remember('key4', false) - div.remember('key5', null) - div.remember('key6', { a: 1 }) - - expect(div.remember('key1')).toEqual('foo') - expect(div.remember('key2')).toEqual(1) - expect(div.remember('key3')).toEqual(true) - expect(div.remember('key4')).toEqual(false) - expect(div.remember('key5')).toBeUndefined() - expect(div.remember('key6')).toEqual({ a: 1 }) - }) - - it('should remember the given object', () => { - const div = new Dom('div') - div.remember({ - key1: 'foo', - key2: 1, - key3: true, - key4: false, - key5: null, - key6: { a: 1 }, - }) - - expect(div.remember('key1')).toEqual('foo') - expect(div.remember('key2')).toEqual(1) - expect(div.remember('key3')).toEqual(true) - expect(div.remember('key4')).toEqual(false) - expect(div.remember('key5')).toBeUndefined() - expect(div.remember('key6')).toEqual({ a: 1 }) - }) - - it('should forget the key when the given value is null', () => { - const div = new Dom('div') - div.remember('key', 'foo') - div.remember('key', null) - expect(div.remember('key')).toBeUndefined() - }) - }) - - describe('forget()', () => { - it('should forget all', () => { - const div = new Dom('div') - div.remember('key1', 'foo') - div.remember('key2', 1) - div.remember('key3', true) - div.remember('key4', false) - div.remember('key5', null) - div.remember('key6', { a: 1 }) - - const keys = ['key1', 'key2', 'key3', 'key4', 'key5', 'key6'] - - div.forget() - - keys.forEach((key) => expect(div.remember(key)).toBeUndefined()) - }) - - it('should forget the given key(s)', () => { - const div = new Dom('div') - div.remember('key1', 'foo') - div.remember('key2', 1) - div.remember('key3', true) - div.remember('key4', false) - div.remember('key5', null) - div.remember('key6', { a: 1 }) - - const keys = ['key1', 'key2', 'key6'] - - div.forget(...keys) - - keys.forEach((key) => expect(div.remember(key)).toBeUndefined()) - }) - }) - }) -}) diff --git a/packages/x6-vector/src/dom/primer/memory.ts b/packages/x6-vector/src/dom/primer/memory.ts deleted file mode 100644 index 1476f1a8789..00000000000 --- a/packages/x6-vector/src/dom/primer/memory.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Base } from '../common/base' - -export class Memory extends Base { - private memo: Record - - remember(obj: Record): this - remember(key: string): T - remember(key: string, v: T): this - remember(k: Record | string, v?: T) { - if (typeof k === 'object') { - Object.keys(k).forEach((key) => this.remember(key, k[key])) - return this - } - - const memory = this.memory() - if (typeof v === 'undefined') { - return memory[k] - } - - if (v == null) { - this.forget(k) - } else { - memory[k] = v - } - - return this - } - - forget(...keys: string[]) { - if (keys.length === 0) { - this.memo = {} - } else { - const memory = this.memory() - keys.forEach((key) => delete memory[key]) - } - return this - } - - memory() { - if (this.memo == null) { - this.memo = {} - } - return this.memo - } -} diff --git a/packages/x6-vector/src/dom/primer/primer.test.ts b/packages/x6-vector/src/dom/primer/primer.test.ts deleted file mode 100644 index 0d713fe770e..00000000000 --- a/packages/x6-vector/src/dom/primer/primer.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Dom } from '../dom' -import { G } from '../../vector/g/g' -import { SVG } from '../../vector/svg/svg' - -describe('Dom', () => { - describe('constructor', () => { - it('should create element with the given node', () => { - const div = new Dom(document.createElement('div'), { tabIndex: 1 }) - expect(div.type.toLowerCase()).toEqual('div') - expect(div.attr('tabIndex')).toEqual(1) - }) - - it('should create element with the given tagName', () => { - const div = new Dom('div', { tabIndex: 1 }) - expect(div.type.toLowerCase()).toEqual('div') - expect(div.attr('tabIndex')).toEqual(1) - }) - - it('should create a div by default', () => { - const div = new Dom() - expect(div.type.toLowerCase()).toEqual('div') - expect(div.node).toBeInstanceOf(HTMLDivElement) - }) - - it('should create element with the given tagName and return the correct type', () => { - const g = new Dom('g') - expect(g.type.toLowerCase()).toEqual('g') - expect(g.node).toBeInstanceOf(SVGGElement) - - const svg = new Dom('svg', { x: 10, y: 10 } as any) - expect(svg.type.toLowerCase()).toEqual('svg') - expect(svg.node).toBeInstanceOf(SVGSVGElement) - expect(svg.attr('x')).toEqual(10) - expect(svg.attr('y')).toEqual(10) - }) - - it('should create element with auto detected tagName', () => { - const g = new G() - expect(g.type.toLowerCase()).toEqual('g') - expect(g.node).toBeInstanceOf(SVGGElement) - - const svg = new SVG({ x: 10, y: 10 }) - expect(svg.type.toLowerCase()).toEqual('svg') - expect(svg.node).toBeInstanceOf(SVGSVGElement) - expect(svg.attr('x')).toEqual(10) - expect(svg.attr('y')).toEqual(10) - }) - - it('should throw an error', () => { - class HTML extends Dom {} - expect(() => new HTML()).toThrowError() - }) - }) -}) diff --git a/packages/x6-vector/src/dom/primer/primer.ts b/packages/x6-vector/src/dom/primer/primer.ts deleted file mode 100644 index 28fdac61de8..00000000000 --- a/packages/x6-vector/src/dom/primer/primer.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { isNode } from '../../util/dom' -import { Base } from '../common/base' -import { Registry } from '../common/registry' -import { Affix } from './affix' -import { Data } from './data' -import { Memory } from './memory' -import { ClassName } from './classname' -import { Style, Hook as StyleHook } from '../style' -import { - Attributes, - AttributesMap, - Hook as AttributesHook, -} from '../attributes' -import { EventEmitter, EventListener, Hook as EventHook } from '../events' -import { Adopter } from '../common/adopter' - -@Primer.mixin( - Affix, - Data, - Memory, - Attributes, - ClassName, - Style, - EventEmitter, - EventListener, -) -export class Primer extends Base { - public readonly node: TElement - - public get type() { - return this.node.nodeName - } - - constructor() - constructor(attrs?: AttributesMap | null) - constructor(node: TElement | null, attrs?: AttributesMap | null) - constructor(tagName: string | null, attrs?: AttributesMap | null) - constructor( - nodeOrAttrs?: TElement | string | AttributesMap | null, - attrs?: AttributesMap | null, - ) - constructor( - nodeOrAttrs?: TElement | string | AttributesMap | null, - attrs?: AttributesMap | null, - ) { - super() - - let attributes: AttributesMap | null | undefined - if (isNode(nodeOrAttrs)) { - this.node = nodeOrAttrs - attributes = attrs - } else { - const ctor = this.constructor as Registry.Definition - let tagName = - typeof nodeOrAttrs === 'string' - ? nodeOrAttrs - : Registry.getTagName(ctor) - if (tagName) { - if (tagName === 'dom') { - tagName = 'div' // return new Dom('div') by dafault - } - - this.node = Adopter.createNode(tagName) - attributes = - nodeOrAttrs != null && typeof nodeOrAttrs !== 'string' - ? nodeOrAttrs - : attrs - } else { - throw new Error( - `Can not initialize "${ctor.name}" with unknown tag name`, - ) - } - } - - if (attributes) { - this.attr(attributes) - } - - this.restoreAffix() - Adopter.cache(this.node, this) - } -} - -export interface Primer - extends Affix, - Data, - Memory, - Style, - Attributes, - ClassName, - EventEmitter, - EventListener {} - -export namespace Primer { - export const registerEventHook = EventHook.register - export const registerStyleHook = StyleHook.register - export const registerAttributeHook = AttributesHook.register -} diff --git a/packages/x6-vector/src/dom/style/.gitignore b/packages/x6-vector/src/dom/style/.gitignore deleted file mode 100644 index 58ccd79dcdb..00000000000 --- a/packages/x6-vector/src/dom/style/.gitignore +++ /dev/null @@ -1 +0,0 @@ -csstype.ts diff --git a/packages/x6-vector/src/dom/style/hook.ts b/packages/x6-vector/src/dom/style/hook.ts deleted file mode 100644 index 878a6969589..00000000000 --- a/packages/x6-vector/src/dom/style/hook.ts +++ /dev/null @@ -1,26 +0,0 @@ -export namespace Hook { - export interface Definition { - get?: ( - elem: TElement, - computed?: boolean, - ) => string | number | undefined - set?: ( - elem: TElement, - styleValue: string | number, - ) => string | number | undefined - } - - const hooks: Record = {} - - export function get(styleName: string) { - return hooks[styleName] - } - - export function register(styleName: string, hook: Definition) { - hooks[styleName] = hook - } - - export function unregister(styleName: string) { - delete hooks[styleName] - } -} diff --git a/packages/x6-vector/src/dom/style/index.ts b/packages/x6-vector/src/dom/style/index.ts deleted file mode 100644 index a24a72b9186..00000000000 --- a/packages/x6-vector/src/dom/style/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './hook' -export * from './style' -export * from './types' diff --git a/packages/x6-vector/src/dom/style/style.test.ts b/packages/x6-vector/src/dom/style/style.test.ts deleted file mode 100644 index f3ccfe3d323..00000000000 --- a/packages/x6-vector/src/dom/style/style.test.ts +++ /dev/null @@ -1,324 +0,0 @@ -import { Dom } from '../dom' -import { Hook } from './hook' - -describe('Dom', () => { - describe('css', () => { - function withCSSContext(css: string, callback: () => void) { - const head = document.head || document.getElementsByTagName('head')[0] - const style = document.createElement('style') - style.setAttribute('type', 'text/css') - head.appendChild(style) - - const elem = style as any - if (elem.styleSheet) { - // This is required for IE8 and below. - elem.styleSheet.cssText = css - } else { - style.appendChild(document.createTextNode(css)) - } - callback() - style.parentNode?.removeChild(style) - } - - describe('css()', () => { - it('should set style with style name-value', () => { - const div = new Dom('div') - div.css('border', '1px') - expect(div.node.getAttribute('style')).toEqual('border: 1px;') - }) - - it('should set style with style cameCase name-value', () => { - const div = new Dom('div') - div.css('borderLeft', '1px') - expect(div.node.getAttribute('style')).toEqual('border-left: 1px;') - }) - - it('should auto add unit when set style with style name-value', () => { - const div = new Dom('div') - div.css('border', 1) - expect(div.node.getAttribute('style')).toEqual('border: 1px;') - }) - - it('should set style with object', () => { - const div = new Dom('div') - div.css({ border: 1, fontSize: 12 }) - expect(div.node.getAttribute('style')).toEqual( - 'border: 1px; font-size: 12px;', - ) - }) - - it('should get style by styleName', () => { - const div = new Dom('div') - expect(div.css('border')).toEqual('') - div.css({ border: 1, fontSize: 12 }) - expect(div.css('border')).toEqual('1px') - expect(div.css('fontSize')).toEqual('12px') - }) - - it('should get computed style by styleName', () => { - const body = new Dom(document.body) - const div = new Dom('div') - body.css({ fontSize: 18 }) - div.appendTo(body) - expect(div.css('fontSize', true)).toEqual('18px') - body.attr('style', null) - div.remove() - }) - - it('should get computed style by styleNames', () => { - const body = new Dom(document.body) - const div = new Dom('div') - body.css({ fontSize: 18, fontWeight: 500 }) - div.appendTo(body) - expect(div.css(['fontSize', 'fontWeight'], true)).toEqual({ - fontSize: '18px', - fontWeight: 500, - }) - body.attr('style', null) - div.remove() - }) - - it('should return all inline style', () => { - const div = new Dom('div') - div.css({ border: 1, left: 0 }) - expect(div.css()).toEqual({ - left: '0px', - border: '1px', - }) - }) - - it('should return an empty object when get all style on invalid node', () => { - const mock = new Dom() as any - mock.node = {} - expect(mock.css()).toEqual({}) - }) - - it('should return undefined when get style on invalid node', () => { - const mock = new Dom() as any - mock.node = {} - expect(mock.css('foo')).toBeUndefined() - }) - - it('should return style by given style names', () => { - const div = new Dom('div') - div.css({ border: 1, left: 0 }) - expect(div.css(['left', 'border'])).toEqual({ - left: '0px', - border: '1px', - }) - }) - - it('should return all the computed style', () => { - const div = new Dom('div') - const body = new Dom(document.body) - body.css({ fontSize: 18 }) - div.appendTo(body) - expect(div.css(true).fontSize).toEqual('18px') - body.attr('style', null) - div.remove() - }) - - it('should fallback to get inline style if node is not in the document', () => { - const div = new Dom('div') - div.css({ fontSize: 18 }) - expect(div.css('fontSize', true)).toEqual('18px') - }) - - it('should try to convert style value to number', () => { - const div = new Dom('div') - div.css({ fontWeight: 600 }) - expect(div.css('fontWeight', true)).toEqual(600) - expect(div.css('fontWeight')).toEqual(600) - }) - - it('should set custom style', () => { - const div = new Dom('div') - div.css('--custom-key', '10px') - expect(div.node.getAttribute('style')).toEqual('--custom-key:10px;') - div.attr('style', null) - div.css('--customKey', '10px') - expect(div.node.getAttribute('style')).toEqual('--custom-key:10px;') - }) - - it('should get custom style', () => { - const div = new Dom('div') - div.css({ - fontSize: 18, - '--custom-key': '10px', - }) - expect(div.css('--customKey')).toEqual('10px') - expect(div.css('--custom-key')).toEqual('10px') - expect(div.css()).toEqual({ - fontSize: '18px', - '--customKey': '10px', - } as any) - }) - - it('should not set style on text/comment node', () => { - const text = new Dom(document.createTextNode('test') as any) - text.css({ fontSize: 18 }) - expect(text.css('fontSize')).toBeUndefined() - expect(text.css()).toEqual({}) - expect(text.css(true)).toEqual({}) - - const comment = new Dom(document.createComment('test') as any) - comment.css({ fontSize: 18 }) - expect(comment.css('fontSize')).toBeUndefined() - expect(comment.css()).toEqual({}) - expect(comment.css(true)).toEqual({}) - }) - - it('should handle "float" specialy', () => { - const div = new Dom('div').appendTo(document.body) - div.css({ float: 'left' }) - expect(div.css('float', true)).toEqual('left') - expect(div.css('float')).toEqual('left') - div.remove() - }) - - it('should auto add browser prefix to style name', () => { - const div = new Dom('div').appendTo(document.body) - div.css('userDrag', 'none') - expect(div.node.getAttribute('style')).toEqual( - '-webkit-user-drag: none;', - ) - expect(div.css('userDrag', true)).toEqual('none') - expect(div.css('userDrag')).toEqual('none') - div.remove() - }) - - it('should apply hook when get style', () => { - const div = new Dom('div') - div.css('fontSize', 18) - Hook.register('fontSize', { - get(node, computed) { - return computed ? 16 : 14 - }, - }) - expect(div.css('fontSize', true)).toEqual(16) - expect(div.css('fontSize')).toEqual(14) - Hook.unregister('fontSize') - }) - - it('should apply hook when set style', () => { - const div = new Dom('div') - Hook.register('fontSize', { - set() { - return 20 - }, - }) - div.css('fontSize', 18) - expect(div.css('fontSize')).toEqual('20px') - }) - }) - - describe('show()', () => { - it('should not change the "display" style when it\'s visible', () => { - const div = new Dom('div') - div.show() - expect(div.node.getAttribute('style')).toBeNull() - expect(div.css('display')).toEqual('') - expect(div.visible()).toBeTrue() - }) - - it('should show the node', () => { - const div = new Dom('div') - div.css('display', 'none') - div.show() - expect(div.node.getAttribute('style')).toEqual('') - expect(div.css('display')).toEqual('') - expect(div.visible()).toBeTrue() - }) - - it('should recover the old value of "display" style', () => { - const div = new Dom('div') - div.css('display', 'inline-block') - - div.hide() - expect(div.css('display')).toEqual('none') - expect(div.visible()).toBeFalse() - - div.show() - expect(div.css('display')).toEqual('inline-block') - expect(div.visible()).toBeTrue() - }) - - it('should force visible when it was hidden in tree', () => { - withCSSContext('.hidden { display: none; }', () => { - const div = new Dom('div').appendTo(document.body) - div.addClass('hidden') - expect(div.visible()).toBeFalse() - div.show() - expect(div.visible()).toBeTrue() - expect(div.css('display')).toEqual('block') - div.remove() - }) - }) - }) - - describe('hide()', () => { - it('should hide the node', () => { - const div = new Dom('div') - div.hide() - expect(div.css('display')).toEqual('none') - expect(div.visible()).toBeFalse() - div.show() - expect(div.css('display')).toEqual('') - expect(div.visible()).toBeTrue() - }) - - it('should do nothing for invalid node', () => { - const mock = new Dom() as any - mock.node = {} - expect(() => mock.hide()).not.toThrowError() - }) - }) - - describe('visible()', () => { - it('should return false when it was hidden in tree', () => { - withCSSContext('.hidden { display: none; }', () => { - const div = new Dom('div').appendTo(document.body) - div.addClass('hidden') - expect(div.visible()).toBeFalse() - - div.removeClass('hidden') - expect(div.visible()).toBeTrue() - - div.addClass('hidden') - expect(div.visible()).toBeFalse() - - div.toggle() - expect(div.visible()).toBeTrue() - div.toggle() - expect(div.visible()).toBeFalse() - - div.remove() - }) - }) - }) - - describe('toggle()', () => { - it('should toggle visible state of node', () => { - const div = new Dom('div') - div.toggle() - expect(div.css('display')).toEqual('none') - expect(div.visible()).toBeFalse() - div.toggle() - expect(div.css('display')).toEqual('') - expect(div.visible()).toBeTrue() - }) - - it('should set visible state of node', () => { - const div = new Dom('div') - div.hide() - div.toggle(false) - expect(div.css('display')).toEqual('none') - expect(div.visible()).toBeFalse() - - div.toggle(true) - expect(div.css('display')).toEqual('') - expect(div.visible()).toBeTrue() - }) - }) - }) -}) diff --git a/packages/x6-vector/src/dom/style/style.ts b/packages/x6-vector/src/dom/style/style.ts deleted file mode 100644 index f1839913d41..00000000000 --- a/packages/x6-vector/src/dom/style/style.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Base } from '../common/base' -import { Util, MockedCSSName } from './util' -import { CSSProperties, CSSPropertyName } from './types' - -export class Style extends Base { - /** - * Set style properties with given `CSSProperties` object. - */ - css(style: T): this - /** - * Set style property with given style-name and style-value. - */ - css( - styleName: T, - styleValue: CSSProperties[T], - ): this - /** - * Get style property by given style-name. Returns computed style when - * `computed` is `true`, otherwise returns inline style. - */ - css( - styleName: T, - computed?: boolean, - ): CSSProperties[T] - /** - * Get style properties by given style-names. Returns computed style - * properties when `computed`is `true`, otherwise returns inline style - * properties. - */ - css( - styleNames: T, - computed?: boolean, - ): { [K in T[number]]: CSSProperties[K] } - /** - * Get style properties. Returns computed style properties when `computed` - * is `true`, otherwise returns inline style properties. - */ - css(computed?: boolean): CSSProperties - css(styleName: string, computed?: boolean): string | number - css(styleNames: string[], computed?: boolean): Record - css(styleName: string, styleValue: string | number): this - css( - style?: - | boolean - | CSSPropertyName - | CSSPropertyName[] - | CSSProperties - | string - | string[], - value?: string | number | null | boolean, - ) { - const node = this.node as any as HTMLElement - - // get full style as object - if (style == null || typeof style === 'boolean') { - if (style) { - const result: CSSProperties = {} - if (Util.isValidNode(node)) { - const computedStyle = Util.getComputedStyle(node) - Array.from(computedStyle).forEach((key) => { - const name = Util.camelCase(key) as MockedCSSName - result[name] = Util.css(node, key, computedStyle) - }) - } - return result - } - - return Util.style(node) - } - - // get style properties as array - if (Array.isArray(style)) { - const result: CSSProperties = {} - if (value) { - if (Util.isValidNode(node)) { - const computedStyle = Util.getComputedStyle(node) - style.forEach((name: MockedCSSName) => { - result[name] = Util.css(node, name, computedStyle) - }) - } - } else { - style.forEach((name: MockedCSSName) => { - result[name] = Util.style(node, name) - }) - } - return result - } - - // set styles in object - if (typeof style === 'object') { - Object.keys(style).forEach((name: CSSPropertyName) => - this.css(name, style[name as MockedCSSName]!), - ) - return this - } - - // get style for property - if (value == null || typeof value === 'boolean') { - return value ? Util.css(node, style) : Util.style(node, style) - } - - // set style for property - Util.style(node, style, value) - - return this - } - - visible() { - return !Util.isHiddenWithinTree(this.node) - } - - show() { - Util.showHide(this.node, true) - return this - } - - hide() { - Util.showHide(this.node, false) - return this - } - - toggle(state?: boolean) { - if (typeof state === 'boolean') { - return state ? this.show() : this.hide() - } - - return Util.isHiddenWithinTree(this.node) ? this.show() : this.hide() - } -} diff --git a/packages/x6-vector/src/dom/style/types.ts b/packages/x6-vector/src/dom/style/types.ts deleted file mode 100644 index b095ac62338..00000000000 --- a/packages/x6-vector/src/dom/style/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as CSS from './csstype' - -export { CSS } - -export interface CSSProperties extends CSS.Properties {} - -export type CSSPropertyName = keyof CSSProperties // Exclude diff --git a/packages/x6-vector/src/dom/style/util.ts b/packages/x6-vector/src/dom/style/util.ts deleted file mode 100644 index 751dc752cd6..00000000000 --- a/packages/x6-vector/src/dom/style/util.ts +++ /dev/null @@ -1,405 +0,0 @@ -import { Global } from '../../global' -import { isInDocument } from '../../util/dom' -import { Hook } from './hook' -import { CSSProperties } from './types' - -export type MockedCSSName = 'left' - -export namespace Util { - export function getComputedStyle(node: TElement) { - const view = node.ownerDocument.defaultView || Global.window - return view.getComputedStyle(node) - } - - export function getComputedStyleValue( - node: TElement, - name: string, - styles: Record | CSSStyleDeclaration = getComputedStyle( - node, - ), - ) { - let result: string | number = styles.getPropertyValue - ? (styles as CSSStyleDeclaration).getPropertyValue(name) - : (styles as Record)[name] - - if (result === '' && !isInDocument(node)) { - result = style(node, name) - } - - return result !== undefined ? `${result}` : result - } - - export function isValidNode(node: TElement) { - // Don't set styles on text and comment nodes - if (!node || node.nodeType === 3 || node.nodeType === 8) { - return false - } - - const style = (node as any as HTMLElement).style - if (!style) { - return false - } - - return true - } - - export function isCustomStyleName(styleName: string) { - return styleName.indexOf('--') === 0 - } - - // Convert dashed to camelCase, handle vendor prefixes. - // Used by the css & effects modules. - // Support: IE <=9 - 11+ - // Microsoft forgot to hump their vendor prefix - export function camelCase(str: string) { - const to = (s: string) => - s - .replace(/^-ms-/, 'ms-') - .replace(/-([a-z])/g, (input) => input[1].toUpperCase()) - - if (isCustomStyleName(str)) { - return `--${to(str.substring(2))}` - } - - return to(str) - } - - export function kebabCase(str: string) { - return ( - str - .replace(/([a-z])([A-Z])/g, '$1-$2') - // vendor - .replace(/^([A-Z])/g, '-$1') - .toLowerCase() - .replace(/^ms-/, '-ms-') - ) - } - - export function tryConvertToNumber(value: string | number) { - if (typeof value === 'number') { - return value - } - - const numReg = /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i - return numReg.test(value) ? +value : value - } -} - -export namespace Util { - /** - * CSS properties which accept numbers but are not in units of "px". - */ - export const isUnitlessNumber: Record = { - animationIterationCount: true, - aspectRatio: true, - borderImageOutset: true, - borderImageSlice: true, - borderImageWidth: true, - boxFlex: true, - boxFlexGroup: true, - boxOrdinalGroup: true, - columnCount: true, - columns: true, - flex: true, - flexGrow: true, - flexPositive: true, - flexShrink: true, - flexNegative: true, - flexOrder: true, - gridArea: true, - gridRow: true, - gridRowEnd: true, - gridRowSpan: true, - gridRowStart: true, - gridColumn: true, - gridColumnEnd: true, - gridColumnSpan: true, - gridColumnStart: true, - fontWeight: true, - lineClamp: true, - lineHeight: true, - opacity: true, - order: true, - orphans: true, - tabSize: true, - widows: true, - zIndex: true, - zoom: true, - - // SVG-related properties - fillOpacity: true, - floodOpacity: true, - stopOpacity: true, - strokeDasharray: true, - strokeDashoffset: true, - strokeMiterlimit: true, - strokeOpacity: true, - strokeWidth: true, - } - - /** - * Support style names that may come passed in prefixed by adding permutations - * of vendor prefixes. - */ - const prefixes = ['Webkit', 'ms', 'Moz', 'O'] - - function prefixKey(prefix: string, key: string) { - return prefix + key.charAt(0).toUpperCase() + key.substring(1) - } - - // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an - // infinite loop, because it iterates over the newly added props too. - Object.keys(isUnitlessNumber).forEach((prop) => { - prefixes.forEach((prefix) => { - isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop] - }) - }) - - const style = document.createElement('div').style - const cache: Record = {} - - // Return a vendor-prefixed property or undefined - function vendor(styleName: string) { - for (let i = 0, l = prefixes.length; i < l; i += 1) { - const prefixed = prefixKey(prefixes[i], styleName) - if (prefixed in style) { - return prefixed - } - } - - return styleName - } - - // Return a potentially-mapped vendor prefixed property - export function tryVendor(styleName: string) { - const final = cache[styleName] - if (final) { - return final - } - - if (styleName in style) { - return styleName - } - - const prefixed = vendor(styleName) - cache[styleName] = prefixed - return prefixed - } -} - -export namespace Util { - export function css( - node: TElement, - name: string, - presets?: Record | CSSStyleDeclaration, - ) { - const styleName = camelCase(name) - const isCustom = isCustomStyleName(name) - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if (!isCustom) { - name = tryVendor(styleName) // eslint-disable-line - } - - let val: string | number | undefined - const hook = Hook.get(name) || Hook.get(styleName) - - // If a hook was provided get the computed value from there - if (hook && hook.get) { - val = hook.get(node, true) - } - - // Otherwise, if a way to get the computed value exists, use that - if (val === undefined) { - val = getComputedStyleValue(node, kebabCase(name), presets) - } - - return tryConvertToNumber(val) - } -} - -export namespace Util { - function normalizeValue( - name: string, - value: string | number, - isCustomProperty: boolean, - ) { - const isEmpty = value == null || typeof value === 'boolean' || value === '' - if (isEmpty) { - return '' - } - - if ( - !isCustomProperty && - typeof value === 'number' && - value !== 0 && - !isUnitlessNumber[name] - ) { - return `${value}px` - } - - return `${value}`.trim() - } - - export function style(node: TElement): CSSProperties - export function style( - node: TElement, - name: string, - ): string | number - export function style( - node: TElement, - name: string, - value: string | number, - ): void - export function style( - node: TElement, - name?: string, - value?: string | number, - ) { - if (!isValidNode(node)) { - return typeof name === 'undefined' ? {} : undefined - } - - const styleDeclaration = (node as any as HTMLElement).style - - if (typeof name === 'undefined') { - const result: CSSProperties = {} - styleDeclaration.cssText - .split(/\s*;\s*/) - .filter((str) => str.length > 0) - .forEach((str) => { - const parts = str.split(/\s*:\s*/) - result[camelCase(parts[0]) as MockedCSSName] = style(node, parts[0]) - }) - return result - } - - // Make sure that we're working with the right name - const styleName = camelCase(name) - const isCustom = isCustomStyleName(name) - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if (!isCustom) { - name = tryVendor(styleName) // eslint-disable-line - } - - // Gets hook for the prefixed version, then unprefixed version - const hook = Hook.get(name) || Hook.get(styleName) - - // Setting a value - if (value !== undefined) { - let val = value - // If a hook was provided, use that value, otherwise just set the specified value - let setting = true - if (hook && hook.set) { - const result = hook.set(node, val) - setting = result !== undefined - if (setting) { - val = result! - } - } - - if (setting) { - val = normalizeValue(name, val, isCustom) - if (name === 'float') { - name = 'cssFloat' // eslint-disable-line - } - - if (isCustom) { - styleDeclaration.setProperty(kebabCase(name), val) - } else { - styleDeclaration[name as MockedCSSName] = val - } - } - } else { - let ret: string | number | undefined - // If a hook was provided get the non-computed value from there - if (hook && hook.get) { - ret = hook.get(node, false) - } - - // Otherwise just get the value from the style object - if (ret === undefined) { - ret = styleDeclaration.getPropertyValue(kebabCase(name)) - } - - return tryConvertToNumber(ret) - } - } -} - -export namespace Util { - const cache: WeakMap = new WeakMap() - - export function isHiddenWithinTree(node: TElement) { - const style = (node as any as HTMLElement).style - return ( - style.display === 'none' || - (style.display === '' && css(node, 'display') === 'none') - ) - } - - const defaultDisplayMap: Record = {} - - export function getDefaultDisplay(node: TElement) { - const nodeName = node.nodeName - let display = defaultDisplayMap[nodeName] - if (display) { - return display - } - - const doc = node.ownerDocument || Global.document - const temp = doc.body.appendChild(doc.createElement(nodeName)) - display = css(temp, 'display') as string - - if (temp.parentNode) { - temp.parentNode.removeChild(temp) - } - - if (display === 'none') { - display = 'block' - } - - defaultDisplayMap[nodeName] = display - - return display - } - - export function showHide( - node: TElement, - show: boolean, - ) { - const style = (node as any as HTMLElement).style - if (!style) { - return - } - - const display = style.display - let val: string | undefined - if (show) { - if (display === 'none') { - val = cache.get(node) - if (!val) { - style.display = '' - } - } - - // for cascade-hidden - if (style.display === '' && isHiddenWithinTree(node)) { - val = getDefaultDisplay(node) - } - } else if (display !== 'none') { - val = 'none' - // Remember what we're overwriting - cache.set(node, display) - } - - if (val != null) { - style.display = val - } - } -} diff --git a/packages/x6-vector/src/dom/transform/transform.test.ts b/packages/x6-vector/src/dom/transform/transform.test.ts deleted file mode 100644 index e23616e2715..00000000000 --- a/packages/x6-vector/src/dom/transform/transform.test.ts +++ /dev/null @@ -1,286 +0,0 @@ -import sinon from 'sinon' -import { Matrix } from '../../struct/matrix' -import { Dom } from '../dom' - -describe('Dom', () => { - describe('transform()', () => { - it('should act as full getter with no argument', () => { - const dom = new Dom().attr('transform', 'translate(10, 20) rotate(45)') - const actual = dom.transform() - const expected = new Matrix().rotate(45).translate(10, 20).decompose() - - expect(actual).toEqual(expected) - }) - - it('should return a single transformation value when string was passed', () => { - const dom = new Dom().attr('transform', 'translate(10, 20) rotate(45)') - expect(dom.transform('rotate')).toBe(45) - expect(dom.transform('translateX')).toBe(10) - expect(dom.transform('translateY')).toBe(20) - }) - - it('should set the transformation with an object', () => { - const dom = new Dom().transform({ rotate: 45, translate: [10, 20] }) - expect(dom.transform('rotate')).toBe(45) - expect(dom.transform('translateX')).toBe(10) - expect(dom.transform('translateY')).toBe(20) - }) - - it('should perform a relative transformation', () => { - const dom = new Dom() - .transform({ rotate: 45, translate: [10, 20] }) - .transform({ rotate: 10 }, true) - expect(dom.transform('rotate')).toBeCloseTo(55, 5) // rounding errors - expect(dom.transform('translateX')).toBe(10) - expect(dom.transform('translateY')).toBe(20) - }) - - it('should perform a relative transformation with other matrix', () => { - const dom = new Dom() - .transform({ rotate: 45, translate: [10, 20] }) - .transform({ rotate: 10 }, new Matrix().rotate(30)) - expect(dom.transform('rotate')).toBeCloseTo(40, 5) // rounding errors - expect(dom.transform('translateX')).toBe(0) - expect(dom.transform('translateY')).toBe(0) - }) - - it('should perform a relative transformation with other element', () => { - const ref = new Dom().transform({ rotate: 30 }) - const dom = new Dom() - .transform({ rotate: 45, translate: [10, 20] }) - .transform({ rotate: 10 }, ref) - expect(dom.transform('rotate')).toBeCloseTo(40, 5) // rounding errors - expect(dom.transform('translateX')).toBe(0) - expect(dom.transform('translateY')).toBe(0) - }) - }) - - describe('untransform()', () => { - it('should return itself', () => { - const dom = new Dom() - expect(dom.untransform()).toBe(dom) - }) - - it('should delete the transform attribute', () => { - const dom = new Dom() - expect(dom.untransform().attr('transform') as any).toBe(undefined) - }) - }) - - describe('matrixify()', () => { - it('should get an empty matrix when there is not transformations', () => { - expect(new Dom().matrixify()).toEqual(new Matrix()) - }) - - it('should reduce all transformations of the transform list into one matrix [1]', () => { - const dom = new Dom().attr('transform', 'matrix(1, 0, 1, 1, 0, 1)') - expect(dom.matrixify()).toEqual(new Matrix(1, 0, 1, 1, 0, 1)) - }) - - it('should reduce all transformations of the transform list into one matrix [2]', () => { - const dom = new Dom().attr('transform', 'translate(10, 20) rotate(45)') - expect(dom.matrixify()).toEqual(new Matrix().rotate(45).translate(10, 20)) - }) - - it('should reduce all transformations of the transform list into one matrix [3]', () => { - const dom = new Dom().attr( - 'transform', - 'translate(10, 20) rotate(45) skew(1,2) skewX(10) skewY(20)', - ) - expect(dom.matrixify()).toEqual( - new Matrix() - .skewY(20) - .skewX(10) - .skew(1, 2) - .rotate(45) - .translate(10, 20), - ) - }) - }) - - describe('matrix()', () => { - it('should get transform as a matrix', () => { - expect(new Dom().matrixify()).toEqual(new Matrix()) - const dom = new Dom().transform({ rotate: 45, translate: [10, 20] }) - expect(dom.matrix()).toEqual(new Matrix().rotate(45).translate(10, 20)) - }) - - it('should transform element with matrix', () => { - expect( - new Dom().matrix(new Matrix().translate(10, 20)).attr('transform'), - ).toEqual('matrix(1,0,0,1,10,20)') - - expect(new Dom().matrix(1, 0, 0, 1, 10, 20).attr('transform')).toEqual( - 'matrix(1,0,0,1,10,20)', - ) - }) - }) - - describe('rotate()', () => { - it('should rotate element', () => { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.rotate(1, 2, 3) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ rotate: 1, ox: 2, oy: 3 }, true]) - }) - }) - - describe('skew()', () => { - it('should skew element with no argument', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.skew() - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([ - { skew: undefined, ox: undefined, oy: undefined }, - true, - ]) - }) - - it('should skew element with one argument', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.skew(5) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([ - { skew: 5, ox: undefined, oy: undefined }, - true, - ]) - }) - - it('should skew element with two argument', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.skew(5, 6) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([ - { skew: [5, 6], ox: undefined, oy: undefined }, - true, - ]) - }) - - it('should skew element with three arguments', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.skew(5, 6, 7) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ skew: 5, ox: 6, oy: 7 }, true]) - }) - - it('should skew element with four arguments', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.skew(5, 6, 7, 8) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ skew: [5, 6], ox: 7, oy: 8 }, true]) - }) - }) - - describe('shear()', () => { - it('should shear element', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.shear(1, 2, 3) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ shear: 1, ox: 2, oy: 3 }, true]) - }) - }) - - describe('scale()', () => { - it('should scale element with no argument', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.scale() - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([ - { scale: undefined, ox: undefined, oy: undefined }, - true, - ]) - }) - - it('should scale element with one argument', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.scale(5) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([ - { scale: 5, ox: undefined, oy: undefined }, - true, - ]) - }) - - it('should scale element with two argument', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.scale(5, 6) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([ - { scale: [5, 6], ox: undefined, oy: undefined }, - true, - ]) - }) - - it('should scale element with three arguments', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.scale(5, 6, 7) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ scale: 5, ox: 6, oy: 7 }, true]) - }) - - it('should scale element with four arguments', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.scale(5, 6, 7, 8) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ scale: [5, 6], ox: 7, oy: 8 }, true]) - }) - }) - - describe('translate()', () => { - it('should translate element', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.translate(1, 2) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ translate: [1, 2] }, true]) - }) - }) - - describe('relative()', () => { - it('should relative element', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.relative(1, 2) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ relative: [1, 2] }, true]) - }) - }) - - describe('flip()', () => { - it('should flip element', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.flip('x', 2) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ flip: 'x', origin: 2 }, true]) - }) - - it('should sets flip to "both" when calling without anything', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.flip() - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ flip: 'both', origin: 'center' }, true]) - }) - - it('should set flip to both and origin to number when called with origin only', function () { - const dom = new Dom() - const spy = sinon.spy(dom, 'transform') - dom.flip(5) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([{ flip: 'both', origin: 5 }, true]) - }) - }) -}) diff --git a/packages/x6-vector/src/dom/transform/transform.ts b/packages/x6-vector/src/dom/transform/transform.ts deleted file mode 100644 index bb2b3ae3bcf..00000000000 --- a/packages/x6-vector/src/dom/transform/transform.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Point } from '../../struct/point' -import { Matrix } from '../../struct/matrix' -import { Primer } from '../primer' -import { getTransformOrigin } from './util' - -export class Transform - extends Primer - implements Matrix.Matrixifiable -{ - transform(): Matrix.Transform - transform(type: keyof Matrix.Transform): number - transform( - options: Matrix.TransformOptions, - relative?: boolean | Transform | Matrix.Raw, - ): this - transform( - matrix: Matrix.MatrixLike, - relative?: boolean | Transform | Matrix.Raw, - ): this - transform( - o?: keyof Matrix.Transform | Matrix.MatrixLike | Matrix.TransformOptions, - relative?: boolean | Transform | Matrix.Raw, - ) { - if (o == null || typeof o === 'string') { - const decomposed = new Matrix(this).decompose() - return o == null ? decomposed : decomposed[o] - } - - const m = Matrix.isMatrixLike(o) - ? o - : { - ...o, - origin: getTransformOrigin(o, this), - } - - const cur = - relative === true ? new Matrix(this) : new Matrix(relative || undefined) - const ret = cur.transform(m) - return this.attr('transform', ret.toString()) - } - - untransform() { - return this.attr('transform', null) - } - - matrixify(): Matrix { - const raw = this.attr('transform') || '' - return ( - raw - .split(/\)\s*,?\s*/) - .slice(0, -1) - .map((str) => { - const kv = str.trim().split('(') - return [kv[0], kv[1].split(/[\s,]+/).map((s) => Number.parseFloat(s))] - }) - .reverse() - // merge every transformation into one matrix - .reduce( - ( - matrix, - transform: ['matrix' | 'rotate' | 'scale' | 'translate', number[]], - ) => { - if (transform[0] === 'matrix') { - return matrix.lmultiply( - Matrix.toMatrixLike(transform[1] as Matrix.MatrixArray), - ) - } - return matrix[transform[0]].call(matrix, ...transform[1]) - }, - new Matrix(), - ) - ) - } - - matrix(): Matrix - matrix(m: Matrix | Matrix.MatrixLike): this - matrix(a: number, b: number, c: number, d: number, e: number, f: number): this - matrix( - a?: Matrix | Matrix.MatrixLike | number, - b?: number, - c?: number, - d?: number, - e?: number, - f?: number, - ) { - if (a == null) { - return new Matrix(this) - } - - const m = - typeof a === 'number' - ? new Matrix( - a, - b as number, - c as number, - d as number, - e as number, - f as number, - ) - : new Matrix(a) - - return this.attr('transform', m.toString()) - } - - rotate(angle: number): this - rotate(angle: number, cx: number, cy: number): this - rotate(angle: number, cx?: number, cy?: number) { - return this.transform({ rotate: angle, ox: cx, oy: cy }, true) - } - - skew(): this - skew(s: number): this - skew(x: number, y: number): this - skew(s: number, cx: number, cy: number): this - skew(x: number, y: number, cx: number, cy: number): this - skew(x?: number, y?: number, cx?: number, cy?: number) { - return arguments.length === 1 || arguments.length === 3 - ? this.transform({ skew: x, ox: y, oy: cx }, true) - : this.transform( - { - skew: typeof x === 'undefined' ? undefined : [x, y as number], - ox: cx, - oy: cy, - }, - true, - ) - } - - shear(lam: number): this - shear(lam: number, cx: number, cy: number): this - shear(lam: number, cx?: number, cy?: number) { - return this.transform({ shear: lam, ox: cx, oy: cy }, true) - } - - scale(): this - scale(s: number): this - scale(x: number, y: number): this - scale(x: number, y: number, cx: number, cy: number): this - scale(s: number, cx: number, cy: number): this - scale(x?: number, y?: number, cx?: number, cy?: number) { - return arguments.length === 1 || arguments.length === 3 - ? this.transform({ scale: x, ox: y, oy: cx }, true) - : this.transform( - { - scale: typeof x === 'undefined' ? undefined : [x, y as number], - ox: cx, - oy: cy, - }, - true, - ) - } - - translate(x: number, y: number) { - return this.transform({ translate: [x, y] }, true) - } - - relative(x: number, y: number) { - return this.transform({ relative: [x, y] }, true) - } - - flip(origin?: number | [number, number] | Point.PointLike): this - flip( - direction: 'both' | 'x' | 'y', - origin?: number | [number, number] | Point.PointLike | 'center', - ): this - flip( - direction: - | 'both' - | 'x' - | 'y' - | number - | [number, number] - | Point.PointLike = 'both', - origin: number | [number, number] | Point.PointLike | 'center' = 'center', - ) { - if (typeof direction !== 'string') { - origin = direction // eslint-disable-line - direction = 'both' // eslint-disable-line - } - - return this.transform({ origin, flip: direction }, true) - } -} diff --git a/packages/x6-vector/src/dom/transform/util.ts b/packages/x6-vector/src/dom/transform/util.ts deleted file mode 100644 index 74b510c6397..00000000000 --- a/packages/x6-vector/src/dom/transform/util.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Global } from '../../global' -import type { Matrix } from '../../struct/matrix' -import type { Vector } from '../../vector/vector/vector' -import type { Transform } from './transform' - -export function getTransformOrigin( - o: Matrix.TransformOptions, - t: Transform, -): [number, number] { - // First check if origin is in ox or originX - let ox = o.ox != null ? o.ox : o.originX != null ? o.originX : 'center' - let oy = o.oy != null ? o.oy : o.originY != null ? o.originY : 'center' - - // Then check if origin was used and overwrite in that case - const { origin } = o - if (origin != null) { - ;[ox, oy] = Array.isArray(origin) - ? origin - : typeof origin === 'object' - ? [origin.x, origin.y] - : [origin, origin] - } - - // Make sure to only call bbox when actually needed - if (typeof ox === 'string' || typeof oy === 'string') { - const node = t.node - const { height, width, x, y } = - node instanceof Global.window.SVGElement - ? (t as any as Vector).bbox() - : (node as HTMLElement).getBoundingClientRect() - - // And only overwrite if string was passed for this specific axis - if (typeof ox === 'string') { - ox = ox.includes('left') - ? x - : ox.includes('right') - ? x + width - : x + width / 2 - } - - if (typeof oy === 'string') { - oy = oy.includes('top') - ? y - : oy.includes('bottom') - ? y + height - : y + height / 2 - } - } - - return [ox, oy] -} diff --git a/packages/x6-vector/src/dom/types/attributes-core.ts b/packages/x6-vector/src/dom/types/attributes-core.ts deleted file mode 100644 index 1f5a265d631..00000000000 --- a/packages/x6-vector/src/dom/types/attributes-core.ts +++ /dev/null @@ -1,233 +0,0 @@ -export { CSSProperties } from '../style/types' - -export type Booleanish = boolean | 'true' | 'false' - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export interface CustomAttributes { - 'vector:data'?: string -} - -// All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/ -export interface AriaAttributes { - /** Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application. */ - 'aria-activedescendant'?: string - /** Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute. */ - 'aria-atomic'?: boolean | 'false' | 'true' - /** - * Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be - * presented if they are made. - */ - 'aria-autocomplete'?: 'none' | 'inline' | 'list' | 'both' - /** Indicates an element is being modified and that assistive technologies MAY want to wait until the modifications are complete before exposing them to the user. */ - 'aria-busy'?: boolean | 'false' | 'true' - /** - * Indicates the current "checked" state of checkboxes, radio buttons, and other widgets. - * @see aria-pressed @see aria-selected. - */ - 'aria-checked'?: boolean | 'false' | 'mixed' | 'true' - /** - * Defines the total number of columns in a table, grid, or treegrid. - * @see aria-colindex. - */ - 'aria-colcount'?: number - /** - * Defines an element's column index or position with respect to the total number of columns within a table, grid, or treegrid. - * @see aria-colcount @see aria-colspan. - */ - 'aria-colindex'?: number - /** - * Defines the number of columns spanned by a cell or gridcell within a table, grid, or treegrid. - * @see aria-colindex @see aria-rowspan. - */ - 'aria-colspan'?: number - /** - * Identifies the element (or elements) whose contents or presence are controlled by the current element. - * @see aria-owns. - */ - 'aria-controls'?: string - /** Indicates the element that represents the current item within a container or set of related elements. */ - 'aria-current'?: - | boolean - | 'false' - | 'true' - | 'page' - | 'step' - | 'location' - | 'date' - | 'time' - /** - * Identifies the element (or elements) that describes the object. - * @see aria-labelledby - */ - 'aria-describedby'?: string - /** - * Identifies the element that provides a detailed, extended description for the object. - * @see aria-describedby. - */ - 'aria-details'?: string - /** - * Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable. - * @see aria-hidden @see aria-readonly. - */ - 'aria-disabled'?: boolean | 'false' | 'true' - /** - * Indicates what functions can be performed when a dragged object is released on the drop target. - * @deprecated in ARIA 1.1 - */ - 'aria-dropeffect'?: 'none' | 'copy' | 'execute' | 'link' | 'move' | 'popup' - /** - * Identifies the element that provides an error message for the object. - * @see aria-invalid @see aria-describedby. - */ - 'aria-errormessage'?: string - /** Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed. */ - 'aria-expanded'?: boolean | 'false' | 'true' - /** - * Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion, - * allows assistive technology to override the general default of reading in document source order. - */ - 'aria-flowto'?: string - /** - * Indicates an element's "grabbed" state in a drag-and-drop operation. - * @deprecated in ARIA 1.1 - */ - 'aria-grabbed'?: boolean | 'false' | 'true' - /** Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element. */ - 'aria-haspopup'?: - | boolean - | 'false' - | 'true' - | 'menu' - | 'listbox' - | 'tree' - | 'grid' - | 'dialog' - /** - * Indicates whether the element is exposed to an accessibility API. - * @see aria-disabled. - */ - 'aria-hidden'?: boolean | 'false' | 'true' - /** - * Indicates the entered value does not conform to the format expected by the application. - * @see aria-errormessage. - */ - 'aria-invalid'?: boolean | 'false' | 'true' | 'grammar' | 'spelling' - /** Indicates keyboard shortcuts that an author has implemented to activate or give focus to an element. */ - 'aria-keyshortcuts'?: string - /** - * Defines a string value that labels the current element. - * @see aria-labelledby. - */ - 'aria-label'?: string - /** - * Identifies the element (or elements) that labels the current element. - * @see aria-describedby. - */ - 'aria-labelledby'?: string - /** Defines the hierarchical level of an element within a structure. */ - 'aria-level'?: number - /** Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region. */ - 'aria-live'?: 'off' | 'assertive' | 'polite' - /** Indicates whether an element is modal when displayed. */ - 'aria-modal'?: boolean | 'false' | 'true' - /** Indicates whether a text box accepts multiple lines of input or only a single line. */ - 'aria-multiline'?: boolean | 'false' | 'true' - /** Indicates that the user may select more than one item from the current selectable descendants. */ - 'aria-multiselectable'?: boolean | 'false' | 'true' - /** Indicates whether the element's orientation is horizontal, vertical, or unknown/ambiguous. */ - 'aria-orientation'?: 'horizontal' | 'vertical' - /** - * Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship - * between DOM elements where the DOM hierarchy cannot be used to represent the relationship. - * @see aria-controls. - */ - 'aria-owns'?: string - /** - * Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value. - * A hint could be a sample value or a brief description of the expected format. - */ - 'aria-placeholder'?: string - /** - * Defines an element's number or position in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM. - * @see aria-setsize. - */ - 'aria-posinset'?: number - /** - * Indicates the current "pressed" state of toggle buttons. - * @see aria-checked @see aria-selected. - */ - 'aria-pressed'?: boolean | 'false' | 'mixed' | 'true' - /** - * Indicates that the element is not editable, but is otherwise operable. - * @see aria-disabled. - */ - 'aria-readonly'?: boolean | 'false' | 'true' - /** - * Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified. - * @see aria-atomic. - */ - 'aria-relevant'?: - | 'additions' - | 'additions removals' - | 'additions text' - | 'all' - | 'removals' - | 'removals additions' - | 'removals text' - | 'text' - | 'text additions' - | 'text removals' - /** Indicates that user input is required on the element before a form may be submitted. */ - 'aria-required'?: boolean | 'false' | 'true' - /** Defines a human-readable, author-localized description for the role of an element. */ - 'aria-roledescription'?: string - /** - * Defines the total number of rows in a table, grid, or treegrid. - * @see aria-rowindex. - */ - 'aria-rowcount'?: number - /** - * Defines an element's row index or position with respect to the total number of rows within a table, grid, or treegrid. - * @see aria-rowcount @see aria-rowspan. - */ - 'aria-rowindex'?: number - /** - * Defines the number of rows spanned by a cell or gridcell within a table, grid, or treegrid. - * @see aria-rowindex @see aria-colspan. - */ - 'aria-rowspan'?: number - /** - * Indicates the current "selected" state of various widgets. - * @see aria-checked @see aria-pressed. - */ - 'aria-selected'?: boolean | 'false' | 'true' - /** - * Defines the number of items in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM. - * @see aria-posinset. - */ - 'aria-setsize'?: number - /** Indicates if items in a table or grid are sorted in ascending or descending order. */ - 'aria-sort'?: 'none' | 'ascending' | 'descending' | 'other' - /** Defines the maximum allowed value for a range widget. */ - 'aria-valuemax'?: number - /** Defines the minimum allowed value for a range widget. */ - 'aria-valuemin'?: number - /** - * Defines the current value for a range widget. - * @see aria-valuetext. - */ - 'aria-valuenow'?: number - /** Defines the human readable text alternative of aria-valuenow for a range widget. */ - 'aria-valuetext'?: string -} - -export type HTMLAttributeReferrerPolicy = - | '' - | 'no-referrer' - | 'no-referrer-when-downgrade' - | 'origin' - | 'origin-when-cross-origin' - | 'same-origin' - | 'strict-origin' - | 'strict-origin-when-cross-origin' - | 'unsafe-url' diff --git a/packages/x6-vector/src/dom/types/attributes-map.ts b/packages/x6-vector/src/dom/types/attributes-map.ts deleted file mode 100644 index bafb038fd71..00000000000 --- a/packages/x6-vector/src/dom/types/attributes-map.ts +++ /dev/null @@ -1,659 +0,0 @@ -import { - Booleanish, - CSSProperties, - AriaAttributes, - CustomAttributes, - HTMLAttributeReferrerPolicy, -} from './attributes-core' - -interface HTMLAttributes extends AriaAttributes, CustomAttributes { - // Standard HTML Attributes - accessKey?: string - class?: string - contentEditable?: Booleanish | 'inherit' - contextMenu?: string - dir?: string - draggable?: Booleanish - hidden?: boolean - id?: string - lang?: string - placeholder?: string - slot?: string - spellCheck?: Booleanish - style?: CSSProperties - tabIndex?: number - title?: string - translate?: 'yes' | 'no' - - // Unknown - radioGroup?: string // , - - // WAI-ARIA - role?: string - - // RDFa Attributes - about?: string - datatype?: string - inlist?: any - prefix?: string - property?: string - resource?: string - typeof?: string - vocab?: string - - // Non-standard Attributes - autoCapitalize?: string - autoCorrect?: string - autoSave?: string - color?: string - itemProp?: string - itemScope?: boolean - itemType?: string - itemID?: string - itemRef?: string - results?: number - security?: string - unselectable?: 'on' | 'off' - - // Living Standard - /** - * Hints at the type of data that might be entered by the user while editing the element or its contents - * @see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute - */ - inputMode?: - | 'none' - | 'text' - | 'tel' - | 'url' - | 'email' - | 'numeric' - | 'decimal' - | 'search' - /** - * Specify that a standard HTML element should behave like a defined custom built-in element - * @see https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is - */ - is?: string -} - -interface HTMLAnchorAttributes extends HTMLAttributes { - download?: any - href?: string - hrefLang?: string - media?: string - ping?: string - rel?: string - target?: string - type?: string - referrerPolicy?: HTMLAttributeReferrerPolicy -} - -interface HTMLAudioAttributes extends MediaHTMLAttributes {} - -interface HTMLAreaAttributes extends HTMLAttributes { - alt?: string - coords?: string - download?: any - href?: string - hrefLang?: string - media?: string - referrerPolicy?: HTMLAttributeReferrerPolicy - rel?: string - shape?: string - target?: string -} - -interface HTMLBaseAttributes extends HTMLAttributes { - href?: string - target?: string -} - -interface HTMLBlockquoteAttributes extends HTMLAttributes { - cite?: string -} - -interface HTMLButtonAttributes extends HTMLAttributes { - autoFocus?: boolean - disabled?: boolean - form?: string - formAction?: string - formEncType?: string - formMethod?: string - formNoValidate?: boolean - formTarget?: string - name?: string - type?: 'submit' | 'reset' | 'button' - value?: string | ReadonlyArray | number -} - -interface HTMLCanvasAttributes extends HTMLAttributes { - height?: number | string - width?: number | string -} - -interface HTMLColAttributes extends HTMLAttributes { - span?: number - width?: number | string -} - -interface HTMLColgroupAttributes extends HTMLAttributes { - span?: number -} - -interface HTMLDataAttributes extends HTMLAttributes { - value?: string | ReadonlyArray | number -} - -interface HTMLDetailsAttributes extends HTMLAttributes { - open?: boolean -} - -interface HTMLDelAttributes extends HTMLAttributes { - cite?: string - dateTime?: string -} - -interface HTMLDialogAttributes extends HTMLAttributes { - open?: boolean -} - -interface HTMLEmbedAttributes extends HTMLAttributes { - height?: number | string - src?: string - type?: string - width?: number | string -} - -interface HTMLFieldsetAttributes extends HTMLAttributes { - disabled?: boolean - form?: string - name?: string -} - -interface HTMLFormAttributes extends HTMLAttributes { - acceptCharset?: string - action?: string - autoComplete?: string - encType?: string - method?: string - name?: string - noValidate?: boolean - target?: string -} - -interface HTMLHtmlAttributes extends HTMLAttributes { - manifest?: string -} - -interface HTMLIframeAttributes extends HTMLAttributes { - allow?: string - allowFullScreen?: boolean - allowTransparency?: boolean - /** @deprecated */ - frameBorder?: number | string - height?: number | string - loading?: 'eager' | 'lazy' - /** @deprecated */ - marginHeight?: number - /** @deprecated */ - marginWidth?: number - name?: string - referrerPolicy?: HTMLAttributeReferrerPolicy - sandbox?: string - /** @deprecated */ - scrolling?: string - seamless?: boolean - src?: string - srcDoc?: string - width?: number | string -} - -interface HTMLImgAttributes extends HTMLAttributes { - alt?: string - crossOrigin?: 'anonymous' | 'use-credentials' | '' - decoding?: 'async' | 'auto' | 'sync' - height?: number | string - loading?: 'eager' | 'lazy' - referrerPolicy?: HTMLAttributeReferrerPolicy - sizes?: string - src?: string - srcSet?: string - useMap?: string - width?: number | string -} - -interface HTMLInsAttributes extends HTMLAttributes { - cite?: string - dateTime?: string -} - -interface HTMLInputAttributes extends HTMLAttributes { - accept?: string - alt?: string - autoComplete?: string - autoFocus?: boolean - capture?: boolean | string // https://www.w3.org/TR/html-media-capture/#the-capture-attribute - checked?: boolean - crossOrigin?: string - disabled?: boolean - enterKeyHint?: - | 'enter' - | 'done' - | 'go' - | 'next' - | 'previous' - | 'search' - | 'send' - form?: string - formAction?: string - formEncType?: string - formMethod?: string - formNoValidate?: boolean - formTarget?: string - height?: number | string - list?: string - max?: number | string - maxLength?: number - min?: number | string - minLength?: number - multiple?: boolean - name?: string - pattern?: string - placeholder?: string - readOnly?: boolean - required?: boolean - size?: number - src?: string - step?: number | string - type?: string - value?: string | ReadonlyArray | number - width?: number | string -} - -interface HTMLKeygenAttributes extends HTMLAttributes { - autoFocus?: boolean - challenge?: string - disabled?: boolean - form?: string - keyType?: string - keyParams?: string - name?: string -} - -interface HTMLLabelAttributes extends HTMLAttributes { - form?: string - for?: string -} - -interface HTMLLiAttributes extends HTMLAttributes { - value?: string | ReadonlyArray | number -} - -interface HTMLLinkAttributes extends HTMLAttributes { - as?: string - crossOrigin?: string - href?: string - hrefLang?: string - integrity?: string - media?: string - referrerPolicy?: HTMLAttributeReferrerPolicy - rel?: string - sizes?: string - type?: string - charSet?: string -} - -interface HTMLMapAttributes extends HTMLAttributes { - name?: string -} - -interface HTMLMenuAttributes extends HTMLAttributes { - type?: string -} - -interface MediaHTMLAttributes extends HTMLAttributes { - autoPlay?: boolean - controls?: boolean - controlsList?: string - crossOrigin?: string - loop?: boolean - mediaGroup?: string - muted?: boolean - playsInline?: boolean - preload?: string - src?: string -} - -interface HTMLMetaAttributes extends HTMLAttributes { - charSet?: string - content?: string - httpEquiv?: string - name?: string -} - -interface HTMLMeterAttributes extends HTMLAttributes { - form?: string - high?: number - low?: number - max?: number | string - min?: number | string - optimum?: number - value?: string | ReadonlyArray | number -} - -interface HTMLQuoteAttributes extends HTMLAttributes { - cite?: string -} - -interface HTMLObjectAttributes extends HTMLAttributes { - classID?: string - data?: string - form?: string - height?: number | string - name?: string - type?: string - useMap?: string - width?: number | string - wmode?: string -} - -interface HTMLOlAttributes extends HTMLAttributes { - reversed?: boolean - start?: number - type?: '1' | 'a' | 'A' | 'i' | 'I' -} - -interface HTMLOptgroupAttributes extends HTMLAttributes { - disabled?: boolean - label?: string -} - -interface HTMLOptionAttributes extends HTMLAttributes { - disabled?: boolean - label?: string - selected?: boolean - value?: string | ReadonlyArray | number -} - -interface HTMLOutputAttributes extends HTMLAttributes { - form?: string - for?: string - name?: string -} - -interface HTMLParamAttributes extends HTMLAttributes { - name?: string - value?: string | ReadonlyArray | number -} - -interface HTMLProgressAttributes extends HTMLAttributes { - max?: number | string - value?: string | ReadonlyArray | number -} - -interface HTMLSlotAttributes extends HTMLAttributes { - name?: string -} - -interface HTMLScriptAttributes extends HTMLAttributes { - async?: boolean - /** @deprecated */ - charSet?: string - crossOrigin?: string - defer?: boolean - integrity?: string - noModule?: boolean - nonce?: string - referrerPolicy?: HTMLAttributeReferrerPolicy - src?: string - type?: string -} - -interface HTMLSelectAttributes extends HTMLAttributes { - autoComplete?: string - autoFocus?: boolean - disabled?: boolean - form?: string - multiple?: boolean - name?: string - required?: boolean - size?: number - value?: string | ReadonlyArray | number -} - -interface HTMLSourceAttributes extends HTMLAttributes { - media?: string - sizes?: string - src?: string - srcSet?: string - type?: string -} - -interface HTMLStyleAttributes extends HTMLAttributes { - media?: string - nonce?: string - scoped?: boolean - type?: string -} - -interface HTMLTableAttributes extends HTMLAttributes { - cellPadding?: number | string - cellSpacing?: number | string - summary?: string - width?: number | string -} - -interface HTMLTextareaAttributes extends HTMLAttributes { - autoComplete?: string - autoFocus?: boolean - cols?: number - dirName?: string - disabled?: boolean - form?: string - maxLength?: number - minLength?: number - name?: string - placeholder?: string - readOnly?: boolean - required?: boolean - rows?: number - value?: string | ReadonlyArray | number - wrap?: string -} - -interface HTMLTdAttributes extends HTMLAttributes { - align?: 'left' | 'center' | 'right' | 'justify' | 'char' - colSpan?: number - headers?: string - rowSpan?: number - scope?: string - abbr?: string - height?: number | string - width?: number | string - valign?: 'top' | 'middle' | 'bottom' | 'baseline' -} - -interface HTMLThAttributes extends HTMLAttributes { - align?: 'left' | 'center' | 'right' | 'justify' | 'char' - colSpan?: number - headers?: string - rowSpan?: number - scope?: string - abbr?: string -} - -interface HTMLTimeAttributes extends HTMLAttributes { - dateTime?: string -} - -interface HTMLTrackAttributes extends HTMLAttributes { - default?: boolean - kind?: string - label?: string - src?: string - srcLang?: string -} - -interface HTMLVideoAttributes extends MediaHTMLAttributes { - height?: number | string - playsInline?: boolean - poster?: string - width?: number | string - disablePictureInPicture?: boolean - disableRemotePlayback?: boolean -} - -interface HTMLWebViewAttributes extends HTMLAttributes { - allowFullScreen?: boolean - allowpopups?: boolean - autoFocus?: boolean - autosize?: boolean - blinkfeatures?: string - disableblinkfeatures?: string - disableguestresize?: boolean - disablewebsecurity?: boolean - guestinstance?: string - httpreferrer?: string - nodeintegration?: boolean - partition?: string - plugins?: boolean - preload?: string - src?: string - useragent?: string - webpreferences?: string -} - -export interface HTMLAttributesTagNameMap { - a: HTMLAnchorAttributes - abbr: HTMLAttributes - address: HTMLAttributes - applet: HTMLAttributes - area: HTMLAreaAttributes - article: HTMLAttributes - aside: HTMLAttributes - audio: HTMLAudioAttributes - b: HTMLAttributes - base: HTMLBaseAttributes - basefont: HTMLAttributes - bdi: HTMLAttributes - bdo: HTMLAttributes - big: HTMLAttributes - blockquote: HTMLBlockquoteAttributes - body: HTMLAttributes - br: HTMLAttributes - button: HTMLButtonAttributes - canvas: HTMLCanvasAttributes - caption: HTMLAttributes - cite: HTMLAttributes - code: HTMLAttributes - col: HTMLColAttributes - colgroup: HTMLColgroupAttributes - data: HTMLDataAttributes - datalist: HTMLAttributes - dd: HTMLAttributes - del: HTMLDelAttributes - details: HTMLDetailsAttributes - dfn: HTMLAttributes - dialog: HTMLDialogAttributes - dir: HTMLAttributes - DIV: HTMLAttributes - div: HTMLAttributes - dl: HTMLAttributes - dt: HTMLAttributes - em: HTMLAttributes - embed: HTMLEmbedAttributes - fieldset: HTMLFieldsetAttributes - figcaption: HTMLAttributes - figure: HTMLAttributes - font: HTMLAttributes - footer: HTMLAttributes - form: HTMLFormAttributes - frame: HTMLAttributes - frameset: HTMLAttributes - h1: HTMLAttributes - h2: HTMLAttributes - h3: HTMLAttributes - h4: HTMLAttributes - h5: HTMLAttributes - h6: HTMLAttributes - head: HTMLAttributes - header: HTMLAttributes - hgroup: HTMLAttributes - hr: HTMLAttributes - html: HTMLHtmlAttributes - i: HTMLAttributes - iframe: HTMLIframeAttributes - img: HTMLImgAttributes - input: HTMLInputAttributes - ins: HTMLInsAttributes - kbd: HTMLAttributes - keygen: HTMLKeygenAttributes - label: HTMLLabelAttributes - legend: HTMLAttributes - li: HTMLLiAttributes - link: HTMLLinkAttributes - main: HTMLAttributes - map: HTMLMapAttributes - mark: HTMLAttributes - menu: HTMLMenuAttributes - menuitem: HTMLAttributes - meta: HTMLMetaAttributes - meter: HTMLMeterAttributes - nav: HTMLAttributes - noindex: HTMLAttributes - noscript: HTMLAttributes - object: HTMLObjectAttributes - ol: HTMLOlAttributes - optgroup: HTMLOptgroupAttributes - option: HTMLOptionAttributes - output: HTMLOutputAttributes - p: HTMLAttributes - param: HTMLParamAttributes - picture: HTMLAttributes - pre: HTMLAttributes - progress: HTMLProgressAttributes - q: HTMLQuoteAttributes - rp: HTMLAttributes - rt: HTMLAttributes - ruby: HTMLAttributes - s: HTMLAttributes - samp: HTMLAttributes - script: HTMLScriptAttributes - section: HTMLAttributes - select: HTMLSelectAttributes - slot: HTMLSlotAttributes - small: HTMLAttributes - source: HTMLSourceAttributes - span: HTMLAttributes - strong: HTMLAttributes - style: HTMLStyleAttributes - sub: HTMLAttributes - summary: HTMLAttributes - sup: HTMLAttributes - table: HTMLTableAttributes - tbody: HTMLAttributes - td: HTMLTdAttributes - template: HTMLAttributes - textarea: HTMLTextareaAttributes - tfoot: HTMLAttributes - th: HTMLThAttributes - thead: HTMLAttributes - time: HTMLTimeAttributes - title: HTMLAttributes - tr: HTMLAttributes - track: HTMLTrackAttributes - u: HTMLAttributes - ul: HTMLAttributes - var: HTMLAttributes - video: HTMLVideoAttributes - wbr: HTMLAttributes - webview: HTMLWebViewAttributes -} - -export type HTMLAttributesMap = { - [K in keyof HTMLElementTagNameMap]-?: T extends HTMLElementTagNameMap[K] - ? K extends keyof HTMLAttributesTagNameMap - ? HTMLAttributesTagNameMap[K] - : never - : never -}[keyof HTMLElementTagNameMap] diff --git a/packages/x6-vector/src/dom/types/index.ts b/packages/x6-vector/src/dom/types/index.ts deleted file mode 100644 index 59b3e5fa0cb..00000000000 --- a/packages/x6-vector/src/dom/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './attributes-core' -export * from './attributes-map' diff --git a/packages/x6-vector/src/global/env.ts b/packages/x6-vector/src/global/env.ts deleted file mode 100644 index e84060ae75d..00000000000 --- a/packages/x6-vector/src/global/env.ts +++ /dev/null @@ -1,3 +0,0 @@ -export namespace Env { - export const isDev = process.env.NODE_ENV === 'development' -} diff --git a/packages/x6-vector/src/global/global.test.ts b/packages/x6-vector/src/global/global.test.ts deleted file mode 100644 index 1ec2fa89117..00000000000 --- a/packages/x6-vector/src/global/global.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Global } from './global' - -describe('Global', () => { - describe('registerWindow()', () => { - it('should set a new window as global', () => { - Global.saveWindow() - const win = {} as any - const doc = {} as any - Global.registerWindow(win, doc) - expect(Global.window).toBe(win) - expect(Global.document).toBe(doc) - Global.restoreWindow() - }) - }) - - describe('withWindow()', () => { - it('should run a function in the specified window context', () => { - const win = { foo: 'bar', document: {} } as any - const oldWindow = Global.window - expect(Global.window).not.toBe(win) - Global.withWindow(win, () => { - expect(Global.window).toEqual(win) - expect(Global.document).toEqual(win.document) - }) - expect(Global.window).toBe(oldWindow) - }) - }) - - describe('getWindow()', () => { - it('should return the registered window', () => { - expect(Global.getWindow()).toBe(Global.window) - }) - }) -}) diff --git a/packages/x6-vector/src/global/global.ts b/packages/x6-vector/src/global/global.ts deleted file mode 100644 index 1986485e6dd..00000000000 --- a/packages/x6-vector/src/global/global.ts +++ /dev/null @@ -1,51 +0,0 @@ -const win = window -const doc = document - -export namespace Global { - export type WindowType = typeof win - export type DocumentType = typeof doc - - export let window = win // eslint-disable-line - export let document = doc // eslint-disable-line - - let saved: { - window: WindowType - document: DocumentType - } - - export function registerWindow( - win: WindowType, - doc: DocumentType = win.document, - ) { - window = win - document = doc - } - - export function getWindow() { - return window - } - - export function saveWindow() { - saved = { - window, - document, - } - } - - export function restoreWindow() { - if (saved) { - window = saved.window - document = saved.document - } - } - - export function withWindow( - w: WindowType, - callback: (win: WindowType, doc: DocumentType) => any, - ) { - saveWindow() - registerWindow(w, w.document) - callback(w, w.document) - restoreWindow() - } -} diff --git a/packages/x6-vector/src/global/index.ts b/packages/x6-vector/src/global/index.ts deleted file mode 100644 index 51f6153b749..00000000000 --- a/packages/x6-vector/src/global/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './global' diff --git a/packages/x6-vector/src/global/version.test.ts b/packages/x6-vector/src/global/version.test.ts deleted file mode 100644 index 4ed8cd95324..00000000000 --- a/packages/x6-vector/src/global/version.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { version } from './version' - -describe('version', () => { - it('should match the `version` field of package.json', () => { - // eslint-disable-next-line - const expected = require('../../package.json').version - expect(version).toBe(expected) - }) -}) diff --git a/packages/x6-vector/src/global/version.ts b/packages/x6-vector/src/global/version.ts deleted file mode 100644 index e5c8f30a55b..00000000000 --- a/packages/x6-vector/src/global/version.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable */ - -/** - * Auto generated version file, do not modify it! - */ -const version = '1.3.0' -export { version } diff --git a/packages/x6-vector/src/index.ts b/packages/x6-vector/src/index.ts deleted file mode 100644 index 7d7c163e849..00000000000 --- a/packages/x6-vector/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './global/version' -export * from './dom' -export * from './vector' diff --git a/packages/x6-vector/src/struct/box.test.ts b/packages/x6-vector/src/struct/box.test.ts deleted file mode 100644 index 5d46cdf3d97..00000000000 --- a/packages/x6-vector/src/struct/box.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { Box } from './box' -import { Matrix } from './matrix' - -const { objectContaining } = jasmine - -describe('Box', () => { - describe('static methods', () => { - describe('isNull', () => { - it('should return true if x, y, with and height is 0', () => { - expect(Box.isNull({ x: 0, y: 0, width: 0, height: 0 })).toBe(true) - }) - - it('should returns false if one or more of x, y, with and height is not 0', () => { - expect(Box.isNull({ x: 0, y: 0, width: 0, height: 1 })).toBe(false) - expect(Box.isNull({ x: 0, y: 1, width: 0, height: 1 })).toBe(false) - }) - }) - }) - - describe('constructor()', () => { - it('should create a new Box with empty args', () => { - const box = new Box() - expect(box).toBeInstanceOf(Box) - expect(box).toEqual( - objectContaining({ - width: 0, - height: 0, - x: 0, - y: 0, - w: 0, - h: 0, - cx: 0, - cy: 0, - x2: 0, - y2: 0, - }), - ) - }) - - it('should create a new Box with x y width and height', () => { - expect(new Box(1, 2, 3, 4).toArray()).toEqual([1, 2, 3, 4]) - }) - - it('should create a new Box with array input', () => { - expect(new Box([1, 2, 3, 4]).toArray()).toEqual([1, 2, 3, 4]) - }) - - it('should create a new Box with string input', () => { - expect(new Box('1,2,3,4').toArray()).toEqual([1, 2, 3, 4]) - }) - - it('should create a new box from parsed string with exponential values', () => { - expect(new Box('-1.12e1 1e-2 +2e2 +.3e+4').toArray()).toEqual([ - -11.2, 0.01, 200, 3000, - ]) - }) - - it('should create a new box with object input', () => { - expect(new Box({ x: 1, y: 2, width: 3, height: 4 }).toArray()).toEqual([ - 1, 2, 3, 4, - ]) - }) - - it('should calculate all derived values correctly', () => { - expect(new Box(2, 4, 6, 8)).toEqual( - objectContaining({ - cx: 5, - cy: 8, - x2: 8, - y2: 12, - w: 6, - h: 8, - }), - ) - }) - - it('should create a new box with input with left instead of x and top instead of y', () => { - expect( - new Box({ left: 1, top: 2, width: 3, height: 4 }).toArray(), - ).toEqual([1, 2, 3, 4]) - }) - }) - - describe('merge()', () => { - it('should merge various bounding boxes', () => { - const box1 = new Box(50, 50, 100, 100) - const box2 = new Box(300, 400, 100, 100) - const box3 = new Box(500, 100, 100, 100) - const merged = box1.merge(box2).merge(box3) - - expect(merged.toArray()).toEqual([50, 50, 550, 450]) - }) - - it('should return a new instance', () => { - const box1 = new Box(50, 50, 100, 100) - const box2 = new Box(300, 400, 100, 100) - const merged = box1.merge(box2) - - expect(merged).toBeInstanceOf(Box) - expect(merged === box1).toBe(false) - expect(merged === box2).toBe(false) - }) - }) - - describe('transform()', () => { - it('should transform the box with given matrix', () => { - const box1 = new Box(50, 50, 100, 100).transform( - new Matrix(1, 0, 0, 1, 20, 20), - ) - const box2 = new Box(50, 50, 100, 100).transform( - new Matrix(2, 0, 0, 2, 0, 0), - ) - const box3 = new Box(-200, -200, 100, 100).transform( - new Matrix(1, 0, 0, 1, -20, -20), - ) - - expect(box1.toArray()).toEqual([70, 70, 100, 100]) - expect(box2.toArray()).toEqual([100, 100, 200, 200]) - expect(box3.toArray()).toEqual([-220, -220, 100, 100]) - }) - - it('should work with matrix like input', () => { - const box1 = new Box(50, 50, 100, 100).transform( - new Matrix(1, 0, 0, 1, 20, 20).toArray() as Matrix.MatrixArray, - ) - const box2 = new Box(50, 50, 100, 100).transform( - new Matrix(2, 0, 0, 2, 0, 0).toArray() as Matrix.MatrixArray, - ) - const box3 = new Box(-200, -200, 100, 100).transform( - new Matrix(1, 0, 0, 1, -20, -20).toArray() as Matrix.MatrixArray, - ) - - expect(box1.toArray()).toEqual([70, 70, 100, 100]) - expect(box2.toArray()).toEqual([100, 100, 200, 200]) - expect(box3.toArray()).toEqual([-220, -220, 100, 100]) - }) - }) - - describe('isNull()', () => { - it('should check if the box consists of only zeros', () => { - expect(new Box().isNull()).toBe(true) - expect(new Box(1, 2, 3, 4).isNull()).toBe(false) - }) - }) - - describe('toJSON()', () => { - it('should create an object representation of Box', () => { - const obj1 = new Box().toJSON() - expect(obj1.x).toBe(0) - expect(obj1.y).toBe(0) - expect(obj1.width).toBe(0) - expect(obj1.height).toBe(0) - - const obj2 = new Box(1, 2, 3, 4).toJSON() - expect(obj2.x).toBe(1) - expect(obj2.y).toBe(2) - expect(obj2.width).toBe(3) - expect(obj2.height).toBe(4) - }) - }) - - describe('toArray()', () => { - it('should return an array representation of the box', () => { - expect(new Box(1, 2, 3, 4).toArray()).toEqual([1, 2, 3, 4]) - }) - }) - - describe('valueOf()', () => { - it('should return an array representation of the box', () => { - expect(new Box(1, 2, 3, 4).valueOf()).toEqual([1, 2, 3, 4]) - }) - }) - - describe('toString()', () => { - it('should return a string representation of the box', () => { - expect(new Box(1, 2, 3, 4).toString()).toBe('1 2 3 4') - }) - }) -}) diff --git a/packages/x6-vector/src/struct/box.ts b/packages/x6-vector/src/struct/box.ts deleted file mode 100644 index 82efcc49559..00000000000 --- a/packages/x6-vector/src/struct/box.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Point } from './point' -import { Matrix } from './matrix' - -export class Box implements Box.BoxLike { - public x: number - public y: number - public width: number - public height: number - - public get w() { - return this.width - } - - public get h() { - return this.height - } - - public get x2() { - return this.x + this.width - } - - public get y2() { - return this.y + this.height - } - - public get cx() { - return this.x + this.width / 2 - } - - public get cy() { - return this.y + this.height / 2 - } - - constructor() - constructor(string: string) - constructor(array: number[]) - constructor(object: Box.BoxObject) - constructor(x?: number, y?: number, width?: number, height?: number) - constructor( - x?: number | string | number[] | Box.BoxObject, - y?: number, - width?: number, - height?: number, - ) { - const source = - typeof x === 'string' - ? x.split(/[\s,]+/).map(parseFloat) - : Array.isArray(x) - ? x - : typeof x === 'object' - ? [ - x.left != null ? x.left : x.x, - x.top != null ? x.top : x.y, - x.width, - x.height, - ] - : arguments.length === 4 - ? [x, y, width, height] - : [0, 0, 0, 0] - - this.x = source[0] || 0 - this.y = source[1] || 0 - this.width = source[2] || 0 - this.height = source[3] || 0 - - return this - } - - merge(box: Box.BoxLike) { - const x = Math.min(this.x, box.x) - const y = Math.min(this.y, box.y) - const width = Math.max(this.x + this.width, box.x + box.width) - x - const height = Math.max(this.y + this.height, box.y + box.height) - y - return new Box(x, y, width, height) - } - - isNull() { - return Box.isNull(this) - } - - transform(matrix: Matrix.Raw) { - return this.clone().transformO(matrix) - } - - transformO(matrix: Matrix.Raw) { - const m = matrix instanceof Matrix ? matrix : new Matrix(matrix) - - let xMin = Number.POSITIVE_INFINITY - let xMax = Number.NEGATIVE_INFINITY - let yMin = Number.POSITIVE_INFINITY - let yMax = Number.NEGATIVE_INFINITY - - const points = [ - new Point(this.x, this.y), - new Point(this.x2, this.y), - new Point(this.x, this.y2), - new Point(this.x2, this.y2), - ] - - points.forEach((p) => { - const point = p.transform(m) - xMin = Math.min(xMin, point.x) - xMax = Math.max(xMax, point.x) - yMin = Math.min(yMin, point.y) - yMax = Math.max(yMax, point.y) - }) - - this.x = xMin - this.y = yMin - this.width = xMax - xMin - this.height = yMax - yMin - - return this - } - - clone() { - return new Box(this) - } - - toJSON(): Box.BoxLike { - return { x: this.x, y: this.y, width: this.width, height: this.height } - } - - toArray(): Box.BoxArray { - return [this.x, this.y, this.width, this.height] - } - - toString() { - return `${this.x} ${this.y} ${this.width} ${this.height}` - } - - valueOf() { - return this.toArray() - } -} - -export namespace Box { - export interface BoxLike extends Point.PointLike { - width: number - height: number - } - - export type BoxArray = [number, number, number, number] - - export interface BoxObject { - x?: number - y?: number - left?: number - top?: number - width?: number - height?: number - } - - export function isNull(box: BoxLike) { - return !box.width && !box.height && !box.x && !box.y - } -} diff --git a/packages/x6-vector/src/struct/color-util.ts b/packages/x6-vector/src/struct/color-util.ts deleted file mode 100644 index 9d81f32a916..00000000000 --- a/packages/x6-vector/src/struct/color-util.ts +++ /dev/null @@ -1,289 +0,0 @@ -/* eslint-disable no-bitwise */ -import { clamp } from './util' - -export namespace Util { - export type RGBA = [number, number, number, number] - export type HSLA = [number, number, number, number] - - export function hue2rgb(m1: number, m2: number, h: number) { - if (h < 0) { - h += 1 // eslint-disable-line - } - if (h > 1) { - h -= 1 // eslint-disable-line - } - - const h6 = 6 * h - if (h6 < 1) { - return m1 + (m2 - m1) * h6 - } - if (2 * h < 1) { - return m2 - } - if (3 * h < 2) { - return m1 + (m2 - m1) * (2 / 3 - h) * 6 - } - return m1 - } - - export function hex2rgb(hex: string): [number, number, number] { - const color = hex.indexOf('#') === 0 ? hex : `#${hex}` - let val = Number(`0x${color.substr(1)}`) - if (!(color.length === 4 || color.length === 7) || Number.isNaN(val)) { - throw new Error('Invalid hex color.') - } - - const bits = color.length === 4 ? 4 : 8 - const mask = (1 << bits) - 1 - const bgr = ['b', 'g', 'r'].map(() => { - const c = val & mask - val >>= bits - return bits === 4 ? 17 * c : c - }) - - return [bgr[2], bgr[1], bgr[0]] - } - - export function rgb2hex(r: number, g: number, b: number) { - const pad = (hex: string) => (hex.length < 2 ? `0${hex}` : hex) - return `${pad(r.toString(16))}${pad(g.toString(16))}${pad(b.toString(16))}` - } - - export function lum( - color: [number, number, number, number] | string, - amt: number, - ): [number, number, number, number] | string { - if (typeof color === 'string') { - const pound = color[0] === '#' - const num = parseInt(pound ? color.substr(1) : color, 16) - const r = clamp((num >> 16) + amt, 0, 255) - const g = clamp(((num >> 8) & 0x00ff) + amt, 0, 255) - const b = clamp((num & 0x0000ff) + amt, 0, 255) - return `${pound ? '#' : ''}${(b | (g << 8) | (r << 16)).toString(16)}` - } - - const hex = rgb2hex(color[0], color[1], color[2]) - const arr = hex2rgb(lum(hex, amt) as string) - - return [arr[0], arr[1], arr[2], color[3]] - } - - export function rgba2hsla(rgba: RGBA): HSLA - export function rgba2hsla(r: number, g: number, b: number, a?: number): HSLA - export function rgba2hsla( - arg0: number | RGBA, - arg1?: number, - arg2?: number, - arg3?: number, - ): HSLA { - const r = Array.isArray(arg0) ? arg0[0] : (arg0 as number) - const g = Array.isArray(arg0) ? arg0[1] : (arg1 as number) - const b = Array.isArray(arg0) ? arg0[2] : (arg2 as number) - const a = Array.isArray(arg0) ? arg0[3] : (arg3 as number) - - const max = Math.max(r, g, b) - const min = Math.min(r, g, b) - const l = (max + min) / 2 - - let h = 0 - let s = 0 - - if (min !== max) { - const d = max - min - s = l > 0.5 ? d / (2 - max - min) : d / (max + min) - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0) - break - case g: - h = (b - r) / d + 2 - break - case b: - h = (r - g) / d + 4 - break - default: - break - } - h /= 6 - } - - return [h, s, l, a == null ? 1 : a] - } - - export function hsla2rgba(hsla: HSLA): RGBA - export function hsla2rgba(h: number, s: number, l: number, a?: number): RGBA - export function hsla2rgba( - arg0: number | HSLA, - arg1?: number, - arg2?: number, - arg3?: number, - ): RGBA { - const h = Array.isArray(arg0) ? arg0[0] : (arg0 as number) - const s = Array.isArray(arg0) ? arg0[1] : (arg1 as number) - const l = Array.isArray(arg0) ? arg0[2] : (arg2 as number) - const a = Array.isArray(arg0) ? arg0[3] : (arg3 as number) - - const m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s - const m1 = 2 * l - m2 - return [ - hue2rgb(m1, m2, h + 1 / 3) * 256, - hue2rgb(m1, m2, h) * 256, - hue2rgb(m1, m2, h - 1 / 3) * 256, - a == null ? 1 : a, - ] - } -} - -export namespace Util { - export const presets = { - aliceblue: '#f0f8ff', - antiquewhite: '#faebd7', - aqua: '#00ffff', - aquamarine: '#7fffd4', - azure: '#f0ffff', - beige: '#f5f5dc', - bisque: '#ffe4c4', - black: '#000000', - blanchedalmond: '#ffebcd', - blue: '#0000ff', - blueviolet: '#8a2be2', - brown: '#a52a2a', - burlywood: '#deb887', - burntsienna: '#ea7e5d', - cadetblue: '#5f9ea0', - chartreuse: '#7fff00', - chocolate: '#d2691e', - coral: '#ff7f50', - cornflowerblue: '#6495ed', - cornsilk: '#fff8dc', - crimson: '#dc143c', - cyan: '#00ffff', - darkblue: '#00008b', - darkcyan: '#008b8b', - darkgoldenrod: '#b8860b', - darkgray: '#a9a9a9', - darkgreen: '#006400', - darkgrey: '#a9a9a9', - darkkhaki: '#bdb76b', - darkmagenta: '#8b008b', - darkolivegreen: '#556b2f', - darkorange: '#ff8c00', - darkorchid: '#9932cc', - darkred: '#8b0000', - darksalmon: '#e9967a', - darkseagreen: '#8fbc8f', - darkslateblue: '#483d8b', - darkslategray: '#2f4f4f', - darkslategrey: '#2f4f4f', - darkturquoise: '#00ced1', - darkviolet: '#9400d3', - deeppink: '#ff1493', - deepskyblue: '#00bfff', - dimgray: '#696969', - dimgrey: '#696969', - dodgerblue: '#1e90ff', - firebrick: '#b22222', - floralwhite: '#fffaf0', - forestgreen: '#228b22', - fuchsia: '#ff00ff', - gainsboro: '#dcdcdc', - ghostwhite: '#f8f8ff', - gold: '#ffd700', - goldenrod: '#daa520', - gray: '#808080', - green: '#008000', - greenyellow: '#adff2f', - grey: '#808080', - honeydew: '#f0fff0', - hotpink: '#ff69b4', - indianred: '#cd5c5c', - indigo: '#4b0082', - ivory: '#fffff0', - khaki: '#f0e68c', - lavender: '#e6e6fa', - lavenderblush: '#fff0f5', - lawngreen: '#7cfc00', - lemonchiffon: '#fffacd', - lightblue: '#add8e6', - lightcoral: '#f08080', - lightcyan: '#e0ffff', - lightgoldenrodyellow: '#fafad2', - lightgray: '#d3d3d3', - lightgreen: '#90ee90', - lightgrey: '#d3d3d3', - lightpink: '#ffb6c1', - lightsalmon: '#ffa07a', - lightseagreen: '#20b2aa', - lightskyblue: '#87cefa', - lightslategray: '#778899', - lightslategrey: '#778899', - lightsteelblue: '#b0c4de', - lightyellow: '#ffffe0', - lime: '#00ff00', - limegreen: '#32cd32', - linen: '#faf0e6', - magenta: '#ff00ff', - maroon: '#800000', - mediumaquamarine: '#66cdaa', - mediumblue: '#0000cd', - mediumorchid: '#ba55d3', - mediumpurple: '#9370db', - mediumseagreen: '#3cb371', - mediumslateblue: '#7b68ee', - mediumspringgreen: '#00fa9a', - mediumturquoise: '#48d1cc', - mediumvioletred: '#c71585', - midnightblue: '#191970', - mintcream: '#f5fffa', - mistyrose: '#ffe4e1', - moccasin: '#ffe4b5', - navajowhite: '#ffdead', - navy: '#000080', - oldlace: '#fdf5e6', - olive: '#808000', - olivedrab: '#6b8e23', - orange: '#ffa500', - orangered: '#ff4500', - orchid: '#da70d6', - palegoldenrod: '#eee8aa', - palegreen: '#98fb98', - paleturquoise: '#afeeee', - palevioletred: '#db7093', - papayawhip: '#ffefd5', - peachpuff: '#ffdab9', - peru: '#cd853f', - pink: '#ffc0cb', - plum: '#dda0dd', - powderblue: '#b0e0e6', - purple: '#800080', - rebeccapurple: '#663399', - red: '#ff0000', - rosybrown: '#bc8f8f', - royalblue: '#4169e1', - saddlebrown: '#8b4513', - salmon: '#fa8072', - sandybrown: '#f4a460', - seagreen: '#2e8b57', - seashell: '#fff5ee', - sienna: '#a0522d', - silver: '#c0c0c0', - skyblue: '#87ceeb', - slateblue: '#6a5acd', - slategray: '#708090', - slategrey: '#708090', - snow: '#fffafa', - springgreen: '#00ff7f', - steelblue: '#4682b4', - tan: '#d2b48c', - teal: '#008080', - thistle: '#d8bfd8', - tomato: '#ff6347', - turquoise: '#40e0d0', - violet: '#ee82ee', - wheat: '#f5deb3', - white: '#ffffff', - whitesmoke: '#f5f5f5', - yellow: '#ffff00', - yellowgreen: '#9acd32', - } -} diff --git a/packages/x6-vector/src/struct/color.test.ts b/packages/x6-vector/src/struct/color.test.ts deleted file mode 100644 index 1c229c322d7..00000000000 --- a/packages/x6-vector/src/struct/color.test.ts +++ /dev/null @@ -1,363 +0,0 @@ -import { Color } from './color' - -const { objectContaining } = jasmine - -describe('Color', () => { - describe('static', () => { - describe('invert()', () => { - it('shoud invert the given hex color', () => { - expect(Color.invert('#ffffff')).toEqual('#000000') - expect(Color.invert([255, 255, 255, 1])).toEqual([0, 0, 0, 1]) - expect(Color.invert([100, 100, 100, 1], true)).toEqual([ - 255, 255, 255, 1, - ]) - expect(Color.invert([200, 200, 200, 1], true)).toEqual([0, 0, 0, 1]) - }) - }) - - describe('fromHex()', () => { - it('should return null when the given string is not a valid hex string', () => { - expect(Color.fromHex('')).toBeNull() - }) - }) - - describe('fromHsl()', () => { - it('should return null when the given string is not a valid hsla string', () => { - expect(Color.fromHsl('')).toBeNull() - }) - }) - - describe('fromRgb()', () => { - it('should return null when the given string is not a valid rgba string', () => { - expect(Color.fromRgb('')).toBeNull() - }) - }) - - describe('isRgbLike()', () => { - it('shoud return true if the given object is rgb like', () => { - expect(Color.isRgbLike({ r: 1, g: 1, b: 1 })).toBeTrue() - expect(Color.isRgbLike({ r: 1, g: 1, b: 1, a: 0.5 })).toBeTrue() - }) - - it('shoud return false if the given object is not rgb like', () => { - expect(Color.isRgbLike({ r: 1, g: 1 })).toBeFalse() - expect(Color.isRgbLike({ r: 1, b: 1, a: 0.5 })).toBeFalse() - }) - }) - - describe('isRgb()', () => { - it('should return true when the given string is a valid rgba string', () => { - expect(Color.isRgb('rgb(0,0,0)')).toBeTrue() - expect(Color.isRgb('rgba(0,0,0,0)')).toBeTrue() - }) - - it('should return false when the given string is a not valid rgba string', () => { - expect(Color.isRgb('')).toBeFalse() - expect(Color.isRgb('#000000')).toBeFalse() - expect(Color.isRgb('hsl(0,0,0)')).toBeFalse() - expect(Color.isRgb('hsla(0,0,0,0)')).toBeFalse() - }) - }) - - describe('isHsl()', () => { - it('should return true when the given string is a valid hsla string', () => { - expect(Color.isHsl('hsl(0,0,0)')).toBeTrue() - expect(Color.isHsl('hsla(0,0,0,0)')).toBeTrue() - }) - - it('should return false when the given string is a not valid hsla string', () => { - expect(Color.isHsl('')).toBeFalse() - expect(Color.isHsl('#000000')).toBeFalse() - expect(Color.isHsl('rgb(0,0,0)')).toBeFalse() - expect(Color.isHsl('rgba(0,0,0,0)')).toBeFalse() - }) - }) - - describe('isHex()', () => { - it('should return true when the given string is a valid hex string', () => { - expect(Color.isHex('#000')).toBeTrue() - expect(Color.isHex('#000000')).toBeTrue() - }) - - it('should return false when the given string is a not valid hex string', () => { - expect(Color.isHex('')).toBeFalse() - expect(Color.isHex('#0000')).toBeFalse() - expect(Color.isHex('#0000000')).toBeFalse() - expect(Color.isHex('rgb(0,0,0)')).toBeFalse() - expect(Color.isHex('rgba(0,0,0,0)')).toBeFalse() - expect(Color.isHex('hsl(0,0,0)')).toBeFalse() - expect(Color.isHex('hsla(0,0,0,0)')).toBeFalse() - }) - }) - - describe('isColor()', () => { - it('should return true when the given value is a color', () => { - expect(Color.isColor('#000')).toBeTrue() - expect(Color.isColor('#000000')).toBeTrue() - expect(Color.isColor('rgb(0,0,0)')).toBeTrue() - expect(Color.isColor('rgba(0,0,0,0)')).toBeTrue() - expect(Color.isColor('hsl(0,0,0)')).toBeTrue() - expect(Color.isColor('hsla(0,0,0,0)')).toBeTrue() - expect(Color.isColor({ r: 0, g: 0, b: 0, a: 0 })).toBeTrue() - }) - - it('should return false when the given string is a not color', () => { - expect(Color.isColor('')).toBeFalse() - expect(Color.isColor(null)).toBeFalse() - expect(Color.isColor({})).toBeFalse() - expect(Color.isColor(false)).toBeFalse() - expect(Color.isColor(1)).toBeFalse() - expect(Color.isColor({ a: 0 })).toBeFalse() - }) - }) - }) - - describe('constructor', () => { - it('shoud create a Color with default args', () => { - const color = new Color() - expect(color.r).toBe(255) - expect(color.g).toBe(255) - expect(color.b).toBe(255) - expect(color.a).toBe(1) - }) - - it('shoud create a Color from named color', () => { - const black = new Color(Color.presets.black) - expect(black.r).toBe(0) - expect(black.g).toBe(0) - expect(black.b).toBe(0) - expect(black.a).toBe(1) - - const white = new Color('white') - expect(white.r).toBe(255) - expect(white.g).toBe(255) - expect(white.b).toBe(255) - expect(black.a).toBe(1) - }) - - it('should create a Color from hex string', () => { - const black = new Color('#000000') - expect(black.r).toBe(0) - expect(black.g).toBe(0) - expect(black.b).toBe(0) - expect(black.a).toBe(1) - - const white = new Color('#fff') - expect(white.r).toBe(255) - expect(white.g).toBe(255) - expect(white.b).toBe(255) - expect(black.a).toBe(1) - }) - - it('should create a Color from rgb string', () => { - expect(new Color('rgb(255,255,255)')).toEqual( - objectContaining({ r: 255, g: 255, b: 255, a: 1 }), - ) - }) - - it('should create a Color from rgba string', () => { - expect(new Color('rgba(255,255,255,0.5)')).toEqual( - objectContaining({ r: 255, g: 255, b: 255, a: 0.5 }), - ) - }) - - it('should create a Color from hsl string', () => { - expect(new Color('hsl(10,10,10)')).toEqual( - objectContaining({ r: 28, g: 24, b: 23, a: 1 }), - ) - }) - - it('should create a Color from hsla string', () => { - expect(new Color('hsl(10,10,10,0.5)')).toEqual( - objectContaining({ r: 28, g: 24, b: 23, a: 0.5 }), - ) - }) - - it('should not parse invalid string', () => { - const color = new Color('') - expect(color.r).toBeUndefined() - expect(color.g).toBeUndefined() - expect(color.b).toBeUndefined() - expect(color.a).toBeUndefined() - }) - - it('should create a Color from rgba array', () => { - const black = new Color([0, 0, 0, 1]) - expect(black.r).toBe(0) - expect(black.g).toBe(0) - expect(black.b).toBe(0) - expect(black.a).toBe(1) - }) - - it('should create a Color from rgba values', () => { - const black = new Color(-1, 0, 300, 1) - expect(black.r).toBe(0) - expect(black.g).toBe(0) - expect(black.b).toBe(255) - expect(black.a).toBe(1) - }) - - it('should create a Color from rgba like Object', () => { - const color1 = new Color({ r: 1, g: 1, b: 1 }) - expect(color1.r).toBe(1) - expect(color1.g).toBe(1) - expect(color1.b).toBe(1) - expect(color1.a).toBe(1) - - const color2 = new Color({ r: 1, g: 1, b: 1, a: 0.5 }) - expect(color2.r).toBe(1) - expect(color2.g).toBe(1) - expect(color2.b).toBe(1) - expect(color2.a).toBe(0.5) - }) - }) - - describe('randomHex()', () => { - it('shoud return valid random hex value', () => { - expect(Color.randomHex()).toMatch(/^#[0-9A-F]{6}/) - }) - }) - - describe('randomRGBA()', () => { - it('shoud generate an rgba color string', () => { - expect(Color.randomRgb().startsWith('rgba')).toBe(true) - expect(Color.randomRgb(true).startsWith('rgba')).toBe(true) - }) - }) - - describe('invert()', () => { - it('shoud return invert value of a color value', () => { - expect(Color.invert('#ffffff', false)).toBe('#000000') - expect(Color.invert('#000', false)).toBe('#ffffff') - expect(Color.invert('234567', false)).toBe('dcba98') - }) - - it('decide font color in white or black depending on background color', () => { - expect(Color.invert('#121212', true)).toBe('#ffffff') - expect(Color.invert('#feeade', true)).toBe('#000000') - }) - - it('shoud throw exception with invalid color value', () => { - expect(() => { - Color.invert('#abcd', false) - }).toThrowError('Invalid hex color.') - }) - }) - - describe('blend()', () => { - it('should generate a blend color', () => { - expect(new Color().blend(new Color(), new Color(0, 0, 0, 1), 50)).toEqual( - objectContaining({ r: 0, g: 0, b: 0, a: 1 }), - ) - }) - }) - - describe('lighten()', () => { - it('should generate a lighten color', () => { - expect(new Color().lighten(10)).toEqual( - objectContaining({ r: 255, g: 255, b: 255, a: 1 }), - ) - }) - }) - - describe('darken()', () => { - it('should generate a darken color', () => { - expect(new Color().darken(10)).toEqual( - objectContaining({ r: 245, g: 245, b: 245, a: 1 }), - ) - - expect(Color.darken('#ffffff', 10)).toEqual('#f5f5f5') - }) - }) - - describe('toHex()', () => { - it('should convert to hex string', () => { - expect(new Color().toHex()).toEqual('#ffffff') - expect(new Color(0, 0, 0).toHex()).toEqual('#000000') - }) - }) - - describe('toRGBA()', () => { - it('should convert to rgba array', () => { - expect(new Color().toRGBA()).toEqual([255, 255, 255, 1]) - }) - }) - - describe('toHSLA()', () => { - it('should convert to hsla array', () => { - expect(new Color().toHSLA()).toEqual([0, 0, 255, 1]) - }) - }) - - describe('toGrey()', () => { - it('should convert to gray color', () => { - expect(new Color().toGrey()).toEqual( - objectContaining({ r: 255, g: 255, b: 255, a: 1 }), - ) - }) - }) - - describe('toCSS()', () => { - it('should convert color to rgba css string', () => { - expect(new Color().toCSS()).toBe('rgba(255,255,255,1)') - }) - - it('should ingore alpha', () => { - expect(new Color().toCSS(true)).toBe('rgb(255,255,255)') - }) - }) - - describe('clone()', () => { - it('should clone a color', () => { - const color1 = new Color() - const clone1 = color1.clone() - expect(clone1).not.toBe(color1) - expect(clone1.r).toBe(255) - expect(clone1.g).toBe(255) - expect(clone1.b).toBe(255) - expect(clone1.a).toBe(1) - - const color2 = new Color(4, 3, 2, 1) - const clone2 = color2.clone() - expect(clone2).not.toBe(color2) - expect(clone2.r).toBe(4) - expect(clone2.g).toBe(3) - expect(clone2.b).toBe(2) - expect(clone2.a).toBe(1) - }) - }) - - describe('toJSON()', () => { - it('should create an object representation of Color', () => { - const obj1 = new Color().toJSON() - expect(obj1.r).toBe(255) - expect(obj1.g).toBe(255) - expect(obj1.b).toBe(255) - expect(obj1.a).toBe(1) - - const obj2 = new Color(4, 3, 2, 1).toJSON() - expect(obj2.r).toBe(4) - expect(obj2.g).toBe(3) - expect(obj2.b).toBe(2) - expect(obj2.a).toBe(1) - }) - }) - - describe('toArray()', () => { - it('should convert color to rgba array', () => { - expect(new Color().toArray()).toEqual([255, 255, 255, 1]) - }) - }) - - describe('toString()', () => { - it('should convert color to rgba string', () => { - expect(new Color().toCSS()).toBe('rgba(255,255,255,1)') - }) - }) - - describe('valueOf()', () => { - it('should return a rgba array', () => { - expect(new Color().valueOf()).toEqual([255, 255, 255, 1]) - }) - }) -}) diff --git a/packages/x6-vector/src/struct/color.ts b/packages/x6-vector/src/struct/color.ts deleted file mode 100644 index 6652c11d0fc..00000000000 --- a/packages/x6-vector/src/struct/color.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { clamp } from './util' -import { Util } from './color-util' - -export class Color implements Color.RGBALike { - public r: number - public g: number - public b: number - public a: number - - constructor() - constructor(color: string) - constructor(color: Color.RGBALike | Color.RGBA) - constructor(r: number, g: number, b: number, a?: number) - constructor( - color?: - | number - | string - | Color.RGBALike - | Color.RGBA - | { - r: number - g: number - b: number - a?: number - }, - g?: number, - b?: number, - a?: number, - ) { - if (color == null) { - return this.set(255, 255, 255, 1) - } - - if (typeof color === 'number') { - return this.set(color, g as number, b as number, a) - } - - if (typeof color === 'string') { - return Color.fromString(color) || this - } - - if (Array.isArray(color)) { - return this.set(color) - } - - this.set(color.r, color.g, color.b, color.a == null ? 1 : color.a) - } - - blend(start: Color, end: Color, weight: number) { - return this.set( - start.r + (end.r - start.r) * weight, - start.g + (end.g - start.g) * weight, - start.b + (end.b - start.b) * weight, - start.a + (end.a - start.a) * weight, - ) - } - - lighten(amount: number) { - const rgba = Color.lighten(this.toArray(), amount) - this.r = rgba[0] - this.g = rgba[1] - this.b = rgba[2] - this.a = rgba[3] - return this - } - - darken(amount: number) { - return this.lighten(-amount) - } - - set(rgba: Color.RGBA): this - set(r: number, g: number, b: number, a?: number): this - set(arg0: number | Color.RGBA, arg1?: number, arg2?: number, arg3?: number) { - const r = Array.isArray(arg0) ? arg0[0] : (arg0 as number) - const g = Array.isArray(arg0) ? arg0[1] : (arg1 as number) - const b = Array.isArray(arg0) ? arg0[2] : (arg2 as number) - const a = Array.isArray(arg0) ? arg0[3] : (arg3 as number) - this.r = Math.round(clamp(r, 0, 255)) - this.g = Math.round(clamp(g, 0, 255)) - this.b = Math.round(clamp(b, 0, 255)) - this.a = a == null ? 1 : clamp(a, 0, 1) - return this - } - - toHex() { - const hex = ['r', 'g', 'b'].map((key: 'r' | 'g' | 'b') => { - const str = this[key].toString(16) - return str.length < 2 ? `0${str}` : str - }) - return `#${hex.join('')}` - } - - toRGBA(): Color.RGBA { - return this.toArray() - } - - toHSLA(): Color.HSLA { - return Util.rgba2hsla(this.r, this.g, this.b, this.a) - } - - toCSS(ignoreAlpha?: boolean) { - const rgb = `${this.r},${this.g},${this.b}` - return ignoreAlpha ? `rgb(${rgb})` : `rgba(${rgb},${this.a})` - } - - toGrey() { - return Color.makeGrey(Math.round((this.r + this.g + this.b) / 3), this.a) - } - - clone() { - return new Color(this) - } - - toJSON(): Color.RGBALike { - return { r: this.r, g: this.g, b: this.b, a: this.a } - } - - toArray(): Color.RGBA { - return [this.r, this.g, this.b, this.a] - } - - toString() { - return this.toCSS() - } - - valueOf() { - return this.toArray() - } -} - -export namespace Color { - export type RGBA = Util.RGBA - export type HSLA = Util.HSLA - export interface RGBALike { - r: number - g: number - b: number - a?: number - } - - export type Presets = keyof typeof Util.presets - export const presets = Util.presets -} - -export namespace Color { - const rHex = /^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i - const rRgb = /^rgba?\(([\s.,0-9]+)\)/ - const rHsl = /^hsla?\(([\s.,0-9]+)\)/ - - export function isColor(val: any) { - return isColorString(val) || isRgbLike(val) || val instanceof Color - } - - export function isRgbLike(val: any): val is RGBALike { - return ( - val != null && - typeof val === 'object' && - typeof val.r === 'number' && - typeof val.g === 'number' && - typeof val.b === 'number' && - (typeof val.a === 'undefined' || typeof val.a === 'number') - ) - } - - export function isRgb(val: any) { - return typeof val === 'string' && rRgb.test(val.toLowerCase()) - } - - export function isHsl(val: any) { - return typeof val === 'string' && rHsl.test(val.toLowerCase()) - } - - export function isHex(val: any) { - return typeof val === 'string' && rHex.test(val) - } - - export function isColorString(val: any) { - return isRgb(val) || isHex(val) || isHsl(val) - } - - export function fromArray(arr: RGBA) { - return new Color(arr) - } - - export function fromHex(hex: string) { - return isHex(hex) ? new Color([...Util.hex2rgb(hex), 1]) : null - } - - export function fromRgb(rgba: string) { - const matches = rgba.toLowerCase().match(rRgb) - if (matches) { - const arr = matches[1].split(/\s*,\s*/).map((v) => parseFloat(v)) - return fromArray(arr as Color.RGBA) - } - - return null - } - - export function fromHsl(color: string) { - const matches = color.toLowerCase().match(rHsl) - if (matches) { - const arr = matches[1].split(/\s*,\s*/) - const h = (((parseFloat(arr[0]) % 360) + 360) % 360) / 360 - const s = parseFloat(arr[1]) / 100 - const l = parseFloat(arr[2]) / 100 - const a = arr[3] == null ? 1 : parseFloat(arr[3]) - return new Color(Util.hsla2rgba(h, s, l, a)) - } - - return null - } - - export function fromString(color: string) { - if (color.startsWith('#')) { - return fromHex(color) - } - - if (color.startsWith('rgb')) { - return fromRgb(color) - } - - if (color.startsWith('hsl')) { - return fromHsl(color) - } - - const preset = Color.presets[color as Color.Presets] - if (preset) { - return fromHex(preset) - } - - return null - } - - export function makeGrey(g: number, a: number) { - return Color.fromArray([g, g, g, a]) - } - - export function random(ignoreAlpha?: boolean) { - return new Color( - Math.round(Math.random() * 256), - Math.round(Math.random() * 256), - Math.round(Math.random() * 256), - ignoreAlpha ? undefined : parseFloat(Math.random().toFixed(2)), - ) - } - - export function randomHex() { - const letters = '0123456789ABCDEF' - let color = '#' - for (let i = 0; i < 6; i += 1) { - color += letters[Math.floor(Math.random() * 16)] - } - return color - } - - export function randomRgb(ignoreAlpha?: boolean) { - return random(ignoreAlpha).toString() - } - - export function invert(rgba: RGBA, bw?: boolean): RGBA - export function invert(hex: string, bw?: boolean): string - export function invert(color: string | RGBA, bw?: boolean) { - if (typeof color === 'string') { - const pound = color[0] === '#' - const [r, g, b] = Util.hex2rgb(color) - if (bw) { - // http://stackoverflow.com/a/3943023/112731 - return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? '#000000' : '#ffffff' - } - - return `${pound ? '#' : ''}${Util.rgb2hex(255 - r, 255 - g, 255 - b)}` - } - - const r = color[0] - const g = color[1] - const b = color[2] - const a = color[3] - - if (bw) { - return r * 0.299 + g * 0.587 + b * 0.114 > 186 - ? [0, 0, 0, a] - : [255, 255, 255, a] - } - - return [255 - r, 255 - g, 255 - b, a] - } - - export function lighten(rgba: RGBA, amt: number): RGBA - export function lighten(hex: string, amt: number): string - export function lighten(color: RGBA | string, amt: number) { - return Util.lum(color, amt) - } - - export function darken(rgba: RGBA, amt: number): RGBA - export function darken(hex: string, amt: number): string - export function darken(color: RGBA | string, amt: number) { - return Util.lum(color, -amt) - } -} diff --git a/packages/x6-vector/src/struct/matrix-util.ts b/packages/x6-vector/src/struct/matrix-util.ts deleted file mode 100644 index debf8d8f7b4..00000000000 --- a/packages/x6-vector/src/struct/matrix-util.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function closeEnough(a: number, b: number, threshold?: number) { - return Math.abs(b - a) < (threshold || 1e-6) -} - -export function toRad(degree: number) { - return ((degree % 360) * Math.PI) / 180 -} diff --git a/packages/x6-vector/src/struct/matrix.test.ts b/packages/x6-vector/src/struct/matrix.test.ts deleted file mode 100644 index 3969ba79477..00000000000 --- a/packages/x6-vector/src/struct/matrix.test.ts +++ /dev/null @@ -1,558 +0,0 @@ -import { Matrix } from './matrix' -import { Rect } from '../vector/rect/rect' - -const { objectContaining } = jasmine - -describe('Point', () => { - const comp = { a: 2, b: 0, c: 0, d: 2, e: 100, f: 50 } - - describe('static methods', () => { - describe('isMatrixLike', () => { - it('should return true if object contains all components', () => { - expect(Matrix.isMatrixLike(new Matrix())).toBe(true) - expect(Matrix.isMatrixLike(new Matrix().valueOf())).toBe(true) - }) - - it('should return false if no component is found', () => { - expect(Matrix.isMatrixLike({ foo: 'bar' })).toBe(false) - }) - }) - - describe('formatTransforms()', () => { - it('should format all transform input varieties to a canonical form', () => { - expect( - Matrix.formatTransforms({ - flip: true, - skew: 5, - scale: 5, - originX: 5, - originY: 5, - positionX: 5, - positionY: 5, - translateX: 5, - translateY: 5, - relativeX: 5, - relativeY: 5, - }), - ).toEqual({ - scaleX: -5, - scaleY: -5, - skewX: 5, - skewY: 5, - shear: 0, - theta: 0, - rx: 5, - ry: 5, - tx: 5, - ty: 5, - ox: 5, - oy: 5, - px: 5, - py: 5, - }) - }) - - it('should respect flip=x', () => { - expect( - Matrix.formatTransforms({ - flip: 'x', - scale: [1, 2], - skew: [1, 2], - }), - ).toEqual( - objectContaining({ scaleX: -1, scaleY: 2, skewX: 1, skewY: 2 }), - ) - }) - - it('should respect flip=y', () => { - expect( - Matrix.formatTransforms({ - flip: 'y', - scaleX: 1, - scaleY: 2, - skewX: 1, - skewY: 2, - }), - ).toEqual( - objectContaining({ scaleX: 1, scaleY: -2, skewX: 1, skewY: 2 }), - ) - }) - - it('should make position NaN if not passed', () => { - expect( - Matrix.formatTransforms({ - flip: 'y', - scaleX: 1, - scaleY: 2, - skewX: 1, - skewY: 2, - }), - ).toEqual(objectContaining({ px: NaN, py: NaN })) - }) - }) - }) - - describe('constructor()', () => { - it('should create a new matrix with default values', () => { - const matrix = new Matrix() - expect(matrix).toEqual( - objectContaining({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }), - ) - }) - - it('should parse the current transform matrix from an element', () => { - const rect = new Rect().transform(comp) - const matrix = new Matrix(rect) - expect(matrix).toEqual(objectContaining(comp)) - }) - - it('should parse a string value correctly', () => { - const matrix = new Matrix('2, 0, 0, 2, 100, 50') - expect(matrix).toEqual(objectContaining(comp)) - }) - - it('should parse an array correctly', () => { - const matrix = new Matrix([2, 0, 0, 2, 100, 50]) - expect(matrix).toEqual(objectContaining(comp)) - }) - - it('should parse an object correctly', () => { - const matrix = new Matrix(comp) - expect(matrix).toEqual(objectContaining(comp)) - }) - - it('should parse a transform object correctly', () => { - const matrix = new Matrix({ scale: 2, translate: [100, 50] }) - expect(matrix).toEqual(objectContaining(comp)) - }) - - it('should parse 6 arguments correctly', () => { - const matrix = new Matrix(2, 0, 0, 2, 100, 50) - expect(matrix).toEqual(objectContaining(comp)) - }) - }) - - describe('clone()', () => { - it('should return a clone of the matrix', () => { - const matrix = new Matrix(2, 0, 0, 5, 0, 0) - const clone = matrix.clone() - expect(matrix).not.toBe(clone) - const keys = 'abcdef'.split('') - keys.forEach((key: 'a') => { - expect(matrix[key]).toEqual(clone[key]) - }) - }) - }) - - describe('toString()', () => { - it('should export correctly to a string', () => { - expect(new Matrix().toString()).toBe('matrix(1,0,0,1,0,0)') - }) - }) - - describe('equals()', () => { - it('should return true if the same matrix is passed', () => { - const matrix = new Matrix() - expect(matrix.equals(matrix)).toBe(true) - }) - - it('should return true if the components match', () => { - const matrix = new Matrix() - expect(matrix.equals(matrix.clone())).toBe(true) - }) - - it('should return false if the components do not match', () => { - const matrix = new Matrix() - expect(matrix.equals(matrix.scale(2))).toBe(false) - }) - }) - - describe('valueOf()', () => { - it('should return an object containing the matrix components', () => { - const matrix = new Matrix().valueOf() - expect(matrix).not.toBeInstanceOf(Matrix) - expect(matrix).toEqual({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }) - }) - }) - - describe('toArray', () => { - it('should convert matrix to array', () => { - const arr = new Matrix().toArray() - expect(arr).toEqual([1, 0, 0, 1, 0, 0]) - }) - }) - - describe('transform()', () => { - it('does simple left matrix multiplication if matrixlike object is passed', () => { - const matrix = new Matrix().transform(new Matrix().scale(2)) - expect(matrix).toEqual(new Matrix().lmultiplyO(new Matrix().scale(2))) - }) - - it('forces the origin to a specific place if position.x is passed', () => { - const matrix = new Matrix().transform({ px: 10 }) - expect(matrix.e).toBe(10) - }) - - it('forces the origin to a specific place if position.y is passed', () => { - const matrix = new Matrix().transform({ py: 10 }) - expect(matrix.f).toBe(10) - }) - }) - - describe('decompose()', () => { - it('should decompose a matrix properly', () => { - const matrix = new Matrix() - .scale(3, 2.5) - .shear(4) - .rotate(30) - .translate(20, 30) - const decomposed = matrix.decompose() - expect(decomposed.scaleX).toBeCloseTo(3) - expect(decomposed.scaleY).toBeCloseTo(2.5) - expect(decomposed.shear).toBeCloseTo(4) - expect(decomposed.rotate).toBeCloseTo(30) - expect(decomposed.translateX).toBeCloseTo(20) - expect(decomposed.translateY).toBeCloseTo(30) - }) - - it('should be recomposed to the same matrix', () => { - const matrix = new Matrix() - .scale(3, 2.5) - .shear(4) - .rotate(30) - .translate(20, 30) - const decomposed = matrix.decompose() - - // Get rid of the matrix values before recomposing with the matrix constructor - const keys = 'abcdef'.split('') - keys.forEach((key: 'a') => { - delete (decomposed as any)[key] - }) - - const composed = new Matrix(decomposed) - expect(matrix.a).toBeCloseTo(composed.a) - expect(matrix.b).toBeCloseTo(composed.b) - expect(matrix.c).toBeCloseTo(composed.c) - expect(matrix.d).toBeCloseTo(composed.d) - expect(matrix.e).toBeCloseTo(composed.e) - expect(matrix.f).toBeCloseTo(composed.f) - }) - }) - - describe('multiply()', () => { - it('should multiplie two matrices', () => { - const matrix1 = new Matrix(1, 4, 2, 5, 3, 6) - const matrix2 = new Matrix(7, 8, 8, 7, 9, 6) - const matrix3 = matrix1.multiply(matrix2) - - expect(matrix1.toString()).toBe('matrix(1,4,2,5,3,6)') - expect(matrix2.toString()).toBe('matrix(7,8,8,7,9,6)') - expect(matrix3.toString()).toBe('matrix(23,68,22,67,24,72)') - }) - - it('should accept matrices in any form', () => { - const matrix1 = new Matrix(1, 4, 2, 5, 3, 6) - const matrix2 = matrix1.multiply('7,8,8,7,9,6') - - expect(matrix1.toString()).toBe('matrix(1,4,2,5,3,6)') - expect(matrix2.toString()).toBe('matrix(23,68,22,67,24,72)') - }) - }) - - describe('inverse()', () => { - it('should inverse matrix', () => { - const matrix1 = new Matrix(2, 0, 0, 5, 4, 3) - const matrix2 = matrix1.inverse() - const abcdef = [0.5, 0, 0, 0.2, -2, -0.6] - - const keys = 'abcdef'.split('') - keys.forEach((key: 'a', index) => { - expect(matrix2[key]).toBeCloseTo(abcdef[index]) - }) - }) - - it('should throw error if matrix is not inversable', () => { - const matrix = new Matrix(0, 0, 0, 0, 0, 0) - expect(() => matrix.inverse()).toThrowError( - 'Cannot invert matrix(0,0,0,0,0,0)', - ) - }) - }) - - describe('translate()', () => { - it('should translate matrix by given x and y values', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).translate(10, 12.5) - expect(matrix.e).toBe(14) - expect(matrix.f).toBe(15.5) - }) - - it('should do nothing if you give it no x or y value', () => { - const matrix = new Matrix(1, 2, 3, 4, 5, 6).translate() - expect(matrix.e).toBe(5) - expect(matrix.f).toBe(6) - }) - }) - - describe('scale()', () => { - it('should perform a uniformal scale with one value', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).scale(3) - - expect(matrix.a).toBe(3) - expect(matrix.d).toBe(3) - expect(matrix.e).toBe(4 * 3) - expect(matrix.f).toBe(3 * 3) - }) - - it('should perform a non-uniformal scale with two values', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).scale(2.5, 3.5) - - expect(matrix.a).toBe(2.5) - expect(matrix.d).toBe(3.5) - expect(matrix.e).toBe(4 * 2.5) - expect(matrix.f).toBe(3 * 3.5) - }) - - it('should perform a uniformal scale at a given center point with three values', () => { - const matrix = new Matrix(1, 3, 2, 3, 4, 3).scale(3, 2, 3) - - expect(matrix.a).toBe(3) - expect(matrix.b).toBe(9) - expect(matrix.c).toBe(6) - expect(matrix.d).toBe(9) - expect(matrix.e).toBe(8) - expect(matrix.f).toBe(3) - }) - - it('should perform a non-uniformal scale at a given center point with four values', () => { - const matrix = new Matrix(1, 3, 2, 3, 4, 3).scale(3, 2, 2, 3) - - expect(matrix.a).toBe(3) - expect(matrix.b).toBe(6) - expect(matrix.c).toBe(6) - expect(matrix.d).toBe(6) - expect(matrix.e).toBe(8) - expect(matrix.f).toBe(3) - }) - }) - - describe('rotate()', () => { - it('should perform a rotation with one argument', () => { - const matrix = new Matrix(1, 3, 2, 3, 4, 3).rotate(30) - - expect(matrix.a).toBeCloseTo(-0.6339746) - expect(matrix.b).toBeCloseTo(3.09807621) - expect(matrix.c).toBeCloseTo(0.23205081) - expect(matrix.d).toBeCloseTo(3.59807621) - expect(matrix.e).toBeCloseTo(1.96410162) - expect(matrix.f).toBeCloseTo(4.59807621) - }) - - it('should perform a rotation around a given point with three arguments', () => { - const matrix = new Matrix(1, 3, 2, 3, 4, 3).rotate(30, 2, 3) - - expect(matrix.a).toBeCloseTo(-0.633974596216) - expect(matrix.b).toBeCloseTo(3.09807621135) - expect(matrix.c).toBeCloseTo(0.232050807569) - expect(matrix.d).toBeCloseTo(3.59807621135) - expect(matrix.e).toBeCloseTo(3.73205080757) - expect(matrix.f).toBeCloseTo(4.0) - }) - }) - - describe('flip()', () => { - describe('with x given', () => { - it('should perform a flip over the horizontal axis with one argument', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('x') - - expect(matrix.a).toBe(-1) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(-4) - expect(matrix.f).toBe(3) - }) - - it('should perform a flip over the horizontal axis over a given point with two arguments', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('x', 150) - - expect(matrix.a).toBe(-1) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(296) - expect(matrix.f).toBe(3) - }) - }) - - describe('with y given', () => { - it('should perform a flip over the vertical axis with one argument', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('y') - - expect(matrix.a).toBe(1) - expect(matrix.d).toBe(-1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(-3) - }) - - it('should perform a flip over the vertical axis over a given point with two arguments', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('y', 100) - - expect(matrix.a).toBe(1) - expect(matrix.d).toBe(-1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(197) - }) - }) - - describe('with no axis given', () => { - it('should perform a flip over the horizontal and vertical axis with no argument', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).flip() - - expect(matrix.a).toBe(-1) - expect(matrix.d).toBe(-1) - expect(matrix.e).toBe(-4) - expect(matrix.f).toBe(-3) - }) - - it('should perform a flip over the horizontal and vertical axis over a given point with one argument that represent both coordinates', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).flip(100) - - expect(matrix.a).toBe(-1) - expect(matrix.d).toBe(-1) - expect(matrix.e).toBe(196) - expect(matrix.f).toBe(197) - }) - - it('should perform a flip over the horizontal and vertical axis over a given point with two arguments', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).flip(50, 100) - - expect(matrix.a).toBe(-1) - expect(matrix.d).toBe(-1) - expect(matrix.e).toBe(96) - expect(matrix.f).toBe(197) - }) - }) - }) - - describe('skew()', () => { - it('should perform a uniformal skew with one value', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.57735026919) - expect(matrix.c).toBeCloseTo(0.57735026919) - expect(matrix.d).toBe(1) - expect(matrix.e).toBeCloseTo(5.73205080757) - expect(matrix.f).toBeCloseTo(5.30940107676) - }) - - it('should perform a non-uniformal skew with two values', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30, 20) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.363970234266) - expect(matrix.c).toBeCloseTo(0.57735026919) - expect(matrix.d).toBe(1) - expect(matrix.e).toBeCloseTo(5.73205080757) - expect(matrix.f).toBeCloseTo(4.45588093706) - }) - - it('should perform a uniformal skew at a given center point with three values', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30, 150, 100) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.57735026919) - expect(matrix.c).toBeCloseTo(0.57735026919) - expect(matrix.d).toBe(1) - expect(matrix.e).toBeCloseTo(-52.0029761114) - expect(matrix.f).toBeCloseTo(-81.2931393017) - }) - - it('should perform a non-uniformal skew at a given center point with four values', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30, 20, 150, 100) - - expect(matrix.a).toBe(1.0) - expect(matrix.b).toBeCloseTo(0.363970234266) - expect(matrix.c).toBeCloseTo(0.57735026919) - expect(matrix.d).toBe(1.0) - expect(matrix.e).toBeCloseTo(-52.0029761114) - expect(matrix.f).toBeCloseTo(-50.1396542029) - }) - - it('should be chained', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(20, 30).skew(30, 20) - expect(matrix.a).toBeCloseTo(1.33333333333) - expect(matrix.b).toBeCloseTo(0.941320503456) - expect(matrix.c).toBeCloseTo(0.941320503456) - expect(matrix.d).toBeCloseTo(1.13247433143) - expect(matrix.e).toBeCloseTo(8.1572948437) - expect(matrix.f).toBeCloseTo(7.16270500812) - }) - }) - - describe('skewX', () => { - it('should perform a skew along the x axis with one value', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).skewX(30) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBe(0) - expect(matrix.c).toBeCloseTo(0.57735026919) - expect(matrix.d).toBe(1) - expect(matrix.e).toBeCloseTo(5.73205080757) - expect(matrix.f).toBe(3) - }) - - it('should perform a skew along the x axis at a given center point with three values', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).skewX(30, 150, 100) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBe(0) - expect(matrix.c).toBeCloseTo(0.57735026919) - expect(matrix.d).toBe(1) - expect(matrix.e).toBeCloseTo(-52.0029761114) - expect(matrix.f).toBe(3) - }) - }) - - describe('skewY', () => { - it('should perform a skew along the y axis with one value', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).skewY(30) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.57735026919) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBeCloseTo(5.30940107676) - }) - - it('should perform a skew along the y axis at a given center point with three values', () => { - const matrix = new Matrix(1, 0, 0, 1, 4, 3).skewY(30, 150, 100) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.57735026919) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBeCloseTo(-81.2931393017) - }) - }) - - describe('around()', () => { - it('should perform a matrix operation around an origin by shifting the origin to 0,0', () => { - const matrix = new Matrix(1, 0, 0, 1, 0, 0).around( - 10, - 10, - new Matrix().scale(2), - ) - - expect(matrix).toEqual(new Matrix(2, 0, 0, 2, -10, -10)) - }) - - it('should around center of 0,0 by default', () => { - const matrix = new Matrix(1, 0, 0, 1, 0, 0).around( - 0, - 0, - new Matrix().scale(2), - ) - - expect(matrix).toEqual(new Matrix(2, 0, 0, 2, 0, 0)) - }) - }) -}) diff --git a/packages/x6-vector/src/struct/matrix.ts b/packages/x6-vector/src/struct/matrix.ts deleted file mode 100644 index 32b8c9674c7..00000000000 --- a/packages/x6-vector/src/struct/matrix.ts +++ /dev/null @@ -1,672 +0,0 @@ -import { closeEnough, toRad } from './matrix-util' - -export class Matrix implements Matrix.MatrixLike { - a: number - b: number - c: number - d: number - e: number - f: number - - constructor() - constructor( - a?: number, - b?: number, - c?: number, - d?: number, - e?: number, - f?: number, - ) - constructor(string: string) - constructor(element: Matrix.Matrixifiable | null) - constructor(array: Matrix.MatrixArray) - constructor(matrix: Matrix.MatrixLike) - constructor(options: Matrix.TransformOptionsStrict) - constructor( - a?: - | number - | string - | Matrix.Matrixifiable - | Matrix.MatrixArray - | Matrix.MatrixLike - | Matrix.TransformOptions - | null, - b?: number, - c?: number, - d?: number, - e?: number, - f?: number, - ) - constructor( - a?: - | number - | string - | Matrix.Matrixifiable - | Matrix.MatrixArray - | Matrix.MatrixLike - | Matrix.TransformOptionsStrict - | null, - b?: number, - c?: number, - d?: number, - e?: number, - f?: number, - ) { - const base = Matrix.toMatrixLike([1, 0, 0, 1, 0, 0]) - const source = - a == null - ? base - : (a as Matrix.Matrixifiable).matrixify != null - ? (a as Matrix.Matrixifiable).matrixify() - : typeof a === 'string' - ? Matrix.toMatrixLike( - a.split(/[\s,]+/).map(parseFloat) as Matrix.MatrixArray, - ) - : Array.isArray(a) - ? Matrix.toMatrixLike(a) - : typeof a === 'object' && Matrix.isMatrixLike(a) - ? a - : typeof a === 'object' - ? new Matrix().transform(a as Matrix.TransformOptionsStrict) - : typeof a === 'number' - ? Matrix.toMatrixLike([ - a, - b as number, - c as number, - d as number, - e as number, - f as number, - ]) - : base - - this.a = source.a != null ? source.a : base.a - this.b = source.b != null ? source.b : base.b - this.c = source.c != null ? source.c : base.c - this.d = source.d != null ? source.d : base.d - this.e = source.e != null ? source.e : base.e - this.f = source.f != null ? source.f : base.f - } - - equals(other: Matrix.Raw) { - if (other instanceof Matrix && other === this) { - return true - } - const comp = new Matrix(other) - return ( - closeEnough(this.a, comp.a) && - closeEnough(this.b, comp.b) && - closeEnough(this.c, comp.c) && - closeEnough(this.d, comp.d) && - closeEnough(this.e, comp.e) && - closeEnough(this.f, comp.f) - ) - } - - decompose(ox = 0, oy = 0) { - const { a, b, c, d, e, f } = this - - // Figure out if the winding direction is clockwise or counterclockwise - const determinant = a * d - b * c - const ccw = determinant > 0 ? 1 : -1 - - // Since we only shear in x, we can use the x basis to get the x scale - // and the rotation of the resulting matrix - const sx = ccw * Math.sqrt(a * a + b * b) - const thetaRad = Math.atan2(ccw * b, ccw * a) - const theta = (180 / Math.PI) * thetaRad - const ct = Math.cos(thetaRad) - const st = Math.sin(thetaRad) - - // We can then solve the y basis vector simultaneously to get the other - // two affine parameters directly from these parameters - const lam = (a * c + b * d) / determinant - const sy = (c * sx) / (lam * a - b) || (d * sx) / (lam * b + a) - - const tx = e - ox + ox * ct * sx + oy * (lam * ct * sx - st * sy) - const ty = f - oy + ox * st * sx + oy * (lam * st * sx + ct * sy) - - return { - // affine parameters - scaleX: sx, - scaleY: sy, - shear: lam, - rotate: theta, - translateX: tx, - translateY: ty, - originX: ox, - originY: oy, - - // matrix parameters - a: this.a, - b: this.b, - c: this.c, - d: this.d, - e: this.e, - f: this.f, - } - } - - around(cx: number, cy: number, matrix: Matrix.Raw) { - return this.clone().aroundO(cx, cy, matrix) - } - - aroundO(cx: number | undefined, cy: number | undefined, matrix: Matrix.Raw) { - const dx = cx || 0 - const dy = cy || 0 - return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy) - } - - scale(s: number): Matrix - scale(x: number, y: number): Matrix - scale(s: number, cx: number, cy: number): Matrix - scale(x: number, y: number, cx: number, cy: number): Matrix - scale(x: number, y?: number, cx?: number, cy?: number) { - return this.clone().scaleO(x, y, cx, cy) - } - - scaleO(s: number): this - scaleO(sx: number, sy: number): this - scaleO(s: number, cx: number, cy: number): this - scaleO(sx: number, sy: number, cx: number, cy: number): this - scaleO(sx: number, sy?: number, cx?: number, cy?: number): this - scaleO(sx: number, sy?: number, cx?: number, cy?: number) { - if (typeof cy === 'undefined') { - if (typeof cx === 'undefined') { - cx = 0 // eslint-disable-line - cy = 0 // eslint-disable-line - if (typeof sy === 'undefined') { - sy = sx // eslint-disable-line - } - } else { - cy = cx // eslint-disable-line - cx = sy // eslint-disable-line - sy = sx // eslint-disable-line - } - } - - const { a, b, c, d, e, f } = this - - this.a = a * sx - this.b = b * (sy as number) - this.c = c * sx - this.d = d * (sy as number) - this.e = e * sx - (cx as number) * sx + (cx as number) - this.f = f * (sy as number) - cy * (sy as number) + cy - - return this - } - - flip(): Matrix - flip(cx: number, cy?: number): Matrix - flip(axis: 'x' | 'y', around?: number): Matrix - flip(axis?: 'x' | 'y' | number, around?: number) { - return this.clone().flipO(axis, around) - } - - flipO(): this - flipO(cx: number, cy?: number): this - flipO(axis: 'x' | 'y', around?: number): this - flipO(axis?: 'x' | 'y' | number, around?: number): this - flipO(axis: 'x' | 'y' | number = 0, around = 0): this { - return axis === 'x' - ? this.scaleO(-1, 1, around, 0) - : axis === 'y' - ? this.scaleO(1, -1, 0, around) - : this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point - } - - inverse() { - return this.clone().inverseO() - } - - inverseO() { - const { a, b, c, d, e, f } = this - - // Invert the 2x2 matrix in the top left - const det = a * d - b * c - if (!det) { - throw new Error(`Cannot invert ${this.toString()}`) - } - - // Calculate the top 2x2 matrix - const na = d / det - const nb = -b / det - const nc = -c / det - const nd = a / det - - // Apply the inverted matrix to the top right - const ne = -(na * e + nc * f) - const nf = -(nb * e + nd * f) - - // Construct the inverted matrix - this.a = na - this.b = nb - this.c = nc - this.d = nd - this.e = ne - this.f = nf - - return this - } - - lmultiply(matrix: Matrix.Raw) { - return this.clone().lmultiplyO(matrix) - } - - lmultiplyO(matrix: Matrix.Raw) { - return Matrix.multiply( - matrix instanceof Matrix ? matrix : new Matrix(matrix), - this, - this, - ) - } - - multiply(matrix: Matrix.Raw) { - return this.clone().multiplyO(matrix) - } - - multiplyO(matrix: Matrix.Raw) { - return Matrix.multiply( - this, - matrix instanceof Matrix ? matrix : new Matrix(matrix), - this, - ) - } - - rotate(degree: number): Matrix - rotate(degree: number, cx: number, cy: number): Matrix - rotate(degree: number, cx?: number, cy?: number) { - return this.clone().rotateO(degree, cx, cy) - } - - rotateO(degree: number): this - rotateO(degree: number, cx: number, cy: number): this - rotateO(degree: number, cx?: number, cy?: number): this - rotateO(degrees: number, cx = 0, cy = 0) { - const radian = toRad(degrees) - const cos = Math.cos(radian) - const sin = Math.sin(radian) - - const { a, b, c, d, e, f } = this - - this.a = a * cos - b * sin - this.b = b * cos + a * sin - this.c = c * cos - d * sin - this.d = d * cos + c * sin - this.e = e * cos - f * sin + cy * sin - cx * cos + cx - this.f = f * cos + e * sin - cx * sin - cy * cos + cy - - return this - } - - shear(a: number): Matrix - shear(a: number, cx: number, cy: number): Matrix - shear(a: number, cx?: number, cy?: number) { - return this.clone().shearO(a, cx, cy) - } - - shearO(lx: number): this - shearO(lx: number, cx: number, cy: number): this - shearO(lx: number, cx?: number, cy?: number): this - shearO(lx: number, cx?: number, cy = 0) { - const { a, b, c, d, e, f } = this - - this.a = a + b * lx - this.c = c + d * lx - this.e = e + f * lx - cy * lx - - return this - } - - skew(s: number): Matrix - skew(sx: number, sy: number): Matrix - skew(s: number, cx: number, cy: number): Matrix - skew(sx: number, sy: number, cx: number, cy: number): Matrix - skew(sx: number, sy?: number, cx?: number, cy?: number): Matrix - skew(sx: number, sy?: number, cx?: number, cy?: number) { - return this.clone().skewO(sx, sy, cx, cy) - } - - skewO(s: number): this - skewO(sx: number, sy: number): this - skewO(s: number, cx: number, cy: number): this - skewO(sx: number, sy: number, cx: number, cy: number): this - skewO(sx: number, sy?: number, cx?: number, cy?: number): this - skewO(sx: number, sy?: number, cx?: number, cy?: number) { - if (typeof cy === 'undefined') { - if (typeof cx === 'undefined') { - cx = 0 // eslint-disable-line - cy = 0 // eslint-disable-line - if (typeof sy === 'undefined') { - sy = sx // eslint-disable-line - } - } else { - cy = cx // eslint-disable-line - cx = sy // eslint-disable-line - sy = sx // eslint-disable-line - } - } - sx = toRad(sx) // eslint-disable-line - sy = toRad(sy as number) // eslint-disable-line - - const lx = Math.tan(sx) - const ly = Math.tan(sy) - - const { a, b, c, d, e, f } = this - - this.a = a + b * lx - this.b = b + a * ly - this.c = c + d * lx - this.d = d + c * ly - this.e = e + f * lx - cy * lx - this.f = f + e * ly - (cx as number) * ly - - return this - } - - skewX(x: number): Matrix - skewX(x: number, cx: number, cy: number): Matrix - skewX(x: number, cx?: number, cy?: number) { - return this.skew(x, 0, cx, cy) - } - - skewY(y: number): Matrix - skewY(y: number, cx: number, cy: number): Matrix - skewY(y: number, cx?: number, cy?: number) { - return this.skew(0, y, cx, cy) - } - - transform(o: Matrix.MatrixLike | Matrix.TransformOptionsStrict) { - if (Matrix.isMatrixLike(o)) { - const matrix = new Matrix(o) - return matrix.multiplyO(this) - } - - // Get the proposed transformations and the current transformations - const ts = Matrix.formatTransforms(o) - const { x: ox, y: oy } = Matrix.transformPoint(ts.ox, ts.oy, this) - - // Construct the resulting matrix - const transformer = new Matrix() - .translateO(ts.rx, ts.ry) - .lmultiplyO(this) - .translateO(-ox, -oy) - .scaleO(ts.scaleX, ts.scaleY) - .skewO(ts.skewX, ts.skewY) - .shearO(ts.shear) - .rotateO(ts.theta) - .translateO(ox, oy) - - // If we want the origin at a particular place, we force it there - if (Number.isFinite(ts.px) || Number.isFinite(ts.py)) { - const origin = Matrix.transformPoint(ox, oy, transformer) - // Doesnt work because t.px is also 0 if it wasnt passed - const dx = Number.isFinite(ts.px) ? ts.px - origin.x : 0 - const dy = Number.isFinite(ts.py) ? ts.py - origin.y : 0 - transformer.translateO(dx, dy) - } - - // Translate now after positioning - transformer.translateO(ts.tx, ts.ty) - return transformer - } - - translate(x?: number, y?: number) { - return this.clone().translateO(x, y) - } - - translateO(x?: number, y?: number) { - this.e += x || 0 - this.f += y || 0 - return this - } - - clone() { - return new Matrix(this) - } - - toJSON(): Matrix.MatrixLike { - return { - a: this.a, - b: this.b, - c: this.c, - d: this.d, - e: this.e, - f: this.f, - } - } - - toArray(): Matrix.MatrixArray { - return [this.a, this.b, this.c, this.d, this.e, this.f] - } - - toString() { - return `matrix(${this.a},${this.b},${this.c},${this.d},${this.e},${this.f})` - } - - valueOf(): Matrix.MatrixLike { - return this.toJSON() - } -} - -export namespace Matrix { - export interface Matrixifiable { - matrixify(): Matrix - } -} - -export namespace Matrix { - export interface TransformOptionsStrict { - flip?: 'both' | 'x' | 'y' | boolean - skew?: number | [number, number] - skewX?: number - skewY?: number - scale?: number | [number, number] - scaleX?: number - scaleY?: number - shear?: number - - rotate?: number - theta?: number - - ox?: number - oy?: number - around?: number - origin?: number | [number, number] | { x: number; y: number } - originX?: number - originY?: number - - px?: number - py?: number - position?: number | [number, number] - positionX?: number - positionY?: number - - tx?: number - ty?: number - translate?: number | [number, number] - translateX?: number - translateY?: number - - rx?: number - ry?: number - relative?: number | [number, number] - relativeX?: number - relativeY?: number - } - - export interface TransformOptions - extends Omit { - origin?: number | [number, number] | { x: number; y: number } | 'center' - } - - export type Transform = ReturnType - - export function formatTransforms(o: TransformOptionsStrict) { - const flipBoth = o.flip === 'both' || o.flip === true - const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1 - const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1 - const skewX = - o.skew && Array.isArray(o.skew) - ? o.skew[0] - : o.skew != null && Number.isFinite(o.skew) - ? o.skew - : o.skewX != null && Number.isFinite(o.skewX) - ? o.skewX - : 0 - const skewY = - o.skew && Array.isArray(o.skew) - ? o.skew[1] - : o.skew != null && Number.isFinite(o.skew) - ? o.skew - : o.skewY != null && Number.isFinite(o.skewY) - ? o.skewY - : 0 - const scaleX = - o.scale && Array.isArray(o.scale) - ? o.scale[0] * flipX - : o.scale != null && Number.isFinite(o.scale) - ? o.scale * flipX - : o.scaleX != null && Number.isFinite(o.scaleX) - ? o.scaleX * flipX - : flipX - const scaleY = - o.scale && Array.isArray(o.scale) - ? o.scale[1] * flipY - : o.scale != null && Number.isFinite(o.scale) - ? o.scale * flipY - : o.scaleY != null && Number.isFinite(o.scaleY) - ? o.scaleY * flipY - : flipY - - const shear = o.shear || 0 - const theta = o.rotate || o.theta || 0 - const origin = Matrix.normalizePoint( - o.origin || o.around || o.ox || o.originX, - o.oy || o.originY, - ) - const ox = origin.x - const oy = origin.y - - // We need Point to be invalid if nothing was passed because we cannot default to 0 here. Thats why NaN - const position = Matrix.normalizePoint( - o.position || o.px || o.positionX || Number.NaN, - o.py || o.positionY || Number.NaN, - ) - const px = position.x - const py = position.y - - const translate = Matrix.normalizePoint( - o.translate || o.tx || o.translateX, - o.ty || o.translateY, - ) - const tx = translate.x - const ty = translate.y - const relative = Matrix.normalizePoint( - o.relative || o.rx || o.relativeX, - o.ry || o.relativeY, - ) - const rx = relative.x - const ry = relative.y - - return { - scaleX, - scaleY, - skewX, - skewY, - shear, - theta, - rx, - ry, - tx, - ty, - ox, - oy, - px, - py, - } - } - - export interface MatrixLike { - a: number - b: number - c: number - d: number - e: number - f: number - } - - export function isMatrixLike(o: any): o is MatrixLike { - return ( - typeof o === 'object' && - typeof o.a === 'number' && - typeof o.b === 'number' && - typeof o.c === 'number' && - typeof o.d === 'number' && - typeof o.e === 'number' && - typeof o.f === 'number' - ) - } - - export type MatrixArray = [number, number, number, number, number, number] - - export function toMatrixLike(a: MatrixArray): MatrixLike { - return { - a: a[0], - b: a[1], - c: a[2], - d: a[3], - e: a[4], - f: a[5], - } - } - - export type Raw = - | string - | Matrix.Matrixifiable - | MatrixLike - | MatrixArray - | TransformOptions - - export function multiply(l: Matrix, r: Matrix, o: Matrix) { - const a = l.a * r.a + l.c * r.b - const b = l.b * r.a + l.d * r.b - const c = l.a * r.c + l.c * r.d - const d = l.b * r.c + l.d * r.d - const e = l.e + l.a * r.e + l.c * r.f - const f = l.f + l.b * r.e + l.d * r.f - - o.a = a - o.b = b - o.c = c - o.d = d - o.e = e - o.f = f - - return o - } - - export function transformPoint(x: number, y: number, matrix: Raw) { - const m = isMatrixLike(matrix) ? matrix : new Matrix(matrix) - - return { - x: m.a * x + m.c * y + m.e, - y: m.b * x + m.d * y + m.f, - } - } - - export function normalizePoint( - x?: number | { x: number; y: number } | [number, number], - y?: number, - ) { - const source = Array.isArray(x) - ? { x: x[0], y: x[1] } - : typeof x === 'object' - ? { x: x.x, y: x.y } - : { x, y } - return { - x: source.x == null ? 0 : source.x, - y: source.y == null ? 0 : source.y, - } - } -} diff --git a/packages/x6-vector/src/struct/number-array.test.ts b/packages/x6-vector/src/struct/number-array.test.ts deleted file mode 100644 index 7e4294e945d..00000000000 --- a/packages/x6-vector/src/struct/number-array.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { NumberArray } from './number-array' - -describe('NumberArray', () => { - describe('constructor()', () => { - it('should preallocate memory if only number is passed', () => { - const arr = new NumberArray(1) - expect(arr.length).toBe(1) - }) - - it('should parse a matrix array correctly to string', () => { - const raw = [ - 0.343, 0.669, 0.119, 0, 0, 0.249, -0.626, 0.13, 0, 0, 0.172, 0.334, - 0.111, 0, 0, 0.0, 0.0, 0.0, 1, -0, - ] - const array = new NumberArray(raw) - - array.forEach((v, i) => { - expect(v).toBe(raw[i]) - }) - - expect(`${array}`).toBe( - '0.343 0.669 0.119 0 0 0.249 -0.626 0.13 0 0 0.172 0.334 0.111 0 0 0 0 0 1 0', - ) - }) - - it('should parse space seperated string and converts it to array', () => { - expect(new NumberArray('1 2 3 4').valueOf()).toEqual([1, 2, 3, 4]) - }) - - it('should parse comma seperated string and converts it to array', () => { - expect(new NumberArray('1,2,3,4').valueOf()).toEqual([1, 2, 3, 4]) - }) - }) - - describe('reverse()', () => { - it('should reverse the array', () => { - const array = new NumberArray([1, 2, 3, 4, 5]).reverse() - expect(array.valueOf()).toEqual([5, 4, 3, 2, 1]) - }) - - it('should returns itself', () => { - const array = new NumberArray() - expect(array.reverse()).toBe(array) - }) - }) - - describe('clone()', () => { - it('should create a shallow clone of the array', () => { - const array = new NumberArray([1, 2, 3, 4, 5, 6, 7, 8]) - const clone = array.clone() - - expect(array.toString()).toBe(clone.toString()) - expect(array).not.toBe(clone) - }) - }) - - describe('toSet()', () => { - it('should create a Set from the Array', () => { - const set = new NumberArray([1, 1, 2, 3]).toSet() - expect(set).toBeInstanceOf(Set) - }) - }) - - describe('toArray()', () => { - it('should convert to an array', () => { - const arr = [1, 1, 2, 3] - expect(new NumberArray(arr).toArray()).toEqual(arr) - }) - }) - - describe('valueOf()', () => { - it('should return an array', () => { - const arr = [1, 1, 2, 3] - expect(new NumberArray(arr).valueOf()).toEqual(arr) - }) - }) -}) diff --git a/packages/x6-vector/src/struct/number-array.ts b/packages/x6-vector/src/struct/number-array.ts deleted file mode 100644 index 8e90959712d..00000000000 --- a/packages/x6-vector/src/struct/number-array.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { TypeArray } from './type-array' - -export class NumberArray extends TypeArray { - parse(raw: string | number[] = []): number[] { - if (Array.isArray(raw)) { - return raw - } - - return raw - .trim() - .split(/[\s,]+/) - .map((s) => Number.parseFloat(s)) - } -} diff --git a/packages/x6-vector/src/struct/path-array.test.ts b/packages/x6-vector/src/struct/path-array.test.ts deleted file mode 100644 index db2ab23de09..00000000000 --- a/packages/x6-vector/src/struct/path-array.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Box } from './box' -import { PathArray } from './path-array' - -describe('PathArray', () => { - let p1: PathArray - let p2: PathArray - let p3: PathArray - - beforeEach(() => { - p1 = new PathArray('m10 10 h 80 v 80 h -80 l 300 400 z') - p2 = new PathArray( - 'm10 80 c 40 10 65 10 95 80 s 150 150 180 80 t 300 300 q 52 10 95 80 z', - ) - p3 = new PathArray('m80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 z') - }) - - describe('constructor()', () => { - it('should parse flat arrays correctly', () => { - const arr = new PathArray(['M', 0, 0, 'L', 100, 100, 'z']) - expect(arr.toString()).toBe('M0 0L100 100Z') - }) - - it('should parse nested arrays correctly', () => { - const arr = new PathArray([['M', 0, 0], ['L', 100, 100], ['z']]) - expect(arr.toString()).toBe('M0 0L100 100Z') - }) - }) - - describe('move()', () => { - it('should move all points in a straight path', () => { - expect(p1.move(100, 200).toString()).toBe('M100 200H180V280H100L400 680Z') - }) - - it('should move all points in a curved path', () => { - expect(p2.move(100, 200).toString()).toBe( - 'M100 200C140 210 165 210 195 280S345 430 375 360T675 660Q727 670 770 740Z', - ) - }) - - it('should move all points in a arc path', () => { - expect(p3.move(100, 200).toString()).toBe( - 'M100 200A45 45 0 0 0 145 245L145 200Z', - ) - }) - - it('should do nothing if passed number is not a number', () => { - expect(p3.move()).toEqual(p3) - }) - }) - - describe('size()', () => { - it('should resize all points in a straight path', () => { - expect(p1.size(600, 200).toString()).toBe( - 'M10 10H170V43.333333333333336H10L610 210Z', - ) - }) - - it('should resize all points in a curved path', () => { - expect(p2.size(600, 200).toString()).toBe( - 'M10 80C45.82089552238806 83.70370370370371 68.2089552238806 83.70370370370371 95.07462686567165 109.62962962962963S229.40298507462686 165.1851851851852 256.2686567164179 139.25925925925927T524.9253731343283 250.37037037037038Q571.4925373134329 254.07407407407408 610 280Z', - ) - }) - - it('should resize all points in a arc path', () => { - const expected = [ - ['M', 80, 80], - ['A', 600, 200, 0, 0, 0, 680, 280], - ['L', 680, 80], - ['Z'], - ] - - p3.size(600, 200).forEach((item, index) => { - expect(item[0].toUpperCase()).toBe( - (expected[index][0] as string).toUpperCase(), - ) - - for (let j = item.length - 1; j > 0; j -= 1) { - expect(item[j]).toBeCloseTo(expected[index][j] as number) - } - }) - }) - }) - - describe('bbox()', () => { - it('should calculate the bounding box of the PathArray', () => { - const box = new PathArray('M0 0 L 10 10').bbox() - expect(box).toEqual(new Box(0, 0, 10, 10)) - }) - }) -}) diff --git a/packages/x6-vector/src/struct/path-array.ts b/packages/x6-vector/src/struct/path-array.ts deleted file mode 100644 index b5e6ae652c0..00000000000 --- a/packages/x6-vector/src/struct/path-array.ts +++ /dev/null @@ -1,110 +0,0 @@ -import type { Path } from '../vector/path/path' -import { withPathContext } from '../util/context' -import { parse, toString } from '../vector/path/util' -import { Box } from './box' -import { TypeArray } from './type-array' -import { UnitNumber } from './unit-number' - -export class PathArray extends TypeArray { - parse(d: string | number[] | PathArray | Path.Segment[] = 'M0 0') { - return d instanceof PathArray - ? d.toArray() - : parse( - Array.isArray(d) ? Array.prototype.concat.apply([], d).toString() : d, - ) - } - - bbox() { - return withPathContext((path) => { - path.setAttribute('d', this.toString()) - return new Box(path.getBBox()) - }) - } - - move(x?: number | string, y?: number | string) { - const box = this.bbox() - const dx = typeof x === 'undefined' ? NaN : UnitNumber.toNumber(x) - box.x - const dy = typeof y === 'undefined' ? NaN : UnitNumber.toNumber(y) - box.y - - if (!Number.isNaN(dx) && !Number.isNaN(dy)) { - for (let i = this.length - 1; i >= 0; i -= 1) { - const seg = this[i] - const cmd = seg[0] - - if (cmd === 'M' || cmd === 'L' || cmd === 'T') { - seg[1] += dx - seg[2] += dy - } else if (cmd === 'H') { - seg[1] += dx - } else if (cmd === 'V') { - seg[1] += dy - } else if (cmd === 'C' || cmd === 'S' || cmd === 'Q') { - seg[1] += dx - seg[2] += dy - seg[3] += dx - seg[4] += dy - - if (cmd === 'C') { - seg[5] += dx - seg[6] += dy - } - } else if (cmd === 'A') { - seg[6] += dx - seg[7] += dy - } - } - } - - return this - } - - size(width: number | string, height: number | string) { - const box = this.bbox() - const w = UnitNumber.toNumber(width) - const h = UnitNumber.toNumber(height) - - // If the box width or height is 0 then we ignore - // transformations on the respective axis - box.width = box.width === 0 ? 1 : box.width - box.height = box.height === 0 ? 1 : box.height - - // recalculate position of all points according to new size - for (let i = this.length - 1; i >= 0; i -= 1) { - const seg = this[i] - const cmd = seg[0] - - if (cmd === 'M' || cmd === 'L' || cmd === 'T') { - seg[1] = ((seg[1] - box.x) * w) / box.width + box.x - seg[2] = ((seg[2] - box.y) * h) / box.height + box.y - } else if (cmd === 'H') { - seg[1] = ((seg[1] - box.x) * w) / box.width + box.x - } else if (cmd === 'V') { - seg[1] = ((seg[1] - box.y) * h) / box.height + box.y - } else if (cmd === 'C' || cmd === 'S' || cmd === 'Q') { - seg[1] = ((seg[1] - box.x) * w) / box.width + box.x - seg[2] = ((seg[2] - box.y) * h) / box.height + box.y - seg[3] = ((seg[3] - box.x) * w) / box.width + box.x - seg[4] = ((seg[4] - box.y) * h) / box.height + box.y - - if (cmd === 'C') { - seg[5] = ((seg[5] - box.x) * w) / box.width + box.x - seg[6] = ((seg[6] - box.y) * h) / box.height + box.y - } - } else if (cmd === 'A') { - // resize radii - seg[1] = (seg[1] * w) / box.width - seg[2] = (seg[2] * h) / box.height - - // move position values - seg[6] = ((seg[6] - box.x) * w) / box.width + box.x - seg[7] = ((seg[7] - box.y) * h) / box.height + box.y - } - } - - return this - } - - toString(): string { - return toString(this) - } -} diff --git a/packages/x6-vector/src/struct/point-array.test.ts b/packages/x6-vector/src/struct/point-array.test.ts deleted file mode 100644 index 95b4e0d87fc..00000000000 --- a/packages/x6-vector/src/struct/point-array.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { Matrix } from './matrix' -import { Point } from './point' -import { PointArray } from './point-array' - -describe('PointArray', () => { - const squareString = '0,0 1,0 1,1 0,1' - - describe('constructor()', () => { - it('should parse a string to a point array', () => { - const array = new PointArray('0,1 -.05,7.95 1000.0001,-200.222') - expect(array.valueOf()).toEqual([ - [0, 1], - [-0.05, 7.95], - [1000.0001, -200.222], - ]) - }) - - it('should parse a points array correctly to string', () => { - const array = new PointArray([ - [0, 0.15], - [-100, -3.141592654], - [50, 100], - ]) - expect(`${array}`).toBe('0,0.15 -100,-3.141592654 50,100') - }) - - it('should parse a flat array of x/y coordinates to a point array', () => { - const array = new PointArray([1, 4, 5, 68, 12, 24]) - expect(array.valueOf()).toEqual([ - [1, 4], - [5, 68], - [12, 24], - ]) - }) - - it('should parse points with space delimitered x/y coordinates', () => { - const array = new PointArray( - '221.08 191.79 0.46 191.79 0.46 63.92 63.8 0.46 284.46 0.46 284.46 128.37 221.08 191.79', - ) - expect(`${array}`).toBe( - '221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79', - ) - }) - - it('should parse points with comma delimitered x/y coordinates', () => { - const array = new PointArray( - '221.08,191.79,0.46,191.79,0.46,63.92,63.8,0.46,284.46,0.46,284.46,128.37,221.08,191.79', - ) - expect(`${array}`).toBe( - '221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79', - ) - }) - - it('should parse points with comma and space delimitered x/y coordinates', () => { - const array = new PointArray( - '221.08, 191.79, 0.46, 191.79, 0.46, 63.92, 63.8, 0.46, 284.46, 0.46, 284.46, 128.37, 221.08, 191.79', - ) - expect(`${array}`).toBe( - '221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79', - ) - }) - - it('should parse points with space and comma delimitered x/y coordinates', () => { - const array = new PointArray( - '221.08 ,191.79 ,0.46 ,191.79 ,0.46 ,63.92 ,63.8 ,0.46 ,284.46 ,0.46 ,284.46 ,128.37 ,221.08 ,191.79', - ) - expect(`${array}`).toBe( - '221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79', - ) - }) - - it('should parse points with redundant spaces at the end', () => { - const array = new PointArray( - '2176.6,1708.8 2176.4,1755.8 2245.8,1801.5 2297,1787.8 ', - ) - expect(`${array}`).toBe( - '2176.6,1708.8 2176.4,1755.8 2245.8,1801.5 2297,1787.8', - ) - }) - - it('should parse points with space delimitered x/y coordinates - even with leading or trailing space', () => { - const array = new PointArray(' 1 2 3 4 ') - expect(`${array}`).toBe('1,2 3,4') - }) - - it('should parse odd number of points with space delimitered x/y coordinates and silently remove the odd point', () => { - // this is according to spec: https://svgwg.org/svg2-draft/shapes.html#DataTypePoints - const array = new PointArray('1 2 3') - expect(`${array}`).toBe('1,2') - }) - - it('should parse odd number of points in a flat array of x/y coordinates and silently remove the odd point', () => { - // this is according to spec: https://svgwg.org/svg2-draft/shapes.html#DataTypePoints - const array = new PointArray([1, 2, 3]) - expect(array.valueOf()).toEqual([[1, 2]]) - }) - }) - - describe('move()', () => { - it('should move the whole array by the passed value', () => { - const arr = new PointArray([1, 2, 3, 4]).move(10, 10) - expect(arr.toArray()).toEqual([ - [10, 10], - [12, 12], - ]) - }) - - it('should do nothing if values not numbers', () => { - const arr = new PointArray([1, 2, 3, 4]).move() - expect(arr.toArray()).toEqual([ - [1, 2], - [3, 4], - ]) - }) - }) - - describe('size()', () => { - it('should rerurn the correct size of the points over the whole area', () => { - const array = new PointArray([10, 10, 20, 20, 30, 30]) - expect(array.size(60, 60).valueOf()).toEqual([ - [10, 10], - [40, 40], - [70, 70], - ]) - }) - - it('should keep let coordinates untouched when width/height is zero', () => { - let array = new PointArray([10, 10, 10, 20, 10, 30]) - expect(array.size(60, 60).valueOf()).toEqual([ - [10, 10], - [10, 40], - [10, 70], - ]) - - array = new PointArray([10, 10, 20, 10, 30, 10]) - expect(array.size(60, 60).valueOf()).toEqual([ - [10, 10], - [40, 10], - [70, 10], - ]) - }) - }) - - describe('transform()', () => { - it('should translate correctly', () => { - const square = new PointArray(squareString) - const translation = new Matrix({ translate: [2, 1] }) - const newSquare = square.transform(translation) - expect(newSquare.toString()).toEqual('2,1 3,1 3,2 2,2') - }) - - it('transforms like Point', () => { - const square = new PointArray(squareString) - const matrix = new Matrix(1, 2, 3, 4, 5, 6) - const newSquare = square.transform(matrix) - for (let i = 0; i < square.length; i += 1) { - const squarePoint = new Point(square[i]) - const newSquarePoint = new Point(newSquare[i]) - expect(squarePoint.transform(matrix)).toEqual(newSquarePoint) - } - }) - - it('should work with transform object instead of matrix', () => { - const square = new PointArray(squareString) - const newSquare = square.transform({ translate: [2, 1] }) - expect(newSquare.toString()).toEqual('2,1 3,1 3,2 2,2') - }) - }) - - describe('toString()', () => { - it('should convert to comma sepereated list', () => { - const square = new PointArray(squareString) - expect(square.toString()).toEqual(squareString) - }) - }) -}) diff --git a/packages/x6-vector/src/struct/point-array.ts b/packages/x6-vector/src/struct/point-array.ts deleted file mode 100644 index aa3c044b91e..00000000000 --- a/packages/x6-vector/src/struct/point-array.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Box } from './box' -import { Matrix } from './matrix' -import { TypeArray } from './type-array' -import { UnitNumber } from './unit-number' - -export class PointArray extends TypeArray<[number, number]> { - parse(raw: string | [number, number][] | number[] = [0, 0]) { - const array: number[] = Array.isArray(raw) - ? Array.prototype.concat.apply([], raw) - : raw - .trim() - .split(/[\s,]+/) - .map((str) => Number.parseFloat(str)) - - if (array.length % 2 !== 0) { - array.pop() - } - - const points: [number, number][] = [] - for (let i = 0, l = array.length; i < l; i += 2) { - points.push([array[i], array[i + 1]]) - } - - return points - } - - bbox() { - let maxX = Number.NEGATIVE_INFINITY - let maxY = Number.NEGATIVE_INFINITY - let minX = Number.POSITIVE_INFINITY - let minY = Number.POSITIVE_INFINITY - - this.forEach((p) => { - maxX = Math.max(p[0], maxX) - maxY = Math.max(p[1], maxY) - minX = Math.min(p[0], minX) - minY = Math.min(p[1], minY) - }) - - return new Box(minX, minY, maxX - minX, maxY - minY) - } - - move(x?: number | string, y?: number | string) { - const box = this.bbox() - const dx = typeof x === 'undefined' ? NaN : UnitNumber.toNumber(x) - box.x - const dy = typeof y === 'undefined' ? NaN : UnitNumber.toNumber(y) - box.y - - if (!Number.isNaN(dx) && !Number.isNaN(dy)) { - for (let i = this.length - 1; i >= 0; i -= 1) { - this[i] = [this[i][0] + dx, this[i][1] + dy] - } - } - - return this - } - - size(width: number | string, height: number | string) { - const box = this.bbox() - const w = UnitNumber.toNumber(width) - const h = UnitNumber.toNumber(height) - - for (let i = this.length - 1; i >= 0; i -= 1) { - if (box.width) { - this[i][0] = ((this[i][0] - box.x) * w) / box.width + box.x - } - - if (box.height) { - this[i][1] = ((this[i][1] - box.y) * h) / box.height + box.y - } - } - - return this - } - - toString() { - return this.map((item) => item.join(',')).join(' ') - } - - transform(matrix: Matrix.Raw) { - return this.clone().transformO(matrix) - } - - transformO(matrix: Matrix.Raw) { - const m = Matrix.isMatrixLike(matrix) ? matrix : new Matrix(matrix) - for (let i = this.length - 1; i >= 0; i -= 1) { - const [x, y] = this[i] - this[i][0] = m.a * x + m.c * y + m.e - this[i][1] = m.b * x + m.d * y + m.f - } - return this - } -} diff --git a/packages/x6-vector/src/struct/point.test.ts b/packages/x6-vector/src/struct/point.test.ts deleted file mode 100644 index 9f7d54946f7..00000000000 --- a/packages/x6-vector/src/struct/point.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Point } from './point' -import { Matrix } from './matrix' - -describe('Point', () => { - describe('constructor()', () => { - it('should create a point with default values', () => { - const point = new Point() - expect(point.x).toBe(0) - expect(point.y).toBe(0) - }) - - it('should create a point with given x and y values', () => { - const point = new Point(2, 4) - expect(point.x).toBe(2) - expect(point.y).toBe(4) - }) - - it('should create a point with only x value and set the y value to 0', () => { - const point = new Point(7) - expect(point.x).toBe(7) - expect(point.y).toBe(0) - }) - - it('should create a point from array', () => { - const point = new Point([2, 4]) - expect(point.x).toBe(2) - expect(point.y).toBe(4) - }) - - it('should create a point from object', () => { - const point = new Point({ x: 2, y: 4 }) - expect(point.x).toBe(2) - expect(point.y).toBe(4) - }) - - it('should create a point from Point', () => { - const point = new Point(new Point(2, 4)) - expect(point.x).toBe(2) - expect(point.y).toBe(4) - }) - }) - - describe('transform()', () => { - it('should transform point with a matrix', () => { - expect( - new Point().transform(new Matrix({ translate: [10, 10] })), - ).toEqual(new Point(10, 10)) - }) - - it('should transform a point with a transformation object', () => { - expect(new Point().transform({ translate: [10, 10] })).toEqual( - new Point(10, 10), - ) - }) - }) - - describe('clone()', () => { - it('should return the cloned point', () => { - const point1 = new Point(1, 1) - const point2 = point1.clone() - expect(point1).toEqual(point2) - expect(point1).not.toBe(point2) - }) - }) - - describe('toArray()', () => { - it('should create an array representation of Point', () => { - const p = new Point(1, 2) - expect(p.toArray()).toEqual([1, 2]) - }) - }) - - describe('valueOf()', () => { - it('should create an array representation of Point', () => { - const p = new Point(1, 2) - expect(p.valueOf()).toEqual([1, 2]) - }) - }) - - describe('toJSON()', () => { - it('should create an object representation of Point', () => { - const obj1 = new Point().toJSON() - expect(obj1.x).toBe(0) - expect(obj1.y).toBe(0) - - const obj2 = new Point(1, 2).toJSON() - expect(obj2.x).toBe(1) - expect(obj2.y).toBe(2) - }) - }) -}) diff --git a/packages/x6-vector/src/struct/point.ts b/packages/x6-vector/src/struct/point.ts deleted file mode 100644 index fc88f382ec3..00000000000 --- a/packages/x6-vector/src/struct/point.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Matrix } from './matrix' - -export class Point implements Point.PointLike { - public x: number - public y: number - - constructor() - constructor(p: Point.PointLike) - constructor(array: Point.PointArray) - constructor(x: number, y: number) - constructor(x?: number | Point.PointLike | Point.PointArray, y?: number) - constructor(x?: number | Point.PointLike | Point.PointArray, y?: number) { - const source = Array.isArray(x) - ? { x: x[0], y: x[1] } - : typeof x === 'object' - ? { x: x.x, y: x.y } - : { x, y } - - this.x = source.x == null ? 0 : source.x - this.y = source.y == null ? 0 : source.y - } - - clone() { - return new Point(this) - } - - toJSON(): Point.PointLike { - return { x: this.x, y: this.y } - } - - toArray(): Point.PointArray { - return [this.x, this.y] - } - - toString() { - return `${this.x} ${this.y}` - } - - valueOf() { - return this.toArray() - } - - transform(matrix: Matrix.Raw) { - return this.clone().transformO(matrix) - } - - transformO(matrix: Matrix.Raw) { - const { x, y } = this - const m = Matrix.isMatrixLike(matrix) ? matrix : new Matrix(matrix) - - this.x = m.a * x + m.c * y + m.e - this.y = m.b * x + m.d * y + m.f - - return this - } -} - -export namespace Point { - export interface PointLike { - x: number - y: number - } - - export type PointArray = [number, number] -} diff --git a/packages/x6-vector/src/struct/type-array.ts b/packages/x6-vector/src/struct/type-array.ts deleted file mode 100644 index 73b259d07c5..00000000000 --- a/packages/x6-vector/src/struct/type-array.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { applyMixins } from '../util/mixin' - -export abstract class TypeArray { - constructor() - constructor(arrayLength: number) - constructor(arrayString: string) - constructor(items: T[] | I) - constructor(instance: TypeArray) - constructor(...args: any[]) { - if (args.length === 0) { - return - } - - if (args.length === 1 && typeof args[0] === 'number') { - this.length = args[0] - return - } - - this.push(...this.parse(args.length === 1 ? args[0] : args)) - } - - abstract parse(raw?: I | T[]): T[] - - clone(): this { - const Ctor = this.constructor as any - return new Ctor(this.toArray()) - } - - toArray(): T[] { - return [...this] - } - - toSet() { - return new Set(this.toArray()) - } -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export interface TypeArray extends Array {} - -export namespace TypeArray { - applyMixins(TypeArray as any, Array) - - TypeArray.prototype.valueOf = function () { - return this.toArray() - } - - TypeArray.prototype.toString = function () { - return this.join(' ') - } -} diff --git a/packages/x6-vector/src/struct/unit-number.test.ts b/packages/x6-vector/src/struct/unit-number.test.ts deleted file mode 100644 index 1d9cad6a98e..00000000000 --- a/packages/x6-vector/src/struct/unit-number.test.ts +++ /dev/null @@ -1,529 +0,0 @@ -import { UnitNumber } from './unit-number' - -describe('UnitNumber', () => { - let number: UnitNumber - - beforeEach(() => { - number = new UnitNumber() - }) - - describe('constructor()', () => { - it('should create a SVGNumber with default values', () => { - expect(number.value).toBe(0) - expect(number.unit).toBe('') - }) - - it('should create a SVGNumber from array', () => { - number = new UnitNumber([30, '%']) - expect(number.value).toBe(30) - expect(number.unit).toBe('%') - }) - - it('should create a SVGNumber from object', () => { - number = new UnitNumber({ value: 30, unit: '%' }) - expect(number.value).toBe(30) - expect(number.unit).toBe('%') - }) - - it('should accept the unit as a second argument', () => { - number = new UnitNumber(30, '%') - expect(number.value).toBe(30) - expect(number.unit).toBe('%') - }) - - it('should parse a pixel value', () => { - number = new UnitNumber('20px') - expect(number.value).toBe(20) - expect(number.unit).toBe('px') - }) - - it('should parse a percent value', () => { - number = new UnitNumber('99%') - expect(number.value).toBe(0.99) - expect(number.unit).toBe('%') - }) - - it('should parse a seconds value', () => { - number = new UnitNumber('2s') - expect(number.value).toBe(2000) - expect(number.unit).toBe('s') - }) - - it('should parse a negative percent value', () => { - number = new UnitNumber('-89%') - expect(number.value).toBe(-0.89) - expect(number.unit).toBe('%') - }) - - it('should fall back to 0 if given value is NaN', () => { - number = new UnitNumber(NaN) - expect(number.value).toBe(0) - }) - - it('should fall back to maximum value if given number is positive infinite', () => { - number = new UnitNumber(1.7976931348623157e10308) - expect(number.value).toBe(3.4e38) - }) - - it('should fall back to minimum value if given number is negative infinite', () => { - number = new UnitNumber(-1.7976931348623157e10308) - expect(number.value).toBe(-3.4e38) - }) - }) - - describe('toString()', () => { - it('should convert the number to a string', () => { - expect(number.toString()).toBe('0') - }) - - it('should append the unit', () => { - number.value = 1.21 - number.unit = 'px' - expect(number.toString()).toBe('1.21px') - }) - - it('should convert percent values properly', () => { - number.value = 1.36 - number.unit = '%' - expect(number.toString()).toBe('136%') - }) - - it('should convert second values properly', () => { - number.value = 2500 - number.unit = 's' - expect(number.toString()).toBe('2.5s') - }) - }) - - describe('toJSON()', () => { - it('should create an object representation of SVGNumver', () => { - const obj1 = number.toJSON() - expect(obj1.value).toBe(0) - expect(obj1.unit).toBe('') - - const obj2 = new UnitNumber(1, 'px').toJSON() - expect(obj2.value).toBe(1) - expect(obj2.unit).toBe('px') - }) - }) - - describe('toArray()', () => { - it('should create an array representation of SVGNumver', () => { - const arr1 = number.toArray() - expect(arr1[0]).toBe(0) - expect(arr1[1]).toBe('') - - const arr2 = new UnitNumber(1, 'px').toArray() - expect(arr2[0]).toBe(1) - expect(arr2[1]).toBe('px') - }) - }) - - describe('valueOf()', () => { - it('should return a numeric value for default units', () => { - expect(number.valueOf()).toBe(0) - number = new UnitNumber('12') - expect(number.valueOf()).toBe(12) - number = new UnitNumber(13) - expect(number.valueOf()).toBe(13) - }) - - it('should return a numeric value for pixel units', () => { - number = new UnitNumber('10px') - expect(number.valueOf()).toBe(10) - }) - - it('should return a numeric value for percent units', () => { - number = new UnitNumber('20%') - expect(number.valueOf()).toBe(0.2) - }) - - it('should convert to a primitive when multiplying', () => { - number.value = 80 - expect((number as any) * 4).toBe(320) - }) - }) - - describe('plus()', () => { - it('should return a new instance', () => { - expect(number.plus(4.5)).not.toBe(number) - expect(number.plus(4.5) === number).toBeFalse() - }) - - it('should add a given number', () => { - expect(number.plus(3.5).valueOf()).toBe(3.5) - }) - - it('should add a given percentage value', () => { - expect(number.plus('225%').valueOf()).toBe(2.25) - }) - - it('should add a given pixel value', () => { - expect(number.plus('83px').valueOf()).toBe(83) - }) - - it('should use the unit of this number as the unit of the returned number by default', () => { - expect(new UnitNumber('12s').plus('3%').unit).toBe('s') - }) - - it('should use the unit of the passed number as the unit of the returned number when this number as no unit', () => { - expect(number.plus('15%').unit).toBe('%') - }) - }) - - describe('minus()', () => { - it('should subtract a given number', () => { - expect(number.minus(3.7).valueOf()).toBe(-3.7) - }) - - it('should subtract a given percentage value', () => { - expect(number.minus('223%').valueOf()).toBe(-2.23) - }) - - it('should subtract a given pixel value', () => { - expect(number.minus('85px').valueOf()).toBe(-85) - }) - - it('should use the unit of this number as the unit of the returned number by default', () => { - expect(new UnitNumber('12s').minus('3%').unit).toBe('s') - }) - - it('should use the unit of the passed number as the unit of the returned number when this number as no unit', () => { - expect(number.minus('15%').unit).toBe('%') - }) - }) - - describe('times()', () => { - beforeEach(() => { - number = number.plus(4) - }) - - it('should multiplie with a given number', () => { - expect(number.times(3).valueOf()).toBe(12) - }) - - it('should multiplie with a given percentage value', () => { - expect(number.times('110%').valueOf()).toBe(4.4) - }) - - it('should multiplie with a given pixel value', () => { - expect(number.times('85px').valueOf()).toBe(340) - }) - - it('should use the unit of this number as the unit of the returned number by default', () => { - expect(new UnitNumber('12s').times('3%').unit).toBe('s') - }) - - it('should use the unit of the passed number as the unit of the returned number when this number as no unit', () => { - expect(number.times('15%').unit).toBe('%') - }) - }) - - describe('divide()', () => { - beforeEach(() => { - number = number.plus(90) - }) - - it('should divide by a given number', () => { - expect(number.divide(3).valueOf()).toBe(30) - }) - - it('should divide by a given percentage value', () => { - expect(number.divide('3000%').valueOf()).toBe(3) - }) - - it('should divide by a given pixel value', () => { - expect(number.divide('45px').valueOf()).toBe(2) - }) - - it('should use the unit of this number as the unit of the returned number by default', () => { - expect(new UnitNumber('12s').divide('3%').unit).toBe('s') - }) - - it('should use the unit of the passed number as the unit of the returned number when this number as no unit', () => { - expect(number.divide('15%').unit).toBe('%') - }) - }) - - describe('unitize()', () => { - it('should change the unit of the number', () => { - const number = new UnitNumber('12px').unitize('%') - expect(number.toString()).toBe('1200%') - }) - }) - - describe('clone()', () => { - it('should clone an UnitNumber', () => { - const num1 = new UnitNumber() - const clone1 = num1.clone() - expect(clone1).not.toBe(num1) - expect(clone1.unit).toBe('') - expect(clone1.value).toBe(0) - - const num2 = new UnitNumber('1px') - const clone2 = num2.clone() - expect(clone2).not.toBe(num2) - expect(clone2.unit).toBe('px') - expect(clone2.value).toBe(1) - }) - }) - - describe('Static Methods', () => { - describe('create()', () => { - it('should create a SVGNumber with default values', () => { - number = UnitNumber.create() - expect(number.value).toBe(0) - expect(number.unit).toBe('') - }) - - it('should create a SVGNumber from array', () => { - number = UnitNumber.create([30, '%']) - expect(number.value).toBe(30) - expect(number.unit).toBe('%') - }) - - it('should create a SVGNumber from object', () => { - number = UnitNumber.create({ value: 30, unit: '%' }) - expect(number.value).toBe(30) - expect(number.unit).toBe('%') - }) - - it('should accept the unit as a second argument', () => { - number = UnitNumber.create(30, '%') - expect(number.value).toBe(30) - expect(number.unit).toBe('%') - }) - - it('should parse a pixel value', () => { - number = UnitNumber.create('20px') - expect(number.value).toBe(20) - expect(number.unit).toBe('px') - }) - - it('should parse a percent value', () => { - number = UnitNumber.create('99%') - expect(number.value).toBe(0.99) - expect(number.unit).toBe('%') - }) - - it('should parse a seconds value', () => { - number = UnitNumber.create('2s') - expect(number.value).toBe(2000) - expect(number.unit).toBe('s') - }) - - it('should parse a negative percent value', () => { - number = UnitNumber.create('-89%') - expect(number.value).toBe(-0.89) - expect(number.unit).toBe('%') - }) - - it('should fall back to 0 if given value is NaN', () => { - number = UnitNumber.create(NaN) - expect(number.value).toBe(0) - }) - - it('should fall back to maximum value if given number is positive infinite', () => { - number = UnitNumber.create(1.7976931348623157e10308) - expect(number.value).toBe(3.4e38) - }) - - it('should fall back to minimum value if given number is negative infinite', () => { - number = UnitNumber.create(-1.7976931348623157e10308) - expect(number.value).toBe(-3.4e38) - }) - }) - - describe('toNumber()', () => { - it('should return it if given value is a number', () => { - expect(UnitNumber.toNumber(1)).toBe(1) - }) - - it('should conver it to number take unit into account if given value is a string', () => { - expect(UnitNumber.toNumber('1')).toBe(1) - expect(UnitNumber.toNumber('1%')).toBe(0.01) - expect(UnitNumber.toNumber('1s')).toBe(1000) - expect(UnitNumber.toNumber('1px')).toBe(1) - }) - - it('should ignore unknown unit', () => { - expect(UnitNumber.toNumber('1k')).toBe(1) - }) - - it('should fall back to 0 if given value is NaN', () => { - expect(UnitNumber.toNumber(NaN)).toBe(0) - }) - - it('should fall back to maximum value if given number is positive infinite', () => { - expect(UnitNumber.toNumber(1.7976931348623157e10308)).toBe(3.4e38) - }) - - it('should fall back to minimum value if given number is negative infinite', () => { - expect(UnitNumber.toNumber(-1.7976931348623157e10308)).toBe(-3.4e38) - }) - }) - - describe('plus()', () => { - it('should plus with two number values', () => { - expect(UnitNumber.plus(1, 2)).toBe(3) - }) - - it('should plus with a number and a string take unit into account', () => { - expect(UnitNumber.plus(1, '2')).toBe(3) - expect(UnitNumber.plus('1', 2)).toBe(3) - - expect(UnitNumber.plus(1, '2%')).toBe('102%') - expect(UnitNumber.plus('1%', 2)).toBe('200.999999%') - - expect(UnitNumber.plus(1, '2s')).toBe('2.001s') - expect(UnitNumber.plus('1s', 2)).toBe('1.002s') - - expect(UnitNumber.plus(1, '2px')).toBe('3px') - expect(UnitNumber.plus('1px', 2)).toBe('3px') - }) - - it('should plus with two strings take unit into account', () => { - expect(UnitNumber.plus('1', '2')).toBe(3) - expect(UnitNumber.plus('1', 2)).toBe(3) - - expect(UnitNumber.plus('1', '2%')).toBe('102%') - expect(UnitNumber.plus('1%', '2')).toBe('200.999999%') - - expect(UnitNumber.plus('1', '2s')).toBe('2.001s') - expect(UnitNumber.plus('1s', '2')).toBe('1.002s') - - expect(UnitNumber.plus('1', '2px')).toBe('3px') - expect(UnitNumber.plus('1px', '2')).toBe('3px') - - expect(UnitNumber.plus('1s', '2%')).toBe('1.00002s') - expect(UnitNumber.plus('1px', '2s')).toBe('2001px') - }) - }) - - describe('minus()', () => { - it('should minus with two number values', () => { - expect(UnitNumber.minus(1, 2)).toBe(-1) - }) - - it('should minus with a number and a string take unit into account', () => { - expect(UnitNumber.minus(1, '2')).toBe(-1) - expect(UnitNumber.minus('1', 2)).toBe(-1) - - expect(UnitNumber.minus(1, '2%')).toBe('98%') - expect(UnitNumber.minus('1%', 2)).toBe('-199%') - - expect(UnitNumber.minus(1, '2s')).toBe('-1.999s') - expect(UnitNumber.minus('1s', 2)).toBe('0.998s') - - expect(UnitNumber.minus(1, '2px')).toBe('-1px') - expect(UnitNumber.minus('1px', 2)).toBe('-1px') - }) - - it('should minus with two strings take unit into account', () => { - expect(UnitNumber.minus('1', '2')).toBe(-1) - expect(UnitNumber.minus('1', 2)).toBe(-1) - - expect(UnitNumber.minus('1', '2%')).toBe('98%') - expect(UnitNumber.minus('1%', '2')).toBe('-199%') - - expect(UnitNumber.minus('1', '2s')).toBe('-1.999s') - expect(UnitNumber.minus('1s', '2')).toBe('0.998s') - - expect(UnitNumber.minus('1', '2px')).toBe('-1px') - expect(UnitNumber.minus('1px', '2')).toBe('-1px') - - expect(UnitNumber.minus('1s', '2%')).toBe('0.99998s') - expect(UnitNumber.minus('1px', '2s')).toBe('-1999px') - }) - }) - - describe('times()', () => { - it('should times with two number values', () => { - expect(UnitNumber.times(1, 2)).toBe(2) - }) - - it('should times with a number and a string take unit into account', () => { - expect(UnitNumber.times(1, '2')).toBe(2) - expect(UnitNumber.times('1', 2)).toBe(2) - - expect(UnitNumber.times(1, '2%')).toBe('2%') - expect(UnitNumber.times('1%', 2)).toBe('2%') - - expect(UnitNumber.times(1, '2s')).toBe('2s') - expect(UnitNumber.times('1s', 2)).toBe('2s') - - expect(UnitNumber.times(1, '2px')).toBe('2px') - expect(UnitNumber.times('1px', 2)).toBe('2px') - }) - - it('should times with two strings take unit into account', () => { - expect(UnitNumber.times('1', '2')).toBe(2) - expect(UnitNumber.times('1', 2)).toBe(2) - - expect(UnitNumber.times('1', '2%')).toBe('2%') - expect(UnitNumber.times('1%', '2')).toBe('2%') - - expect(UnitNumber.times('1', '2s')).toBe('2s') - expect(UnitNumber.times('1s', '2')).toBe('2s') - - expect(UnitNumber.times('1', '2px')).toBe('2px') - expect(UnitNumber.times('1px', '2')).toBe('2px') - - expect(UnitNumber.times('1s', '2%')).toBe('0.02s') - expect(UnitNumber.times('1px', '2s')).toBe('2000px') - }) - }) - - describe('divide()', () => { - it('should divide with two number values', () => { - expect(UnitNumber.divide(1, 2)).toBe(0.5) - }) - - it('should divide with a number and a string take unit into account', () => { - expect(UnitNumber.divide(1, '2')).toBe(0.5) - expect(UnitNumber.divide('1', 2)).toBe(0.5) - - expect(UnitNumber.divide(1, '2%')).toBe('5000%') - expect(UnitNumber.divide('1%', 2)).toBe('0.5%') - - expect(UnitNumber.divide(1, '2s')).toBe('5e-7s') - expect(UnitNumber.divide('1s', 2)).toBe('0.5s') - - expect(UnitNumber.divide(1, '2px')).toBe('0.5px') - expect(UnitNumber.divide('1px', 2)).toBe('0.5px') - }) - - it('should divide with two strings take unit into account', () => { - expect(UnitNumber.divide('1', '2')).toBe(0.5) - expect(UnitNumber.divide('1', 2)).toBe(0.5) - - expect(UnitNumber.divide('1', '2%')).toBe('5000%') - expect(UnitNumber.divide('1%', '2')).toBe('0.5%') - - expect(UnitNumber.divide('1', '2s')).toBe('5e-7s') - expect(UnitNumber.divide('1s', '2')).toBe('0.5s') - - expect(UnitNumber.divide('1', '2px')).toBe('0.5px') - expect(UnitNumber.divide('1px', '2')).toBe('0.5px') - - expect(UnitNumber.divide('1s', '2%')).toBe('50s') - expect(UnitNumber.divide('1px', '2s')).toBe('0.0005px') - }) - }) - - describe('parse()', () => { - it('should parse a string to UnitNumberLike object', () => { - expect(UnitNumber.parse('1')).toEqual({ value: 1, unit: '' }) - expect(UnitNumber.parse('1px')).toEqual({ value: 1, unit: 'px' }) - expect(UnitNumber.parse('1t')).toEqual({ value: 1, unit: 't' }) - expect(UnitNumber.parse('1%')).toEqual({ value: 0.01, unit: '%' }) - expect(UnitNumber.parse('1s')).toEqual({ value: 1000, unit: 's' }) - }) - - it('should return null when can not parse the given string ', () => { - expect(UnitNumber.parse('')).toBeNull() - expect(UnitNumber.parse('px')).toBeNull() - }) - }) - }) -}) diff --git a/packages/x6-vector/src/struct/unit-number.ts b/packages/x6-vector/src/struct/unit-number.ts deleted file mode 100644 index b1b603a1149..00000000000 --- a/packages/x6-vector/src/struct/unit-number.ts +++ /dev/null @@ -1,175 +0,0 @@ -export class UnitNumber implements UnitNumber.UnitNumberLike { - public value: number - public unit: string - - constructor() - constructor( - value: number | string | UnitNumber.UnitNumberLike | null | undefined, - unit?: string, - ) - constructor(array: [number | string, string?]) - constructor(arg1?: UnitNumber.Raw, arg2?: string) { - const value = Array.isArray(arg1) ? arg1[0] : arg1 - const unit = Array.isArray(arg1) ? arg1[1] : arg2 - - this.value = 0 - this.unit = unit || '' - - if (value != null) { - if (typeof value === 'number') { - this.value = UnitNumber.normalize(value) - } else if (typeof value === 'string') { - const obj = UnitNumber.parse(value) - if (obj) { - this.value = obj.value - this.unit = obj.unit - } - } else if (typeof value === 'object') { - this.value = value.value - this.unit = value.unit - } - } - } - - minus(number: UnitNumber.Raw) { - const input = UnitNumber.create(number) - return new UnitNumber(this.value - input.value, this.unit || input.unit) - } - - plus(number: UnitNumber.Raw) { - const input = UnitNumber.create(number) - return new UnitNumber(this.value + input.value, this.unit || input.unit) - } - - times(number: UnitNumber.Raw) { - const input = UnitNumber.create(number) - return new UnitNumber(this.value * input.value, this.unit || input.unit) - } - - divide(number: UnitNumber.Raw) { - const input = UnitNumber.create(number) - return new UnitNumber(this.value / input.value, this.unit || input.unit) - } - - unitize(unit: string) { - return new UnitNumber(this.value, unit) - } - - naturalize() { - return this.unit ? this.toString() : this.valueOf() - } - - clone() { - return new UnitNumber(this) - } - - toJSON(): UnitNumber.UnitNumberLike { - return { value: this.value, unit: this.unit } - } - - toArray(): UnitNumber.UnitNumberArray { - return [this.value, this.unit] - } - - toString() { - const value = - this.unit === '%' - ? Math.trunc(this.value * 1e8) / 1e6 - : this.unit === 's' - ? this.value / 1e3 - : this.value - - return `${value}${this.unit}` - } - - valueOf() { - return this.value - } -} - -export namespace UnitNumber { - export type Raw = - | undefined - | null - | number - | string - | UnitNumberLike - | [number | string, string?] - - export interface UnitNumberLike { - value: number - unit: string - } - - export type UnitNumberArray = [number, string] - - export function create(): UnitNumber - export function create(number: number, unit?: string): UnitNumber - export function create(number: Raw): UnitNumber - export function create(number?: Raw, unit?: string) { - return Array.isArray(number) - ? new UnitNumber(number[0], number[1]) - : new UnitNumber(number, unit) - } - - export function plus(left: Raw, right: Raw) { - if (typeof left === 'number' && typeof right === 'number') { - return left + right - } - return create(left).plus(right).naturalize() - } - - export function minus(left: Raw, right: Raw) { - if (typeof left === 'number' && typeof right === 'number') { - return left - right - } - return create(left).minus(right).naturalize() - } - - export function times(left: Raw, right: Raw) { - if (typeof left === 'number' && typeof right === 'number') { - return left * right - } - return create(left).times(right).naturalize() - } - - export function divide(left: Raw, right: Raw) { - if (typeof left === 'number' && typeof right === 'number') { - return left / right - } - return create(left).divide(right).naturalize() - } - - export function normalize(v: number) { - return Number.isNaN(v) - ? 0 - : !Number.isFinite(v) - ? v < 0 - ? -3.4e38 - : +3.4e38 - : v - } - - export const REGEX = /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i - - export function parse(str: string): UnitNumberLike | null { - const matches = str.match(REGEX) - if (matches) { - let value = normalize(Number.parseFloat(matches[1])) - const unit = matches[5] || '' - - // normalize - if (unit === '%') { - value /= 100 - } else if (unit === 's') { - value *= 1000 - } - return { value, unit } - } - return null - } - - export function toNumber(v: number | string) { - return typeof v === 'number' ? normalize(v) : create(v).valueOf() - } -} diff --git a/packages/x6-vector/src/struct/util.ts b/packages/x6-vector/src/struct/util.ts deleted file mode 100644 index 38d51944b73..00000000000 --- a/packages/x6-vector/src/struct/util.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function clamp(value: number, min: number, max: number) { - return Math.max(min, Math.min(max, value)) -} diff --git a/packages/x6-vector/src/types/common.test.ts b/packages/x6-vector/src/types/common.test.ts deleted file mode 100644 index 7a50bda6191..00000000000 --- a/packages/x6-vector/src/types/common.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as Util from './common' - -describe('types', () => { - describe('isFalsy()', () => { - it('should return true when the given value is falsy', () => { - const vals = [null, undefined, 0, false] - vals.forEach((v) => { - expect(Util.isFalsy(v)).toBeTrue() - }) - }) - - it('should return false when the given value is not falsy', () => { - const vals = [1, {}, () => {}, true] - vals.forEach((v) => { - expect(Util.isFalsy(v)).toBeFalse() - }) - }) - }) - - describe('isNullish()', () => { - it('should return true when the given value is nil', () => { - const vals = [null, undefined] - vals.forEach((v) => { - expect(Util.isNullish(v)).toBeTrue() - }) - }) - - it('should return false when the given value is not nil', () => { - const vals = [1, {}, () => {}, true] - vals.forEach((v) => { - expect(Util.isNullish(v)).toBeFalse() - }) - }) - }) - - describe('isPrimitive()', () => { - it('should return true when the given value is primitive', () => { - const vals = [null, undefined, 1, 'abc', true, Symbol('test')] - vals.forEach((v) => { - expect(Util.isPrimitive(v)).toBeTrue() - }) - }) - - it('should return true when the given value is not primitive', () => { - const vals = [{}, () => {}] - vals.forEach((v) => { - expect(Util.isPrimitive(v)).toBeFalsy() - }) - }) - }) -}) diff --git a/packages/x6-vector/src/types/common.ts b/packages/x6-vector/src/types/common.ts deleted file mode 100644 index c2e963c749d..00000000000 --- a/packages/x6-vector/src/types/common.ts +++ /dev/null @@ -1,43 +0,0 @@ -export type Primitive = - | string - | number - | bigint - | boolean - | symbol - | null - | undefined - -export const isPrimitive = (val: any): val is Primitive => { - if (val === null || val === undefined) { - return true - } - switch (typeof val) { - case 'string': - case 'number': - case 'bigint': - case 'boolean': - case 'symbol': { - return true - } - default: - return false - } -} - -export type Falsy = false | '' | 0 | null | undefined - -export const isFalsy = (val: any): val is Falsy => !val - -export type Nullish = null | undefined - -export const isNullish = (val: any): val is Nullish => val == null // eslint-disable-line eqeqeq - -export type KeyValue = Record - -// export type Attrs = Record - -export interface Class { - new (...args: Args): ReturnType -} - -export type Entity = Record diff --git a/packages/x6-vector/src/types/element.ts b/packages/x6-vector/src/types/element.ts deleted file mode 100644 index 3bfa065e5fc..00000000000 --- a/packages/x6-vector/src/types/element.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Fragment } from '../vector/fragment/fragment' - -import { A } from '../vector/a/a' -import { ClipPath } from '../vector/clippath/clippath' -import { Defs } from '../vector/defs/defs' -import { Desc } from '../vector/desc/desc' -import { G } from '../vector/g/g' -import { Stop } from '../vector/gradient/stop' -import { LinearGradient } from '../vector/gradient/linear' -import { RadialGradient } from '../vector/gradient/radial' -import { Marker } from '../vector/marker/marker' -import { Mask } from '../vector/mask/mask' -import { Pattern } from '../vector/pattern/pattern' -import { SVG } from '../vector/svg/svg' -import { Symbol } from '../vector/symbol/symbol' -import { Dom } from '../dom/dom' -import { Vector } from '../vector/vector/vector' -import { Circle } from '../vector/circle/circle' -import { Ellipse } from '../vector/ellipse/ellipse' -import { ForeignObject } from '../vector/foreignobject/foreignobject' -import { Image } from '../vector/image/image' -import { Line } from '../vector/line/line' -import { Path } from '../vector/path/path' -import { Polygon } from '../vector/polygon/polygon' -import { Polyline } from '../vector/polyline/polyline' -import { Rect } from '../vector/rect/rect' -import { Style } from '../vector/style/style' -import { Title } from '../vector/title/title' -import { Text } from '../vector/text/text' -import { TextPath } from '../vector/textpath/textpath' -import { TSpan } from '../vector/tspan/tspan' -import { Use } from '../vector/use/use' - -// prettier-ignore -export type ElementMap = - T extends SVGAElement ? A : - T extends SVGClipPathElement ? ClipPath : - T extends SVGDefsElement ? Defs : - T extends SVGDescElement ? Desc : - T extends SVGGElement ? G : - T extends SVGStopElement ? Stop : - T extends SVGLinearGradientElement ? LinearGradient : - T extends SVGRadialGradientElement ? RadialGradient : - T extends SVGMarkerElement ? Marker : - T extends SVGMaskElement ? Mask : - T extends SVGPatternElement ? Pattern : - T extends SVGSVGElement ? SVG : - T extends SVGSymbolElement ? Symbol : // eslint-disable-line - - T extends SVGCircleElement ? Circle : - T extends SVGEllipseElement ? Ellipse : - T extends SVGForeignObjectElement ? ForeignObject : - T extends DocumentFragment ? Fragment : - T extends SVGImageElement ? Image : - T extends SVGLineElement ? Line : - T extends SVGPathElement ? Path : - T extends SVGPolygonElement ? Polygon : - T extends SVGPolylineElement ? Polyline : - T extends SVGRectElement ? Rect : - T extends SVGStyleElement ? Style : - T extends SVGTitleElement ? Title : - T extends SVGTextElement ? Text : - T extends SVGTextPathElement ? TextPath : - T extends SVGTSpanElement ? TSpan : - T extends SVGUseElement ? Use : - T extends SVGElement ? Vector: - T extends HTMLElement ? Dom : Dom - -export type SVGTagNameMap = { - [K in keyof SVGElementTagNameMap]: ElementMap -} - -export type HTMLTagNameMap = { - [K in keyof HTMLElementTagNameMap]: Dom -} diff --git a/packages/x6-vector/src/types/index.ts b/packages/x6-vector/src/types/index.ts deleted file mode 100644 index ddfb4f2ba09..00000000000 --- a/packages/x6-vector/src/types/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import './lib.dom' - -export * from './common' -export * from './element' diff --git a/packages/x6-vector/src/types/lib.dom.ts b/packages/x6-vector/src/types/lib.dom.ts deleted file mode 100644 index 61ace638a0e..00000000000 --- a/packages/x6-vector/src/types/lib.dom.ts +++ /dev/null @@ -1,265 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ - -// Fix extends detection errors by adding readonly `x6_vector_type` -// ============================================================================= -// type AA = SVGForeignObjectElement extends SVGSVGElement ? true : false -// type AAA = SVGSVGElement extends SVGForeignObjectElement ? true : false - -// type BB = SVGGElement extends SVGDefsElement ? true : false -// type BBB = SVGDefsElement extends SVGGElement ? true : false - -// type CC = SVGGElement extends SVGDefsElement ? true : false -// type CCC = SVGDefsElement extends SVGGElement ? true : false - -// type DD = SVGTSpanElement extends SVGTextElement ? true : false -// type DDD = SVGTextElement extends SVGTSpanElement ? true : false - -// type EE = SVGPolylineElement extends SVGPolygonElement ? true : false -// type EEE = SVGPolygonElement extends SVGPolylineElement ? true : false -// ============================================================================= - -interface DocumentFragment extends Element, SVGElement {} - -interface SVGAElement { - readonly x6_vector_type: 'a' -} - -interface SVGAnimateElement { - readonly x6_vector_type: 'animate' -} - -interface SVGAnimateMotionElement { - readonly x6_vector_type: 'animateMotion' -} - -interface SVGAnimateTransformElement { - readonly x6_vector_type: 'animateTransform' -} - -interface SVGCircleElement { - readonly x6_vector_type: 'circle' -} - -interface SVGClipPathElement { - readonly x6_vector_type: 'clipPath' -} - -interface SVGDefsElement { - readonly x6_vector_type: 'defs' -} - -interface SVGDescElement { - readonly x6_vector_type: 'desc' -} - -interface SVGEllipseElement { - readonly x6_vector_type: 'ellipse' -} - -interface SVGFEBlendElement { - readonly x6_vector_type: 'feBlend' -} - -interface SVGFEColorMatrixElement { - readonly x6_vector_type: 'feColorMatrix' -} - -interface SVGFEComponentTransferElement { - readonly x6_vector_type: 'feComponentTransfer' -} - -interface SVGFECompositeElement { - readonly x6_vector_type: 'feComposite' -} - -interface SVGFEConvolveMatrixElement { - readonly x6_vector_type: 'feConvolveMatrix' -} - -interface SVGFEDiffuseLightingElement { - readonly x6_vector_type: 'feDiffuseLighting' -} - -interface SVGFEDisplacementMapElement { - readonly x6_vector_type: 'feDisplacementMap' -} - -interface SVGFEDistantLightElement { - readonly x6_vector_type: 'feDistantLight' -} - -interface SVGFEFloodElement { - readonly x6_vector_type: 'feFlood' -} - -interface SVGFEFuncAElement { - readonly x6_vector_type: 'feFuncA' -} - -interface SVGFEFuncBElement { - readonly x6_vector_type: 'feFuncB' -} - -interface SVGFEFuncGElement { - readonly x6_vector_type: 'feFuncG' -} - -interface SVGFEFuncRElement { - readonly x6_vector_type: 'feFuncR' -} - -interface SVGFEGaussianBlurElement { - readonly x6_vector_type: 'feGaussianBlur' -} - -interface SVGFEImageElement { - readonly x6_vector_type: 'feImage' -} - -interface SVGFEMergeElement { - readonly x6_vector_type: 'feMerge' -} - -interface SVGFEMergeNodeElement { - readonly x6_vector_type: 'feMergeNode' -} - -interface SVGFEMorphologyElement { - readonly x6_vector_type: 'feMorphology' -} - -interface SVGFEOffsetElement { - readonly x6_vector_type: 'feOffset' -} - -interface SVGFEPointLightElement { - readonly x6_vector_type: 'fePointLight' -} - -interface SVGFESpecularLightingElement { - readonly x6_vector_type: 'feSpecularLighting' -} - -interface SVGFESpotLightElement { - readonly x6_vector_type: 'feSpotLight' -} - -interface SVGFETileElement { - readonly x6_vector_type: 'feTile' -} - -interface SVGFETurbulenceElement { - readonly x6_vector_type: 'feTurbulence' -} - -interface SVGFilterElement { - readonly x6_vector_type: 'filter' -} - -interface SVGForeignObjectElement { - readonly x6_vector_type: 'foreignObject' -} - -interface SVGGElement { - readonly x6_vector_type: 'g' -} - -interface SVGImageElement { - readonly x6_vector_type: 'image' -} - -interface SVGLineElement { - readonly x6_vector_type: 'line' -} - -interface SVGLinearGradientElement { - readonly x6_vector_type: 'linearGradient' -} - -interface SVGMarkerElement { - readonly x6_vector_type: 'marker' -} - -interface SVGMaskElement { - readonly x6_vector_type: 'mask' -} - -interface SVGMetadataElement { - readonly x6_vector_type: 'metadata' -} - -interface SVGPathElement { - readonly x6_vector_type: 'path' -} - -interface SVGPatternElement { - readonly x6_vector_type: 'pattern' -} - -interface SVGPolygonElement { - readonly x6_vector_type: 'polygon' -} - -interface SVGPolylineElement { - readonly x6_vector_type: 'polyline' -} - -interface SVGRadialGradientElement { - readonly x6_vector_type: 'radialGradient' -} - -interface SVGRectElement { - readonly x6_vector_type: 'rect' -} - -interface SVGScriptElement { - readonly x6_vector_type: 'script' -} - -interface SVGStopElement { - readonly x6_vector_type: 'stop' -} - -interface SVGStyleElement { - readonly x6_vector_type: 'style' -} - -interface SVGSVGElement { - readonly x6_vector_type: 'svg' -} - -interface SVGSwitchElement { - readonly x6_vector_type: 'switch' -} - -interface SVGSymbolElement { - readonly x6_vector_type: 'symbol' -} - -interface SVGTextElement { - readonly x6_vector_type: 'text' -} - -interface SVGTextPathElement { - readonly x6_vector_type: 'textPath' -} - -interface SVGTitleElement { - readonly x6_vector_type: 'title' -} - -interface SVGTSpanElement { - readonly x6_vector_type: 'tspan' -} - -interface SVGUseElement { - readonly x6_vector_type: 'use' -} - -interface SVGViewElement { - readonly x6_vector_type: 'view' -} - -interface HTMLAbbrElement extends HTMLElement { - readonly x6_vector_type: 'abbr' -} diff --git a/packages/x6-vector/src/util/context.ts b/packages/x6-vector/src/util/context.ts deleted file mode 100644 index e349a8f6145..00000000000 --- a/packages/x6-vector/src/util/context.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Global } from '../global' -import { createSVGNode } from './dom' - -export function withSvgContext(callback: (svg: SVGSVGElement) => T) { - const svg = createSVGNode('svg') - - svg.setAttribute('width', '2') - svg.setAttribute('height', '0') - svg.setAttribute('focusable', 'false') - svg.setAttribute('aria-hidden', 'true') - - const style = svg.style - style.opacity = '0' - style.position = 'absolute' - style.left = '-100%' - style.top = '-100%' - style.overflow = 'hidden' - - const wrap = Global.document.body || Global.document.documentElement - wrap.appendChild(svg) - const ret = callback(svg) - wrap.removeChild(svg) - return ret -} - -export function withPathContext(callback: (path: SVGPathElement) => T) { - return withSvgContext((svg) => { - const path = createSVGNode('path') - svg.appendChild(path) - return callback(path) - }) -} diff --git a/packages/x6-vector/src/util/dom.ts b/packages/x6-vector/src/util/dom.ts deleted file mode 100644 index 771b864c3a2..00000000000 --- a/packages/x6-vector/src/util/dom.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Global } from '../global' - -export const namespaces = { - svg: 'http://www.w3.org/2000/svg', - html: 'http://www.w3.org/1999/xhtml', - xmlns: 'http://www.w3.org/2000/xmlns/', - xlink: 'http://www.w3.org/1999/xlink', -} - -export function createNode( - tagName: string, - ns: string, -): TElement { - return Global.document.createElementNS(ns, tagName) as TElement -} - -export function createHTMLNode( - tagName: string, -) { - return createNode(tagName, namespaces.html) -} - -export function createSVGNode( - tagName: string, -) { - return createNode(tagName, namespaces.svg) -} - -export function isNode(node: any): node is Node { - return node != null && node instanceof Global.window.Node -} - -export function isWindow(obj: any): obj is Window { - return obj != null && obj === obj.window -} - -export function isDocument(node: any): node is Document { - return node != null && node === Global.document -} - -export function isSVGSVGElement(node: any) { - return node != null && node instanceof Global.window.SVGSVGElement -} - -export function isDocumentFragment(node: any) { - return isNode(node) && node.nodeName === '#document-fragment' -} - -export function isAncestorOf(a: Node, b: Node) { - const adown = a.nodeType === 9 ? (a as any).documentElement : a - const bup = b && b.parentNode - - return ( - a === bup || - !!( - bup && - bup.nodeType === 1 && - // Support: IE 9 - 11+ - // IE doesn't have `contains` on SVG. - (adown.contains - ? adown.contains(bup) - : // eslint-disable-next-line no-bitwise - a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16) - ) - ) -} - -export const isInDocument = - Global.document.documentElement.getRootNode != null - ? (elem: Element) => - isAncestorOf(elem.ownerDocument, elem) || - elem.getRootNode({ composed: true }) === elem.ownerDocument - : (elem: Element) => isAncestorOf(elem.ownerDocument, elem) diff --git a/packages/x6-vector/src/util/mixin.ts b/packages/x6-vector/src/util/mixin.ts deleted file mode 100644 index b53d4e71662..00000000000 --- a/packages/x6-vector/src/util/mixin.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Class } from '../types' - -export function applyMixins(target: Class, ...sources: Class[]) { - sources.forEach((source) => { - Object.getOwnPropertyNames(source.prototype).forEach((name) => { - if (name !== 'constructor') { - Object.defineProperty( - target.prototype, - name, - Object.getOwnPropertyDescriptor(source.prototype, name)!, - ) - } - }) - }) -} diff --git a/packages/x6-vector/src/vector/a/a.test.ts b/packages/x6-vector/src/vector/a/a.test.ts deleted file mode 100644 index 751dc6723a8..00000000000 --- a/packages/x6-vector/src/vector/a/a.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { G } from '../g/g' -import { A } from './a' - -describe('A', () => { - const url = 'https://placeholder.com' - - describe('constructor()', () => { - it('should create an instance', () => { - expect(A.create()).toBeInstanceOf(A) - }) - - it('should create an instance with given attributes', () => { - expect(A.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance with given url and attributes', () => { - const g = new G() - const a = g.link(url, { id: 'foo' }) - expect(a.id()).toBe('foo') - expect(a.to()).toBe(url) - expect(a.attr('href')).toBe(url) - }) - }) - - describe('to()', () => { - it('should get/set xlink:href attribute', () => { - const link = new A() - link.to(url) - expect(link.attr('href')).toBe(url) - }) - }) - - describe('target()', () => { - it('should get/set target attribute', () => { - const link = new A() - link.target('_blank') - expect(link.attr('target')).toBe('_blank') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/a/a.ts b/packages/x6-vector/src/vector/a/a.ts deleted file mode 100644 index 3f1e16cdf6c..00000000000 --- a/packages/x6-vector/src/vector/a/a.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ContainerGeometry } from '../container/geometry' -import { SVGAAttributes } from './types' - -@A.register('A') -export class A extends ContainerGeometry { - target(): string - target(target: '_self' | '_parent' | '_top' | '_blank' | string | null): this - target(target?: string | null) { - return this.attr('target', target) - } - - to(): string - to(url: string | null): this - to(url?: string | null) { - return this.attr('href', url) - } -} - -export namespace A { - export function create(): A - export function create( - attrs: Attributes, - ): A - export function create( - url: string, - attrs?: Attributes | null, - ): A - export function create( - url?: string | Attributes | null, - attrs?: Attributes | null, - ): A - export function create( - url?: string | Attributes | null, - attrs?: Attributes | null, - ) { - const a = new A() - if (url != null) { - if (typeof url === 'string') { - a.to(url) - if (attrs) { - a.attr(attrs) - } - } else if (typeof url === 'object') { - a.attr(url) - } - } - return a - } -} diff --git a/packages/x6-vector/src/vector/a/exts.test.ts b/packages/x6-vector/src/vector/a/exts.test.ts deleted file mode 100644 index f8d6b904693..00000000000 --- a/packages/x6-vector/src/vector/a/exts.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { G } from '../g/g' -import { Rect } from '../rect/rect' -import { A } from './a' - -describe('A', () => { - const url = 'https://placeholder.com' - - describe('linker()', () => { - it('should return the instance of the link of a linked element', () => { - const link = new A().to(url) - const rect = link.rect(100, 100) - expect(rect.linker()).toBe(link) - }) - - it('should return null if no link is found', () => { - const group = new G() - const rect = group.rect(100, 100) - expect(rect.linker()).toBe(null) - }) - - it('should return null if the element is not in dom at all', () => { - const group = new G() - expect(group.linker()).toBe(null) - }) - }) - - describe('unlink()', () => { - it('should return itself', () => { - const group = new G() - expect(group.unlink()).toBe(group) - }) - - it('should remove the link', () => { - const group = new G() - const link = group.link(url) - const rect = link.rect(100, 100) - - expect(rect.unlink().parent()).toBe(group) - expect(link.parent()).toBe(null) - }) - - it("should remove also the link when link wasn't in document", () => { - const link = new A().to(url) - const rect = link.rect(100, 100) - - expect(rect.unlink().parent()).toBe(null) - expect(link.parent()).toBe(null) - }) - }) - - describe('linkTo()', () => { - it('should wrap the called element in a link with given url', () => { - const rect = new Rect() - rect.linkTo(url) - expect(rect.linker()).toBeInstanceOf(A) - expect(rect.linker()!.attr('href')).toBe(url) - }) - - it('should wrap the called element in a link with given block', () => { - const rect = new Rect() - rect.linkTo((link) => { - link.to(url).target('_blank') - }) - expect(rect.linker()!.attr('href')).toBe(url) - expect(rect.linker()!.attr('target')).toBe('_blank') - }) - - it('should reuse existing link if possible', () => { - const rect = new Rect() - rect.linkTo(url) - const link = rect.linker() - rect.linkTo(`${url}/something`) - expect(rect.linker()).toBe(link) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/a/exts.ts b/packages/x6-vector/src/vector/a/exts.ts deleted file mode 100644 index 250d6890287..00000000000 --- a/packages/x6-vector/src/vector/a/exts.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Base } from '../common/base' -import { A } from './a' -import { SVGAAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - link(): A - link(attrs: Attributes): A - link( - url: string, - attrs?: Attributes | null, - ): A - link( - url?: string | Attributes | null, - attrs?: Attributes | null, - ) { - return A.create(url, attrs).appendTo(this) - } -} - -export class ElementExtension< - TSVGElement extends SVGElement, -> extends Base { - unlink() { - const link = this.linker() - if (!link) { - return this - } - - const parent = link.parent() - if (!parent) { - return this.remove() - } - - const index = parent.indexOf(link) - parent.add(this, index) - link.remove() - return this - } - - linkTo(url: string | ((this: A, a: A) => void)) { - const link = this.linker() || new A() - - if (typeof url === 'function') { - url.call(link, link) - } else { - link.to(url) - } - - if (this.parent() !== link) { - this.wrap(link) - } - - return this - } - - linker() { - const link = this.parent() - if (link && link.node.nodeName.toLowerCase() === 'a') { - return link - } - return null - } -} diff --git a/packages/x6-vector/src/vector/a/types.ts b/packages/x6-vector/src/vector/a/types.ts deleted file mode 100644 index dcd392ff956..00000000000 --- a/packages/x6-vector/src/vector/a/types.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - HTMLAttributeReferrerPolicy, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGAAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes { - /** - * Instructs browsers to download a URL instead of navigating to it, so the - * user will be prompted to save it as a local file. - */ - download?: string - /** - * The URL or URL fragment the hyperlink points to. - */ - href?: string - /** - * The human language of the URL or URL fragment that the hyperlink points to. - */ - hrefLang?: string - /** - * A space-separated list of URLs to which, when the hyperlink is followed, - * POST requests with the body PING will be sent by the browser (in the - * background). Typically used for tracking. - */ - ping?: string - /** - * The relationship of the target object to the link object. - */ - ref?: string - /** - * Where to display the linked URL. - */ - target?: string - /** - * A MIME type for the linked URL. - */ - type?: string - /** - * Which referrer to send when fetching the URL. - */ - referrerPolicy?: HTMLAttributeReferrerPolicy -} diff --git a/packages/x6-vector/src/vector/animate-motion/animate-motion.test.ts b/packages/x6-vector/src/vector/animate-motion/animate-motion.test.ts deleted file mode 100644 index 6b200508509..00000000000 --- a/packages/x6-vector/src/vector/animate-motion/animate-motion.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { G } from '../g/g' -import { Circle } from '../circle/circle' -import { AnimateMotion } from './animate-motion' - -describe('Animate', () => { - describe('constructor()', () => { - it('should create an AnimateMotion', () => { - expect(AnimateMotion.create()).toBeInstanceOf(AnimateMotion) - }) - - it('should create an AnimateMotion with given attributes', () => { - expect(AnimateMotion.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an AnimateMotion in a group', () => { - const group = new G() - const animate = group.animateMotion({ id: 'foo' }) - expect(animate.attr('id')).toBe('foo') - expect(animate).toBeInstanceOf(AnimateMotion) - }) - - it('should create an AnimateMotion in a circle', () => { - const circle = new Circle() - const animate = circle.animateMotion({ id: 'foo' }) - expect(animate.attr('id')).toBe('foo') - expect(animate).toBeInstanceOf(AnimateMotion) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/animate-motion/animate-motion.ts b/packages/x6-vector/src/vector/animate-motion/animate-motion.ts deleted file mode 100644 index d1a3499b367..00000000000 --- a/packages/x6-vector/src/vector/animate-motion/animate-motion.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Animation } from '../animate/animation' -import { SVGAnimateMotionAttributes } from './types' - -@AnimateMotion.register('AnimateMotion') -export class AnimateMotion extends Animation {} - -export namespace AnimateMotion { - export function create( - attrs?: Attributes | null, - ) { - return new AnimateMotion(attrs) - } -} diff --git a/packages/x6-vector/src/vector/animate-motion/exts.ts b/packages/x6-vector/src/vector/animate-motion/exts.ts deleted file mode 100644 index 4e5c887ab1c..00000000000 --- a/packages/x6-vector/src/vector/animate-motion/exts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Base } from '../common/base' -import { AnimateMotion } from './animate-motion' -import { SVGAnimateMotionAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - animateMotion( - attrs?: Attributes | null, - ) { - return AnimateMotion.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/animate-motion/types.ts b/packages/x6-vector/src/vector/animate-motion/types.ts deleted file mode 100644 index e9f785ef231..00000000000 --- a/packages/x6-vector/src/vector/animate-motion/types.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - SVGCoreAttributes, - SVGConditionalProcessingAttributes, - SVGXLinkAttributes, - SVGAnimationValueAttributes, - SVGAnimationTimingAttributes, - SVGAnimationAdditionAttributes, - AnimationAttributeTargetAttributes, -} from '../types/attributes-core' - -export interface SVGAnimateMotionAttributes - extends SVGCoreAttributes, - SVGConditionalProcessingAttributes, - SVGXLinkAttributes, - SVGAnimationValueAttributes, - SVGAnimationTimingAttributes, - SVGAnimationAdditionAttributes, - AnimationAttributeTargetAttributes { - /** - * This attribute defines the path of the motion, using the same syntax as - * the `d` attribute. - */ - path?: string - /** - * This attribute indicate, in the range `[0,1]`, how far is the object - * along the path for each keyTimes associated values. - */ - keyPoints?: string - /** - * This attribute defines a rotation applied to the element animated along - * a path, usually to make it pointing in the direction of the animation. - */ - rotate?: number | 'auto' | 'auto-reverse' -} diff --git a/packages/x6-vector/src/vector/animate-transform/animate-transform.test.ts b/packages/x6-vector/src/vector/animate-transform/animate-transform.test.ts deleted file mode 100644 index 1791b28a0a7..00000000000 --- a/packages/x6-vector/src/vector/animate-transform/animate-transform.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { G } from '../g/g' -import { Circle } from '../circle/circle' -import { AnimateTransform } from './animate-transform' - -describe('Animate', () => { - describe('constructor()', () => { - it('should create an AnimateTransform', () => { - expect(AnimateTransform.create()).toBeInstanceOf(AnimateTransform) - }) - - it('should create an AnimateTransform with given attributes', () => { - expect(AnimateTransform.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an AnimateTransform in a group', () => { - const group = new G() - const animate = group.animateTransform({ id: 'foo' }) - expect(animate.attr('id')).toBe('foo') - expect(animate).toBeInstanceOf(AnimateTransform) - }) - - it('should create an AnimateTransform in a circle', () => { - const circle = new Circle() - const animate = circle.animateTransform({ id: 'foo' }) - expect(animate.attr('id')).toBe('foo') - expect(animate).toBeInstanceOf(AnimateTransform) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/animate-transform/animate-transform.ts b/packages/x6-vector/src/vector/animate-transform/animate-transform.ts deleted file mode 100644 index a7abd26f521..00000000000 --- a/packages/x6-vector/src/vector/animate-transform/animate-transform.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Animation } from '../animate/animation' -import { SVGAnimateTransformAttributes } from './types' - -@AnimateTransform.register('AnimateTransform') -export class AnimateTransform extends Animation {} - -export namespace AnimateTransform { - export function create( - attrs?: Attributes | null, - ) { - return new AnimateTransform(attrs) - } -} diff --git a/packages/x6-vector/src/vector/animate-transform/exts.ts b/packages/x6-vector/src/vector/animate-transform/exts.ts deleted file mode 100644 index b43fd1dafbd..00000000000 --- a/packages/x6-vector/src/vector/animate-transform/exts.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Base } from '../common/base' -import { AnimateTransform } from './animate-transform' -import { SVGAnimateTransformAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - animateTransform( - attrs?: Attributes | null, - ) { - return AnimateTransform.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/animate-transform/types.ts b/packages/x6-vector/src/vector/animate-transform/types.ts deleted file mode 100644 index 8d1f8d78e82..00000000000 --- a/packages/x6-vector/src/vector/animate-transform/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - SVGCoreAttributes, - SVGConditionalProcessingAttributes, - SVGXLinkAttributes, - SVGAnimationValueAttributes, - SVGAnimationTimingAttributes, - SVGAnimationAdditionAttributes, - AnimationAttributeTargetAttributes, -} from '../types/attributes-core' - -export interface SVGAnimateTransformAttributes - extends SVGCoreAttributes, - SVGConditionalProcessingAttributes, - SVGXLinkAttributes, - SVGAnimationValueAttributes, - SVGAnimationTimingAttributes, - SVGAnimationAdditionAttributes, - AnimationAttributeTargetAttributes { - /** - * The `type` attribute defines the type of transformation, whose values - * change over time. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/type - */ - type?: 'translate' | 'scale' | 'rotate' | 'skewX' | 'skewY' -} diff --git a/packages/x6-vector/src/vector/animate/animate.test.ts b/packages/x6-vector/src/vector/animate/animate.test.ts deleted file mode 100644 index cfcf070205d..00000000000 --- a/packages/x6-vector/src/vector/animate/animate.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { G } from '../g/g' -import { Circle } from '../circle/circle' -import { Animate } from './animate' - -describe('Animate', () => { - describe('constructor()', () => { - it('should create an Animate', () => { - expect(Animate.create()).toBeInstanceOf(Animate) - }) - - it('should create an Animate with given attributes', () => { - expect(Animate.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an Animate in a group', () => { - const group = new G() - const animate = group.animate({ id: 'foo' }) - expect(animate.attr('id')).toBe('foo') - expect(animate).toBeInstanceOf(Animate) - }) - - it('should create an Animate in a circle', () => { - const circle = new Circle() - const animate = circle.animate({ id: 'foo' }) - expect(animate.attr('id')).toBe('foo') - expect(animate).toBeInstanceOf(Animate) - }) - }) - - describe('sugar methods with attributes', () => { - const methods = [ - 'from', - 'to', - 'by', - 'calcMode', - 'values', - 'keyTimes', - 'keySplines', - 'begin', - 'end', - 'dur', - 'min', - 'max', - 'repeatCount', - 'repeatDur', - 'restartMode', - 'fillMode', - 'additive', - 'accumulate', - 'attributeName', - 'attributeType', - ] - const values = [ - 10, - 20, - 30, - 'linear', - '0 1 2 3 4', - 'foo', - 'bar', - '1s', - '2s', - '3s', - '0s', - '100s', - 3, - 3000, - 'whenNotActive', - 'freeze', - 'replace', - 'none', - 'color', - 'CSS', - ] - - methods.forEach((method, index) => { - const val = values[index] as any - const attrMap = { - restartMode: 'restart', - fillMode: 'fill', - } - const attr = attrMap[method as keyof typeof attrMap] || method - - it(`should call attribute with "${method}" and return itself`, () => { - const animate = new Animate() - const spy = spyOn(animate, 'attr').and.callThrough() - expect(animate[method as 'from'](val)).toBe(animate) - expect(spy).toHaveBeenCalledWith(attr, val) - }) - - it(`should get the "${method}" atribute`, () => { - const animate = Animate.create() - animate[method as 'from'](val) - expect(animate[method as 'from']()).toBe(val) - }) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/animate/animate.ts b/packages/x6-vector/src/vector/animate/animate.ts deleted file mode 100644 index 85d8c77c43e..00000000000 --- a/packages/x6-vector/src/vector/animate/animate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Animation } from './animation' -import { SVGAnimateAttributes } from './types' - -@Animate.register('Animate') -export class Animate extends Animation {} - -export namespace Animate { - export function create( - attrs?: Attributes | null, - ) { - return new Animate(attrs) - } -} diff --git a/packages/x6-vector/src/vector/animate/animation.ts b/packages/x6-vector/src/vector/animate/animation.ts deleted file mode 100644 index 2f946e6e0e0..00000000000 --- a/packages/x6-vector/src/vector/animate/animation.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { Base } from '../common/base' -import { - SVGAnimationValueCalcMode, - SVGAnimationTimingRestartMode, - SVGAnimationTimingRepeatCount, - SVGAnimationTimingRepeatDuration, - SVGAnimationTimingFillMode, - AnimationAttributeTargetAttributeType, - SVGAnimationAdditionAttributeAdditive, - SVGAnimationAdditionAttributeAccumulate, -} from '../types' - -export class Animation< - TSVGAnimationElement extends - | SVGAnimateElement - | SVGAnimateMotionElement - | SVGAnimateTransformElement, -> extends Base { - // #region SVGAnimationValueAttributes - - from(): string | number - from(v: string | number | null): this - from(v?: string | number | null) { - return this.attr('from', v) - } - - to(): string | number - to(v: string | number | null): this - to(v?: string | number | null) { - return this.attr('to', v) - } - - by(): string | number - by(v: string | number | null): this - by(v?: string | number | null) { - return this.attr('by', v) - } - - calcMode(): SVGAnimationValueCalcMode - calcMode(mode: SVGAnimationValueCalcMode | null): this - calcMode(mode?: SVGAnimationValueCalcMode | null) { - return this.attr('calcMode', mode) - } - - values(): string - values(v: string | null): this - values(v?: string | null) { - return this.attr('values', v) - } - - keyTimes(): string - keyTimes(v: string | null): this - keyTimes(v?: string | null) { - return this.attr('keyTimes', v) - } - - keySplines(): string - keySplines(v: string | null): this - keySplines(v?: string | null) { - return this.attr('keySplines', v) - } - - // #endregion - - // #region SVGAnimationTimingAttributes - - begin(): string - begin(v: string | null): this - begin(v?: string | null) { - return this.attr('begin', v) - } - - end(): string - end(v: string | null): this - end(v?: string | null) { - return this.attr('end', v) - } - - dur(): string - dur(v: string | null): this - dur(v?: string | null) { - return this.attr('dur', v) - } - - min(): string - min(v: string | null): this - min(v?: string | null) { - return this.attr('min', v) - } - - max(): string - max(v: string | null): this - max(v?: string | null) { - return this.attr('max', v) - } - - repeatCount(): SVGAnimationTimingRepeatCount - repeatCount(v: SVGAnimationTimingRepeatCount | null): this - repeatCount(v?: SVGAnimationTimingRepeatCount | null) { - return this.attr('repeatCount', v) - } - - repeatDur(): SVGAnimationTimingRepeatDuration - repeatDur(v: SVGAnimationTimingRepeatDuration | null): this - repeatDur(v?: SVGAnimationTimingRepeatDuration | null) { - return this.attr('repeatDur', v) - } - - restartMode(): SVGAnimationTimingRestartMode - restartMode(v: SVGAnimationTimingRestartMode | null): this - restartMode(v?: SVGAnimationTimingRestartMode | null) { - return this.attr('restart', v) - } - - fillMode(): SVGAnimationTimingFillMode - fillMode(v: SVGAnimationTimingFillMode | null): this - fillMode(v?: SVGAnimationTimingFillMode | null) { - return this.attr('fill', v) - } - - // #endregion - - // #region SVGAnimationAdditionAttributes - - additive(): SVGAnimationAdditionAttributeAdditive - additive(v: SVGAnimationAdditionAttributeAdditive | null): this - additive(v?: SVGAnimationAdditionAttributeAdditive | null) { - return this.attr('additive', v) - } - - accumulate(): SVGAnimationAdditionAttributeAccumulate - accumulate(v: SVGAnimationAdditionAttributeAccumulate | null): this - accumulate(v?: SVGAnimationAdditionAttributeAccumulate | null) { - return this.attr('accumulate', v) - } - - // #endregion - - // #region AnimationAttributeTargetAttributes - - attributeName(): string - attributeName(name: string | null): this - attributeName(name?: string | null) { - return this.attr('attributeName', name) - } - - attributeType(): AnimationAttributeTargetAttributeType - attributeType(type: AnimationAttributeTargetAttributeType | null): this - attributeType(type?: AnimationAttributeTargetAttributeType | null) { - return this.attr('attributeType', type) - } - - // #endregion -} - -export namespace Animation {} diff --git a/packages/x6-vector/src/vector/animate/exts.ts b/packages/x6-vector/src/vector/animate/exts.ts deleted file mode 100644 index 39d7818e02f..00000000000 --- a/packages/x6-vector/src/vector/animate/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { Animate } from './animate' -import { SVGAnimateAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - animate(attrs?: Attributes | null) { - return Animate.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/animate/types.ts b/packages/x6-vector/src/vector/animate/types.ts deleted file mode 100644 index 7e8db5858c6..00000000000 --- a/packages/x6-vector/src/vector/animate/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - SVGCoreAttributes, - SVGXLinkAttributes, - SVGConditionalProcessingAttributes, - SVGAnimationValueAttributes, - SVGAnimationTimingAttributes, - SVGAnimationAdditionAttributes, - AnimationAttributeTargetAttributes, -} from '../types/attributes-core' - -export interface SVGAnimateAttributes - extends SVGCoreAttributes, - SVGConditionalProcessingAttributes, - SVGXLinkAttributes, - SVGAnimationValueAttributes, - SVGAnimationTimingAttributes, - SVGAnimationAdditionAttributes, - AnimationAttributeTargetAttributes {} diff --git a/packages/x6-vector/src/vector/circle/circle.test.ts b/packages/x6-vector/src/vector/circle/circle.test.ts deleted file mode 100644 index 602204afc05..00000000000 --- a/packages/x6-vector/src/vector/circle/circle.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { G } from '../g/g' -import { Circle } from './circle' - -describe('Circle', () => { - describe('constructor()', () => { - it('should create a circle', () => { - expect(Circle.create()).toBeInstanceOf(Circle) - }) - - it('should create a circle with given attributes', () => { - expect(Circle.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create a circle with given size', () => { - const group = new G() - const circle = group.circle(100) - expect(circle.attr('r')).toEqual(50) - expect(circle).toBeInstanceOf(Circle) - }) - - it('should create a circle with given size', () => { - const group = new G() - const circle = group.circle(100) - expect(circle.attr('r')).toEqual(50) - expect(circle).toBeInstanceOf(Circle) - }) - - it('should create a circle with given size and attributes', () => { - const group = new G() - const circle = group.circle(100, { id: 'foo' }) - expect(circle.attr('r')).toEqual(50) - expect(circle).toBeInstanceOf(Circle) - expect(circle.id()).toBe('foo') - }) - }) - - describe('rx()', () => { - it('should call attribute with rx and return itself', () => { - const circle = new Circle() - const spy = spyOn(circle, 'attr').and.callThrough() - expect(circle.rx(50)).toBe(circle) - expect(spy).toHaveBeenCalledWith('r', 50) - }) - }) - - describe('ry()', () => { - it('should call attribute with ry and return itself', () => { - const circle = new Circle() - const spy = spyOn(circle, 'attr').and.callThrough() - expect(circle.ry(50)).toBe(circle) - expect(spy).toHaveBeenCalledWith('r', 50) - }) - }) - - describe('radius()', () => { - it('should set radius', () => { - const circle = new Circle().radius(5) - expect(circle.attr('r')).toEqual(5) - }) - }) - - describe('x()', () => { - it('should set x position and returns itself', () => { - const circle = Circle.create() - expect(circle.x(50)).toBe(circle) - expect(circle.bbox().x).toBe(50) - }) - - it('should get the x position', () => { - const circle = Circle.create() - circle.x(50) - expect(circle.x()).toBe(50) - }) - }) - - describe('y()', () => { - it('should set y position and returns itself', () => { - const circle = Circle.create() - expect(circle.y(50)).toBe(circle) - expect(circle.bbox().y).toBe(50) - }) - - it('should get the y position', () => { - const circle = Circle.create() - circle.y(50) - expect(circle.y()).toBe(50) - }) - }) - - describe('cx()', () => { - it('should call attribute with cx and returns itself', () => { - const circle = Circle.create() - const spy = spyOn(circle, 'attr').and.callThrough() - expect(circle.cx(50)).toBe(circle) - expect(spy).toHaveBeenCalledWith('cx', 50) - }) - }) - - describe('cy()', () => { - it('should call attribute with cy and returns itself', () => { - const circle = Circle.create() - const spy = spyOn(circle, 'attr').and.callThrough() - expect(circle.cy(50)).toBe(circle) - expect(spy).toHaveBeenCalledWith('cy', 50) - }) - }) - - describe('width()', () => { - it('should set rx by half the given width', () => { - const circle = Circle.create() - const spy = spyOn(circle, 'rx').and.callThrough() - expect(circle.width(50)).toBe(circle) - expect(spy).toHaveBeenCalledWith(25) - }) - - it('should get the width of the element', () => { - const circle = Circle.create() - circle.width(100) - expect(circle.width()).toBe(100) - }) - }) - - describe('height()', () => { - it('should set ry by half the given height', () => { - const circle = Circle.create() - const spy = spyOn(circle, 'ry').and.callThrough() - expect(circle.height(50)).toBe(circle) - expect(spy).toHaveBeenCalledWith(25) - }) - - it('should get the height of the element', () => { - const circle = Circle.create() - circle.height(100) - expect(circle.height()).toBe(100) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/circle/circle.ts b/packages/x6-vector/src/vector/circle/circle.ts deleted file mode 100644 index 4e498a47929..00000000000 --- a/packages/x6-vector/src/vector/circle/circle.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { UnitNumber } from '../../struct/unit-number' -import { Shape } from '../common/shape' -import { SVGCircleAttributes } from './types' - -@Circle.register('Circle') -export class Circle extends Shape { - rx(): number - rx(rx: string | number | null): this - rx(rx?: string | number | null) { - return this.attr('r', rx) - } - - ry(): number - ry(ry: string | number | null): this - ry(ry?: string | number | null) { - return this.attr('r', ry) - } - - radius(): number - radius(r: string | number | null): this - radius(r?: string | number | null) { - return this.attr('r', r) - } - - size(size: string | number) { - return this.radius(UnitNumber.divide(size, 2)) - } - - cx(): number - cx(x: string | number | null): this - cx(x?: string | number | null) { - return this.attr('cx', x) - } - - cy(): number - cy(y: string | number | null): this - cy(y?: string | number | null) { - return this.attr('cy', y) - } - - x(): number - x(x?: null): number - x(x: string | number): this - x(x?: string | number | null) { - return x == null - ? this.cx() - this.rx() - : this.cx(UnitNumber.plus(x, this.rx())) - } - - y(): number - y(y: null): number - y(y: string | number): this - y(y?: string | number | null) { - return y == null - ? this.cy() - this.ry() - : this.cy(UnitNumber.plus(y, this.ry())) - } - - width(): number - width(w: null): number - width(w: string | number): this - width(w?: string | number | null) { - return w == null ? this.rx() * 2 : this.rx(UnitNumber.divide(w, 2)) - } - - height(): number - height(h: null): number - height(h: string | number): this - height(h?: string | number | null) { - return h == null ? this.ry() * 2 : this.ry(UnitNumber.divide(h, 2)) - } -} - -export namespace Circle { - export function create( - attrs?: Attributes | null, - ): Circle - export function create( - size: number | string, - attrs?: Attributes | null, - ): Circle - export function create( - size?: number | string | Attributes | null, - attrs?: Attributes | null, - ): Circle - export function create( - size?: number | string | Attributes | null, - attrs?: Attributes | null, - ) { - const circle = new Circle() - if (size == null) { - circle.size(0) - } else if (typeof size === 'object') { - circle.size(0).attr(size) - } else { - circle.size(size) - if (attrs) { - circle.attr(attrs) - } - } - - return circle - } -} diff --git a/packages/x6-vector/src/vector/circle/exts.ts b/packages/x6-vector/src/vector/circle/exts.ts deleted file mode 100644 index 6096203dcce..00000000000 --- a/packages/x6-vector/src/vector/circle/exts.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Base } from '../common/base' -import { Circle } from './circle' -import { SVGCircleAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - circle( - attrs?: Attributes | null, - ): Circle - circle( - size: number | string, - attrs?: Attributes | null, - ): Circle - circle( - size?: number | string | Attributes | null, - attrs?: Attributes | null, - ) { - return Circle.create(size, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/circle/types.ts b/packages/x6-vector/src/vector/circle/types.ts deleted file mode 100644 index 44c378d3f45..00000000000 --- a/packages/x6-vector/src/vector/circle/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGCircleAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - cx?: string | number - cy?: string | number - r?: string | number - pathLength?: number - requiredExtensions?: string - systemLanguage?: string -} diff --git a/packages/x6-vector/src/vector/clippath/clippath.test.ts b/packages/x6-vector/src/vector/clippath/clippath.test.ts deleted file mode 100644 index 12fc623f8f6..00000000000 --- a/packages/x6-vector/src/vector/clippath/clippath.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { SVG } from '../svg/svg' -import { ClipPath } from './clippath' - -describe('ClipPath', () => { - describe('constructor()', () => { - it('should create an instance', () => { - expect(ClipPath.create()).toBeInstanceOf(ClipPath) - }) - - it('should create an instance with given attributes', () => { - expect(ClipPath.create({ id: 'foo' }).id()).toBe('foo') - }) - }) - - describe('remove()', () => { - it('should unclip all targets', () => { - const svg = new SVG() - const clip = svg.clip() - const rect = svg.rect(100, 100).clipWith(clip) - expect(clip.remove()).toBe(clip) - expect(rect.clipper()).toBe(null) - }) - }) - - describe('targets()', () => { - it('should return an empty array when the element is not in SVGSVGElement', () => { - expect(ClipPath.create().targets()).toEqual([]) - }) - - it('should get all targets of this clipPath', () => { - const svg = new SVG() - const clip = svg.clip() - const rect = svg.rect(100, 100).clipWith(clip) - expect(clip.targets()).toEqual([rect]) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/clippath/clippath.ts b/packages/x6-vector/src/vector/clippath/clippath.ts deleted file mode 100644 index dc594091516..00000000000 --- a/packages/x6-vector/src/vector/clippath/clippath.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Vector } from '../vector/vector' -import { Vessel } from '../container/vessel' -import { SVGClipPathAttributes } from './types' - -@ClipPath.register('ClipPath') -export class ClipPath extends Vessel { - remove() { - this.targets().forEach((target) => target.unclip()) - return super.remove() - } - - targets() { - return this.findTargets('clip-path') - } -} - -export namespace ClipPath { - export function create( - attrs?: Attributes | null, - ) { - const clip = new ClipPath() - if (attrs) { - clip.attr(attrs) - } - return clip - } -} diff --git a/packages/x6-vector/src/vector/clippath/exts.test.ts b/packages/x6-vector/src/vector/clippath/exts.test.ts deleted file mode 100644 index fbbc40d5cb3..00000000000 --- a/packages/x6-vector/src/vector/clippath/exts.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Rect } from '../rect/rect' -import { SVG } from '../svg/svg' -import { ClipPath } from './clippath' - -describe('ClipPath', () => { - describe('clipper()', () => { - it('should return the instance of ClipPath the current element is clipped with', () => { - const svg = new SVG().appendTo(document.body) - const clip = svg.clip() - const rect = svg.rect(100, 100).clipWith(clip) - expect(rect.clipper()).toEqual(clip) - svg.remove() - }) - - it('should return null if no clipPath was found', () => { - expect(new Rect().clipper()).toBe(null) - }) - }) - - describe('clipWith()', () => { - it('should set the clip-path attribute on the element to the id of the clipPath', () => { - const clip = new ClipPath().id('foo') - const rect = new Rect().clipWith(clip) - expect(rect.attr('clip-path')).toBe('url("#foo")') - }) - - it('should create a clipPath and appends the passed element to it to clip current element', () => { - const svg = new SVG() - const circle = svg.circle(40) - const rect = svg.rect(100, 100).clipWith(circle) - const clipper = circle.parent()! - expect(clipper).toBeInstanceOf(ClipPath) - expect(rect.attr('clip-path')).toBe(`url("#${clipper.id()}")`) - }) - }) - - describe('unclip()', () => { - it('should set the clip-target attribute to null and returns itself', () => { - const clip = new ClipPath().id('foo') - const rect = new Rect().clipWith(clip) - expect(rect.unclip()).toBe(rect) - expect(rect.attr('clip-path')).toBeUndefined() - }) - }) -}) diff --git a/packages/x6-vector/src/vector/clippath/exts.ts b/packages/x6-vector/src/vector/clippath/exts.ts deleted file mode 100644 index 122a8633aec..00000000000 --- a/packages/x6-vector/src/vector/clippath/exts.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Adopter } from '../../dom/common/adopter' -import { Decorator } from '../common/decorator' -import { Base } from '../common/base' -import { Vector } from '../vector/vector' -import { Container } from '../container/container' -import { ClipPath } from './clippath' -import { SVGClipPathAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - @Decorator.checkDefs - clip( - attrs?: Attributes | null, - ): ClipPath { - return this.defs()!.clip(attrs) - } -} - -export class DefsExtension< - TSVGElement extends SVGElement, -> extends Base { - clip( - attrs?: Attributes | null, - ): ClipPath { - return ClipPath.create(attrs).appendTo(this) - } -} - -export class ElementExtension< - TSVGElement extends SVGElement, -> extends Base { - clipper() { - return this.reference('clip-path') - } - - clipWith(element: ClipPath | Adopter.Target) { - // use given clip or create a new one - let clipper: ClipPath | undefined | null - if (element instanceof ClipPath) { - clipper = element - } else { - const parent = this.parent() - clipper = parent && parent.clip() - if (clipper) { - clipper.add(element) - } - } - - if (clipper) { - this.attr('clip-path', `url("#${clipper.id()}")`) - } - - return this - } - - unclip() { - return this.attr('clip-path', null) - } -} diff --git a/packages/x6-vector/src/vector/clippath/types.ts b/packages/x6-vector/src/vector/clippath/types.ts deleted file mode 100644 index 26a3c789673..00000000000 --- a/packages/x6-vector/src/vector/clippath/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGClipPathAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - /** - * Defines the coordinate system for the contents of the `` element. - */ - clipPathUnits?: 'userSpaceOnUse' | 'objectBoundingBox' -} diff --git a/packages/x6-vector/src/vector/common/base.test.ts b/packages/x6-vector/src/vector/common/base.test.ts deleted file mode 100644 index ec0ed3610dd..00000000000 --- a/packages/x6-vector/src/vector/common/base.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import sinon from 'sinon' -import { G } from '../g/g' -import { namespaces } from '../../util/dom' - -describe('Base', () => { - describe('html()', () => { - it('should call xml with the svg namespace', () => { - const group = new G() - const spy = sinon.spy(group, 'xml') - group.svg('') - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual(['', undefined, namespaces.svg]) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/common/base.ts b/packages/x6-vector/src/vector/common/base.ts deleted file mode 100644 index 859adc85e5e..00000000000 --- a/packages/x6-vector/src/vector/common/base.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { namespaces } from '../../util/dom' -import { Registry } from '../../dom/common/registry' -import { Dom } from '../../dom/dom' -import type { SVG } from '../svg/svg' - -export class Base< - TSVGElement extends SVGElement = SVGElement, -> extends Dom { - root(): SVG | null { - const parent = this.parent(Registry.getClass('Svg')) - return parent ? parent.root() : null - } - - defs() { - const root = this.root() - return root ? root.defs() : null - } - - reference(attr: string): T | null { - const value = this.attr(attr) - if (!value) { - return null - } - - // reference id - const matches = `${value}`.match(/(#[_a-z][\w-]*)/i) - return matches ? Base.findOne(matches[1]) : null - } - - svg(): string - svg(outerXML: boolean): string - svg(process: (dom: Dom) => false | Dom, outerXML?: boolean): string - svg(content: string, outerXML?: boolean): string - svg(arg1?: boolean | string | ((dom: Dom) => false | Dom), arg2?: boolean) { - return this.xml(arg1, arg2, namespaces.svg) - } -} diff --git a/packages/x6-vector/src/vector/common/decorator.ts b/packages/x6-vector/src/vector/common/decorator.ts deleted file mode 100644 index c93a9636ede..00000000000 --- a/packages/x6-vector/src/vector/common/decorator.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { Vector } from '../vector/vector' - -export namespace Decorator { - export function checkDefs( - target: any, - methodName: string, - descriptor: PropertyDescriptor, - ) { - const raw = descriptor.value - descriptor.value = function (this: Vector, ...args: any[]) { - const defs = this.defs() - if (defs == null) { - throw new Error( - 'Can not get or create SVGDefsElement in the current document tree. ' + - 'Please ensure that the current element is attached into any SVG context.', - ) - } - return raw.call(this, ...args) - } - } -} diff --git a/packages/x6-vector/src/vector/common/shape-mixins.ts b/packages/x6-vector/src/vector/common/shape-mixins.ts deleted file mode 100644 index b9e4b8e38a8..00000000000 --- a/packages/x6-vector/src/vector/common/shape-mixins.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { applyMixins } from '../../util/mixin' - -import { ContainerExtension as AnimateExtension } from '../animate/exts' -import { ContainerExtension as AnimateMotionExtension } from '../animate-motion/exts' -import { ContainerExtension as AnimateTransformExtension } from '../animate-transform/exts' - -import { Shape } from './shape' - -declare module './shape' { - interface Shape - extends AnimateExtension, - AnimateMotionExtension, - AnimateTransformExtension {} -} - -applyMixins( - Shape, - AnimateExtension, - AnimateMotionExtension, - AnimateTransformExtension, -) diff --git a/packages/x6-vector/src/vector/common/shape.ts b/packages/x6-vector/src/vector/common/shape.ts deleted file mode 100644 index af42082dc61..00000000000 --- a/packages/x6-vector/src/vector/common/shape.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Vector } from '../vector/vector' -import { AttributesMap } from '../../dom/attributes' - -@Shape.register('Shape') -export class Shape< - TSVGGraphicsElement extends SVGGraphicsElement, -> extends Vector { - constructor() - constructor(attrs?: AttributesMap | null) - constructor( - node: TSVGGraphicsElement | null, - attrs?: AttributesMap | null, - ) - // eslint-disable-next-line no-useless-constructor - constructor( - node?: TSVGGraphicsElement | AttributesMap | null, - attrs?: AttributesMap | null, - ) { - super(node, attrs) - } -} diff --git a/packages/x6-vector/src/vector/common/size.ts b/packages/x6-vector/src/vector/common/size.ts deleted file mode 100644 index c2cd06fc459..00000000000 --- a/packages/x6-vector/src/vector/common/size.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Box } from '../../struct/box' -import type { Vector } from '../vector/vector' -import { UnitNumber } from '../../struct/unit-number' - -export namespace Size { - export function normalize( - element: Vector, - width?: string | number | null, - height?: string | number | null, - box?: Box, - ) { - if (width == null || height == null) { - const bbox = box || element.bbox() - - let w = width - let h = height - if (w == null) { - w = (bbox.width / bbox.height) * UnitNumber.toNumber(h!) - } else if (h == null) { - h = (bbox.height / bbox.width) * UnitNumber.toNumber(w) - } - - return { width: w, height: h! } - } - - return { - width, - height, - } - } -} diff --git a/packages/x6-vector/src/vector/container/container-mixins.ts b/packages/x6-vector/src/vector/container/container-mixins.ts deleted file mode 100644 index 39a2551fed5..00000000000 --- a/packages/x6-vector/src/vector/container/container-mixins.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { applyMixins } from '../../util/mixin' - -// containers -import { ContainerExtension as AExtension } from '../a/exts' -import { ContainerExtension as AnimateExtension } from '../animate/exts' -import { ContainerExtension as AnimateMotionExtension } from '../animate-motion/exts' -import { ContainerExtension as AnimateTransformExtension } from '../animate-transform/exts' -import { ContainerExtension as GExtension } from '../g/exts' -import { ContainerExtension as SvgExtension } from '../svg/exts' -import { ContainerExtension as MaskExtension } from '../mask/exts' -import { ContainerExtension as MarkerExtension } from '../marker/exts' -import { ContainerExtension as PatternExtension } from '../pattern/exts' -import { ContainerExtension as ClipPathExtension } from '../clippath/exts' -import { ContainerExtension as GradientExtension } from '../gradient/exts' -import { ContainerExtension as LinearGradientExtension } from '../gradient/linear-exts' -import { ContainerExtension as RadialGradientExtension } from '../gradient/radial-exts' -import { ContainerExtension as SymbolExtension } from '../symbol/exts' -import { ContainerExtension as FilterExtension } from '../filter/exts' -import { ContainerExtension as SwitchExtension } from '../switch/exts' -// shapes -import { ContainerExtension as CircleExtension } from '../circle/exts' -import { ContainerExtension as EllipseExtension } from '../ellipse/ext' -import { ContainerExtension as ForeignObjectExtension } from '../foreignobject/exts' -import { ContainerExtension as ImageExtension } from '../image/exts' -import { ContainerExtension as LineExtension } from '../line/exts' -import { ContainerExtension as PathExtension } from '../path/exts' -import { ContainerExtension as PolygonExtension } from '../polygon/exts' -import { ContainerExtension as PolylineExtension } from '../polyline/exts' -import { ContainerExtension as RectExtension } from '../rect/exts' -import { ContainerExtension as TextExtension } from '../text/exts' -import { ContainerExtension as UseExtension } from '../use/exts' -import { ContainerExtension as TextPathExtension } from '../textpath/exts' - -import { Container } from './container' - -declare module './container' { - interface Container< - TSVGElement extends SVGElement = SVGElement, - > extends AExtension, - // animation - AnimateExtension, - AnimateMotionExtension, - AnimateTransformExtension, - // - GExtension, - SvgExtension, - MaskExtension, - MarkerExtension, - SymbolExtension, - SwitchExtension, - FilterExtension, - PatternExtension, - ClipPathExtension, - GradientExtension, - LinearGradientExtension, - RadialGradientExtension, - // shapes - UseExtension, - RectExtension, - LineExtension, - TextExtension, - PathExtension, - ImageExtension, - CircleExtension, - EllipseExtension, - PolygonExtension, - PolylineExtension, - TextPathExtension, - ForeignObjectExtension {} -} - -applyMixins( - Container, - AExtension, - AnimateExtension, - AnimateMotionExtension, - AnimateTransformExtension, - GExtension, - SvgExtension, - MaskExtension, - MarkerExtension, - FilterExtension, - SymbolExtension, - SwitchExtension, - PatternExtension, - ClipPathExtension, - GradientExtension, - LinearGradientExtension, - RadialGradientExtension, - // shapes - UseExtension, - RectExtension, - LineExtension, - TextExtension, - PathExtension, - ImageExtension, - CircleExtension, - EllipseExtension, - PolygonExtension, - PolylineExtension, - TextPathExtension, - ForeignObjectExtension, -) diff --git a/packages/x6-vector/src/vector/container/container.test.ts b/packages/x6-vector/src/vector/container/container.test.ts deleted file mode 100644 index 240c4004004..00000000000 --- a/packages/x6-vector/src/vector/container/container.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { Circle } from '../circle/circle' -import { G } from '../g/g' -import { Line } from '../line/line' -import { Rect } from '../rect/rect' -import { SVG } from '../svg/svg' - -describe('Container', () => { - let svg: SVG - let rect1: Rect - let group1: G - let rect2: Rect - let circle1: Circle - let group2: G - let circle2: Circle - let group3: G - let line1: Line - let line2: Line - let circle3: Circle - let group4: G - let rect3: Rect - - beforeEach(() => { - svg = new SVG().appendTo(document.body) - rect1 = svg.rect(100, 100).id('rect1') - group1 = svg.group().id('group1') - rect2 = group1.rect(100, 100).id('rect2') - circle1 = group1.circle(50).id('circle1') - group2 = group1.group().id('group2') - circle2 = group2.circle(50).id('circle2') - group3 = group2.group().id('group3') - line1 = group3.line(1, 1, 2, 2).id('line1') - line2 = group3.line(1, 1, 2, 2).id('line2') - circle3 = group2.circle(50).id('circle3') - group4 = svg.group().id('group4') - rect3 = group4.rect(100, 100).id('rect3') - - /* should be: - svg - rect1 - group1 - rect2 - circle1 - group2 - circle2 - group3 - line1 - line2 - circle3 - group4 - rect3 - */ - }) - - afterEach(() => { - svg.remove() - }) - - describe('flatten()', () => { - it('should flatten the whole document when called on the root', () => { - svg.flatten() - - expect(svg.children()).toEqual([ - rect1, - rect2, - circle1, - circle2, - line1, - line2, - circle3, - rect3, - ]) - }) - - it('should flatten a group and places all children into its parent when called on a group - 1', () => { - group1.flatten() - - /* now should be: - svg - rect1 - group1 - rect2 - circle1 - circle2 - line1 - line2 - circle3 - group4 - rect3 - */ - - expect(svg.children()).toEqual([rect1, group1, group4]) - expect(group1.children()).toEqual([ - rect2, - circle1, - circle2, - line1, - line2, - circle3, - ]) - }) - - it('should flatten a group and places all children into its parent when called on a group - 2', () => { - group2.flatten() - - /* now should be: - svg - rect1 - group1 - rect2 - circle1 - group2 - circle2 - line1 - line2 - circle3 - group4 - rect3 - */ - - expect(group2.children()).toEqual([circle2, line1, line2, circle3]) - }) - }) - - describe('ungroup()', () => { - it('should ungroup a group and inserts all children in the correct order in the parent parent of the group', () => { - group1.ungroup() - - expect(svg.children()).toEqual([rect1, rect2, circle1, group2, group4]) - - group4.ungroup() - - expect(svg.children()).toEqual([rect1, rect2, circle1, group2, rect3]) - }) - - it('should ungroup a group into another group and appends the elements to the other group', () => { - group1.ungroup(group4) - expect(group4.children()).toEqual([rect3, rect2, circle1, group2]) - }) - - it('should ungroup a group into another group at the specified position', () => { - group2.ungroup(group1, 1) - expect(group1.children()).toEqual([ - rect2, - circle2, - group3, - circle3, - circle1, - ]) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/container/container.ts b/packages/x6-vector/src/vector/container/container.ts deleted file mode 100644 index 3ca9c0ef6d4..00000000000 --- a/packages/x6-vector/src/vector/container/container.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Vector } from '../vector/vector' -import { Wrapper } from './wrapper' - -export class Container< - TSVGElement extends SVGElement = SVGElement, -> extends Wrapper { - flatten() { - this.eachChild((child) => { - if (child instanceof Container) { - child.flatten().ungroup() - } - }) - - return this - } - - ungroup(parent?: Container, index?: number) { - const ancestor = parent != null ? parent : this.parent() - if (ancestor) { - let idx = index == null ? ancestor.indexOf(this) : index - // when parent != this, we want append all elements to the end - idx = idx === -1 ? ancestor.children().length : idx - - this.children() - .reverse() - .forEach((child) => child.toParent(ancestor, idx)) - - this.remove() - } - return this - } -} diff --git a/packages/x6-vector/src/vector/container/geometry.test.ts b/packages/x6-vector/src/vector/container/geometry.test.ts deleted file mode 100644 index 989779b351a..00000000000 --- a/packages/x6-vector/src/vector/container/geometry.test.ts +++ /dev/null @@ -1,218 +0,0 @@ -import { G } from '../g/g' -import { Rect } from '../rect/rect' -import { SVG } from '../svg/svg' - -describe('ContainerGeometry', () => { - let svg: SVG - let g: G - - beforeEach(() => { - document.body.style.margin = '0px' - document.body.style.padding = '0px' - svg = new SVG().appendTo(document.body) - g = svg.group() - }) - - afterEach(() => { - svg.remove() - document.body.style.margin = '' - document.body.style.padding = '' - }) - - describe('dmove()', () => { - it('should move the bbox of the group by a certain amount (1)', () => { - g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) - g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) - - g.dmove(10, 10) - - const box = g.bbox() - expect(box.x).toEqual(20) - expect(box.y).toEqual(30) - }) - - it('should move the bbox of the group by a certain amount (2)', () => { - g.rect(400, 200).move(123, 312).rotate(34).skew(12) - g.rect(100, 50).move(11, 43).translate(123, 32).skew(-12) - g.rect(400, 200).rotate(90) - g.group().rotate(23).group().skew(32).rect(100, 40).skew(11).rotate(12) - - const oldBox = g.bbox() - - g.dmove(10, 10) - - const newBox = g.bbox() - - expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4) - expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4) - expect(newBox.w).toBeCloseTo(oldBox.w, 4) - expect(newBox.h).toBeCloseTo(oldBox.h, 4) - }) - }) - - describe('dx()', () => { - it('should call dmove with dy=0 and returns itself', () => { - const spy = spyOn(g, 'dmove').and.callThrough() - expect(g.dx(10)).toBe(g) - expect(spy).toHaveBeenCalledWith(10, 0) - }) - }) - - describe('dy()', () => { - it('should call dmove with dx=0 and returns itself', () => { - const spy = spyOn(g, 'dmove').and.callThrough() - expect(g.dy(10)).toBe(g) - expect(spy).toHaveBeenCalledWith(0, 10) - }) - }) - - describe('move()', () => { - it('should call dmove() with the correct difference', () => { - g.rect(100, 200).move(111, 223) - - spyOn(g, 'dmove') - - g.move(100, 150) - expect(g.dmove).toHaveBeenCalledWith(-11, -73) - }) - }) - - describe('x()', () => { - it('should get the x value of the bbox', () => { - g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) - g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) - - expect(g.x()).toBe(g.bbox().x) - expect(g.x()).toBe(10) - }) - - it('should call move with the paramater as x', () => { - g.rect(100, 200).move(111, 223) - spyOn(g, 'move') - g.x(100) - expect(g.move).toHaveBeenCalledWith(100, g.bbox().y, g.bbox()) - }) - }) - - describe('y()', () => { - it('should get the y value of the bbox', () => { - g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) - g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) - - expect(g.y()).toBe(g.bbox().y) - expect(g.y()).toBe(20) - }) - - it('should call move with the paramater as y', () => { - g.rect(100, 200).move(111, 223) - spyOn(g, 'move') - g.y(100) - expect(g.move).toHaveBeenCalledWith(g.bbox().x, 100, g.bbox()) - }) - }) - - describe('size()', () => { - it('should change the dimensions of the bbox (1)', () => { - g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) - g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) - - const oldBox = g.bbox() - - g.size(100, 100) - - const newBox = g.bbox() - - expect(newBox.x).toBeCloseTo(oldBox.x, 4) - expect(newBox.y).toBeCloseTo(oldBox.y, 4) - expect(newBox.w).toBeCloseTo(100, 4) - expect(newBox.h).toBeCloseTo(100, 4) - - const rbox1 = g.children()[0].rbox() - const rbox2 = g.children()[1].rbox() - - expect(rbox1.width).toBeCloseTo(90.9, 1) - expect(Math.floor(rbox2.width * 10) / 10).toBeCloseTo(63.6, 1) // Browsers have different opinion on this one (chrome: 63.6, ff: 63.7) - - expect(rbox1.x).toBeCloseTo(10, 1) - expect(rbox2.x).toBeCloseTo(46.4, 1) - expect(rbox1.height).toBeCloseTo(85.7, 1) - expect(rbox2.height).toBeCloseTo(71.4, 1) - expect(rbox1.y).toBeCloseTo(20, 1) - expect(rbox2.y).toBeCloseTo(48.6, 1) - }) - - it('should change the dimensions of the bbox (2)', () => { - g.rect(400, 200).move(123, 312).rotate(34).skew(12) - g.rect(100, 50).move(11, 43).translate(123, 32).skew(-12) - g.rect(400, 200).rotate(90) - g.group().rotate(23).group().skew(32).rect(100, 40).skew(11).rotate(12) - - const oldBox = g.bbox() - - g.size(100, 100) - - const newBox = g.bbox() - - expect(newBox.x).toBeCloseTo(oldBox.x, 4) - expect(newBox.y).toBeCloseTo(oldBox.y, 4) - expect(newBox.w).toBeCloseTo(100, 4) - expect(newBox.h).toBeCloseTo(100, 4) - }) - }) - - describe('width()', () => { - it('should get the width value of the bbox', () => { - g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) - g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) - - expect(g.width()).toBe(g.bbox().width) - expect(g.width()).toBe(110) - }) - - it('should set the width value of the bbox by moving all children', () => { - g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) - g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) - - expect(g.width(100)).toBe(g) - expect(g.bbox().width).toBe(100) - - const rbox1 = g.children()[0].rbox() - const rbox2 = g.children()[1].rbox() - - expect(rbox1.width).toBeCloseTo(90.9, 1) - expect(Math.floor(rbox2.width * 10) / 10).toBeCloseTo(63.6, 1) // Browsers have different opinion on this one (chrome: 63.6, ff: 63.7) - - expect(rbox1.x).toBeCloseTo(10, 3) - expect(rbox2.x).toBeCloseTo(46.4, 1) - }) - }) - - describe('height()', () => { - it('should get the height value of the bbox', () => { - g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) - g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) - - expect(g.height()).toBe(g.bbox().height) - expect(g.height()).toBe(140) - - svg.remove() - }) - - it('should set the height value of the bbox by moving all children', () => { - g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) - g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) - - expect(g.height(100)).toBe(g) - expect(g.bbox().height).toBeCloseTo(100, 3) - - const rbox1 = g.children()[0].rbox() - const rbox2 = g.children()[1].rbox() - - expect(rbox1.height).toBeCloseTo(85.7, 1) - expect(rbox2.height).toBeCloseTo(71.4, 1) - - expect(rbox1.y).toBeCloseTo(20, 3) - expect(rbox2.y).toBeCloseTo(48.6, 1) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/container/geometry.ts b/packages/x6-vector/src/vector/container/geometry.ts deleted file mode 100644 index cfe3cb2a47e..00000000000 --- a/packages/x6-vector/src/vector/container/geometry.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Box } from '../../struct/box' -import { Point } from '../../struct/point' -import { Matrix } from '../../struct/matrix' -import { UnitNumber } from '../../struct/unit-number' -import { Vector } from '../vector/vector' -import { Size } from '../common/size' -import { Container } from './container' - -export abstract class ContainerGeometry< - TSVGElement extends SVGAElement | SVGGElement, -> extends Container { - dmove(dx: number | string = 0, dy: number | string = 0) { - this.eachChild((child) => { - const bbox = child.bbox() - const m = new Matrix(child) - // Translate childs matrix by amount and - // transform it back into parents space - const matrix = m - .translate(UnitNumber.toNumber(dx), UnitNumber.toNumber(dy)) - .transform(m.inverse()) - // Calculate new x and y from old box - const p = new Point(bbox.x, bbox.y).transform(matrix) - // Move child - child.move(p.x, p.y) - }) - - return this - } - - move(x: number | string = 0, y: number | string = 0, box = this.bbox()) { - const dx = UnitNumber.toNumber(x) - box.x - const dy = UnitNumber.toNumber(y) - box.y - - return this.dmove(dx, dy) - } - - dx(dx: number | string) { - return this.dmove(dx, 0) - } - - dy(dy: number | string) { - return this.dmove(0, dy) - } - - x(): number - x(x: number | string | null, box?: Box): this - x(x?: number | string | null, box = this.bbox()) { - if (x == null) { - return box.x - } - return this.move(x, box.y, box) - } - - y(): number - y(y: number | string | null, box?: Box): this - y(y?: number | string | null, box = this.bbox()) { - if (y == null) { - return box.y - } - return this.move(box.x, y, box) - } - - size( - width?: number | string | null, - height?: number | string | null, - box = this.bbox(), - ) { - const size = Size.normalize(this, width, height, box) - const sx = UnitNumber.toNumber(size.width) / box.width - const sy = UnitNumber.toNumber(size.height) / box.height - - this.eachChild((child) => { - const o = new Point(box).transform(new Matrix(child).inverse()) - child.scale(sx, sy, o.x, o.y) - }) - - return this - } - - width(): number - width(width: number | string | null, box?: Box): this - width(width?: number | string | null, box = this.bbox()) { - if (width == null) { - return box.width - } - return this.size(new UnitNumber(width).value, box.height, box) - } - - height(): number - height(height: number | string | null, box?: Box): this - height(height?: number | string | null, box = this.bbox()) { - if (height == null) { - return box.height - } - return this.size(box.width, new UnitNumber(height).value, box) - } -} diff --git a/packages/x6-vector/src/vector/container/referent.ts b/packages/x6-vector/src/vector/container/referent.ts deleted file mode 100644 index 9f45cad2870..00000000000 --- a/packages/x6-vector/src/vector/container/referent.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Vector } from '../vector/vector' -import { Wrapper } from './wrapper' - -export class Referent< - TSVGElement extends SVGElement = SVGElement, -> extends Wrapper { - url() { - return `url(#${this.id()})` - } - - toString() { - return this.url() - } - - protected findTargets(type: string): TVector[] { - const root = this.root() - return root ? root.find(`[${type}*="${this.id()}"]`) : [] - } -} diff --git a/packages/x6-vector/src/vector/container/vessel-mixins.ts b/packages/x6-vector/src/vector/container/vessel-mixins.ts deleted file mode 100644 index 267ebba8d0d..00000000000 --- a/packages/x6-vector/src/vector/container/vessel-mixins.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { applyMixins } from '../../util/mixin' - -import { ContainerExtension as LineExtension } from '../line/exts' -import { ContainerExtension as RectExtension } from '../rect/exts' -import { ContainerExtension as CircleExtension } from '../circle/exts' -import { ContainerExtension as EllipseExtension } from '../ellipse/ext' -import { ContainerExtension as PolygonExtension } from '../polygon/exts' -import { ContainerExtension as PolylineExtension } from '../polyline/exts' -import { ContainerExtension as PathExtension } from '../path/exts' -import { ContainerExtension as ImageExtension } from '../image/exts' -import { ContainerExtension as TextExtension } from '../text/exts' -import { ContainerExtension as TextPathExtension } from '../textpath/exts' -import { ContainerExtension as UseExtension } from '../use/exts' - -import { Vessel } from './vessel' - -declare module './vessel' { - interface Vessel - extends UseExtension, - LineExtension, - RectExtension, - CircleExtension, - EllipseExtension, - PathExtension, - TextExtension, - ImageExtension, - PolygonExtension, - PolylineExtension, - TextPathExtension {} -} - -applyMixins( - Vessel, - // shapes - UseExtension, - LineExtension, - RectExtension, - CircleExtension, - EllipseExtension, - PathExtension, - ImageExtension, - PolygonExtension, - PolylineExtension, - TextExtension, - TextPathExtension, -) diff --git a/packages/x6-vector/src/vector/container/vessel.ts b/packages/x6-vector/src/vector/container/vessel.ts deleted file mode 100644 index 71cf5679e49..00000000000 --- a/packages/x6-vector/src/vector/container/vessel.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Referent } from './referent' - -export class Vessel< - TSVGElement extends SVGElement = SVGElement, -> extends Referent {} diff --git a/packages/x6-vector/src/vector/container/viewbox.test.ts b/packages/x6-vector/src/vector/container/viewbox.test.ts deleted file mode 100644 index 2ef347ad4bd..00000000000 --- a/packages/x6-vector/src/vector/container/viewbox.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Box } from '../../struct/box' -import { SVG } from '../svg/svg' - -describe('Viewbox', () => { - describe('viewbox()', () => { - it('should set the viewbox of the element', () => { - const svg = new SVG().viewbox(10, 10, 200, 200) - expect(svg.attr('viewBox')).toEqual('10 10 200 200') - }) - - it('should gets the viewbox of the element', () => { - const svg = new SVG().viewbox(10, 10, 200, 200) - expect(svg.viewbox()).toBeInstanceOf(Box) - expect(svg.viewbox().toArray()).toEqual([10, 10, 200, 200]) - }) - }) - - describe('zoom()', () => { - it('should zoom around the center by default', () => { - const svg = new SVG().size(100, 50).viewbox(0, 0, 100, 50).zoom(2) - expect(svg.attr('viewBox')).toEqual('25 12.5 50 25') - }) - - it('should zoom around a point', () => { - const svg = new SVG() - .size(100, 50) - .viewbox(0, 0, 100, 50) - .zoom(2, { x: 0, y: 0 }) - expect(svg.attr('viewBox')).toEqual('0 0 50 25') - }) - - it('should get the zoom', () => { - const svg = new SVG().size(100, 50).viewbox(0, 0, 100, 50).zoom(2) - expect(svg.zoom()).toEqual(2) - }) - - it('should get the zoom with clientHeight', () => { - const svg = new SVG() - .css({ width: '100px', height: '50px' }) - .viewbox(25, 12.5, 50, 25) - - expect(svg.zoom()).toEqual(0) - }) - - it('should handle zoom level 0 which is - which basically sets the viewbox to a very high value', () => { - const svg = new SVG().size(100, 50).viewbox(0, 0, 100, 50).zoom(0) - expect(svg.zoom()).toBeCloseTo(0, 10) - }) - - it('should handle zoom level 0 and can recover from it', () => { - const svg = new SVG().size(100, 50).viewbox(0, 0, 100, 50).zoom(0).zoom(1) - expect(svg.zoom()).toBe(1) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/container/viewbox.ts b/packages/x6-vector/src/vector/container/viewbox.ts deleted file mode 100644 index c4c587701eb..00000000000 --- a/packages/x6-vector/src/vector/container/viewbox.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Box } from '../../struct/box' -import { Point } from '../../struct/point' -import { Util } from '../../dom/attributes/util' -import { Vector } from '../vector/vector' - -export class Viewbox< - TSVGElement extends - | SVGSVGElement - | SVGSymbolElement - | SVGPatternElement - | SVGMarkerElement, -> extends Vector { - viewbox(): Box - viewbox(box: Box.BoxLike): this - viewbox( - x: number | string, - y: number | string, - width: number | string, - height: number | string, - ): this - viewbox( - x?: number | string | Box.BoxLike, - y?: number | string, - width?: number | string, - height?: number | string, - ) { - if (x == null) { - return new Box(this.attr('viewBox')) - } - - return this.attr( - 'viewBox', - typeof x === 'object' - ? `${x.x} ${x.y} ${x.width} ${x.height}` - : `${x} ${y} ${width} ${height}`, - ) - } - - zoom(): number - zoom(level: number, origin?: Point.PointLike): this - zoom(level?: number, origin?: Point.PointLike) { - const node = this.node - let width = Util.tryConvertToNumber(node.getAttribute('width')) - let height = Util.tryConvertToNumber(node.getAttribute('height')) - - if ( - (width == null && height == null) || - typeof width === 'string' || - typeof height === 'string' - ) { - width = this.node.clientWidth - height = this.node.clientHeight - } - - if (width == null || height == null) { - throw new Error( - 'Impossible to get absolute width and height. ' + - 'Please provide an absolute width and height attribute on the zooming element', - ) - } - - const w = width as number - const h = height as number - const v = this.viewbox() - const zoomX = w / v.width - const zoomY = h / v.height - const zoom = Math.min(zoomX, zoomY) - - if (level == null) { - return zoom - } - - let zoomAmount = zoom / level - - // Set the zoomAmount to the highest value which is safe to process and - // recover from. - // The * 100 is a bit of wiggle room for the matrix transformation. - if (zoomAmount === Number.POSITIVE_INFINITY) { - zoomAmount = Number.MAX_SAFE_INTEGER / 100 - } - - const o = origin || { - x: w / 2 / zoomX + v.x, - y: h / 2 / zoomY + v.y, - } - - const box = new Box(v).transform({ scale: zoomAmount, origin: o }) - - return this.viewbox(box) - } -} diff --git a/packages/x6-vector/src/vector/container/wrapper.ts b/packages/x6-vector/src/vector/container/wrapper.ts deleted file mode 100644 index f55c1715244..00000000000 --- a/packages/x6-vector/src/vector/container/wrapper.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Vector } from '../vector/vector' -import { AttributesMap } from '../../dom/attributes' - -export class Wrapper< - TSVGElement extends SVGElement = SVGElement, -> extends Vector { - constructor() - constructor(attrs: AttributesMap | null) - constructor( - node: TSVGElement | null, - attrs?: AttributesMap | null, - ) - constructor( - node?: TSVGElement | AttributesMap | null, - attrs?: AttributesMap | null, - ) - // eslint-disable-next-line no-useless-constructor - constructor( - node?: TSVGElement | AttributesMap | null, - attrs?: AttributesMap | null, - ) { - super(node, attrs) - } -} diff --git a/packages/x6-vector/src/vector/defs/defs.ts b/packages/x6-vector/src/vector/defs/defs.ts deleted file mode 100644 index 1f8fdf07b40..00000000000 --- a/packages/x6-vector/src/vector/defs/defs.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Wrapper } from '../container/wrapper' - -@Defs.register('Defs') -export class Defs extends Wrapper {} diff --git a/packages/x6-vector/src/vector/defs/mixins.ts b/packages/x6-vector/src/vector/defs/mixins.ts deleted file mode 100644 index 4456abcebcd..00000000000 --- a/packages/x6-vector/src/vector/defs/mixins.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { applyMixins } from '../../util/mixin' -import { DefsExtension as MaskExtension } from '../mask/exts' -import { DefsExtension as MarkerExtension } from '../marker/exts' -import { DefsExtension as FilterExtension } from '../filter/exts' -import { DefsExtension as PatternExtension } from '../pattern/exts' -import { DefsExtension as ClipPathExtension } from '../clippath/exts' -import { DefsExtension as GradientExtension } from '../gradient/exts' -import { DefsExtension as LinearGradientExtension } from '../gradient/linear-exts' -import { DefsExtension as RadialGradientExtension } from '../gradient/radial-exts' -import { ContainerExtension as CircleExtension } from '../circle/exts' -import { ContainerExtension as EllipseExtension } from '../ellipse/ext' -import { ContainerExtension as ImageExtension } from '../image/exts' -import { ContainerExtension as LineExtension } from '../line/exts' -import { ContainerExtension as PathExtension } from '../path/exts' -import { ContainerExtension as PolygonExtension } from '../polygon/exts' -import { ContainerExtension as PolylineExtension } from '../polyline/exts' -import { ContainerExtension as RectExtension } from '../rect/exts' -import { ContainerExtension as TextExtension } from '../text/exts' - -import { Defs } from './defs' - -declare module './defs' { - interface Defs - extends ClipPathExtension, - MaskExtension, - MarkerExtension, - FilterExtension, - PatternExtension, - GradientExtension, - LinearGradientExtension, - RadialGradientExtension, - // shapes - RectExtension, - LineExtension, - TextExtension, - PathExtension, - ImageExtension, - CircleExtension, - EllipseExtension, - PolygonExtension, - PolylineExtension {} -} - -applyMixins( - Defs, - MaskExtension, - MarkerExtension, - FilterExtension, - PatternExtension, - ClipPathExtension, - GradientExtension, - LinearGradientExtension, - RadialGradientExtension, - // shapes - RectExtension, - LineExtension, - TextExtension, - PathExtension, - ImageExtension, - CircleExtension, - EllipseExtension, - PolygonExtension, - PolylineExtension, -) diff --git a/packages/x6-vector/src/vector/defs/types.ts b/packages/x6-vector/src/vector/defs/types.ts deleted file mode 100644 index 31c70aa2d2c..00000000000 --- a/packages/x6-vector/src/vector/defs/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, -} from '../types/attributes-core' - -export interface SVGDefsAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes {} diff --git a/packages/x6-vector/src/vector/desc/desc.test.ts b/packages/x6-vector/src/vector/desc/desc.test.ts deleted file mode 100644 index f617510447e..00000000000 --- a/packages/x6-vector/src/vector/desc/desc.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { G } from '../g/g' -import { Desc } from './desc' - -describe('Desc', () => { - describe('constructor()', () => { - it('should create a new object of type Desc', () => { - expect(new Desc()).toBeInstanceOf(Desc) - }) - - it('should set passed attributes on the element', () => { - expect(Desc.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from element', () => { - const desc = new G().desc('foo', { id: 'foo' }) - expect(desc.node.textContent).toEqual('foo') - expect(desc.id()).toBe('foo') - }) - }) - - describe('update()', () => { - it('should update desc with text', () => { - const desc = Desc.create() - desc.update('foo') - expect(desc.node.textContent).toEqual('foo') - }) - - it('should remove desc content when pass null', () => { - const desc = Desc.create('foo') - expect(desc.node.textContent).toEqual('foo') - desc.update(null) - expect(desc.node.textContent).toEqual('') - }) - - it('should update desc with callback', () => { - const desc = Desc.create() - desc.update((instance) => (instance.node.textContent = 'foo')) - expect(desc.node.textContent).toEqual('foo') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/desc/desc.ts b/packages/x6-vector/src/vector/desc/desc.ts deleted file mode 100644 index e92e0479c70..00000000000 --- a/packages/x6-vector/src/vector/desc/desc.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Base } from '../common/base' -import { SVGDescAttributes } from './types' - -@Desc.register('Desc') -export class Desc extends Base { - update(desc?: string | null | Desc.Update) { - if (typeof desc === 'string' || desc == null) { - this.node.textContent = desc || null - } else { - desc.call(this, this) - } - return this - } -} - -export namespace Desc { - export type Update = (this: Desc, pattern: Desc) => void - - export function create( - attrs?: Attributes | null, - ): Desc - export function create( - desc: string, - attrs?: Attributes | null, - ): Desc - export function create( - update: Update, - attrs?: Attributes | null, - ): Desc - export function create( - desc?: string | Update | Attributes | null, - attrs?: Attributes | null, - ): Desc - export function create( - desc?: string | Update | Attributes | null, - attrs?: Attributes | null, - ): Desc { - const instance = new Desc() - if (typeof desc === 'string' || typeof desc === 'function') { - instance.update(desc) - if (attrs) { - instance.attr(attrs) - } - } else if (desc) { - instance.attr(desc) - } - return instance - } -} diff --git a/packages/x6-vector/src/vector/desc/exts.ts b/packages/x6-vector/src/vector/desc/exts.ts deleted file mode 100644 index b847e896070..00000000000 --- a/packages/x6-vector/src/vector/desc/exts.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Base } from '../common/base' -import { Desc } from './desc' -import { SVGDescAttributes } from './types' - -export class ElementExtension< - TSVGElement extends SVGElement, -> extends Base { - desc(attrs?: Attributes | null): Desc - desc( - desc: string, - attrs?: Attributes | null, - ): Desc - desc( - update: Desc.Update, - attrs?: Attributes | null, - ): Desc - desc( - desc?: string | Desc.Update | Attributes | null, - attrs?: Attributes | null, - ) { - return Desc.create(desc, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/desc/types.ts b/packages/x6-vector/src/vector/desc/types.ts deleted file mode 100644 index 13078c9e57a..00000000000 --- a/packages/x6-vector/src/vector/desc/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { SVGCoreAttributes, SVGStyleAttributes } from '../types/attributes-core' - -export interface SVGDescAttributes - extends SVGCoreAttributes, - SVGStyleAttributes {} diff --git a/packages/x6-vector/src/vector/ellipse/ellipse.test.ts b/packages/x6-vector/src/vector/ellipse/ellipse.test.ts deleted file mode 100644 index 8a39ea3ac79..00000000000 --- a/packages/x6-vector/src/vector/ellipse/ellipse.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { G } from '../g/g' -import { Ellipse } from './ellipse' - -describe('Ellipse', () => { - describe('constructor()', () => { - it('should create a ellipse', () => { - expect(Ellipse.create()).toBeInstanceOf(Ellipse) - }) - - it('should create a ellipse with given attributes', () => { - expect(Ellipse.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create a ellipse with given width and height', () => { - const group = new G() - const ellipse = group.ellipse(100, 200) - expect(ellipse.attr(['rx', 'ry'])).toEqual({ - rx: 50, - ry: 100, - }) - }) - - it('should create a ellipse with given width, height and attributes', () => { - const group = new G() - const ellipse = group.ellipse(100, 200, { id: 'foo' }) - expect(ellipse.attr(['rx', 'ry'])).toEqual({ - rx: 50, - ry: 100, - }) - expect(ellipse).toBeInstanceOf(Ellipse) - expect(ellipse.id()).toBe('foo') - }) - - it('should create a ellipse with given size', () => { - const group = new G() - const ellipse = group.ellipse(100) - expect(ellipse.attr(['rx', 'ry'])).toEqual({ - rx: 50, - ry: 50, - }) - expect(ellipse).toBeInstanceOf(Ellipse) - }) - - it('should create a ellipse with given size and attributes', () => { - const group = new G() - const ellipse = group.ellipse(100, { id: 'foo' }) - expect(ellipse.attr(['rx', 'ry'])).toEqual({ - rx: 50, - ry: 50, - }) - expect(ellipse).toBeInstanceOf(Ellipse) - expect(ellipse.id()).toBe('foo') - }) - }) - - describe('rx()', () => { - it('should call attribute with rx and return itself', () => { - const ellipse = new Ellipse() - const spy = spyOn(ellipse, 'attr').and.callThrough() - expect(ellipse.rx(50)).toBe(ellipse) - expect(spy).toHaveBeenCalledWith('rx', 50) - }) - }) - - describe('ry()', () => { - it('should call attribute with ry and return itself', () => { - const ellipse = new Ellipse() - const spy = spyOn(ellipse, 'attr').and.callThrough() - expect(ellipse.ry(50)).toBe(ellipse) - expect(spy).toHaveBeenCalledWith('ry', 50) - }) - }) - - describe('radius()', () => { - it('should set rx and ry', () => { - const ellipse = new Ellipse().radius(5, 10) - expect(ellipse.attr('rx')).toEqual(5) - expect(ellipse.attr('ry')).toEqual(10) - }) - }) - - describe('x()', () => { - it('should set x position and returns itself', () => { - const ellipse = Ellipse.create(50, 50) - expect(ellipse.x(50)).toBe(ellipse) - expect(ellipse.bbox().x).toBe(50) - }) - - it('should get the x position', () => { - const ellipse = Ellipse.create(50, 50) - ellipse.x(50) - expect(ellipse.x()).toBe(50) - }) - }) - - describe('y()', () => { - it('should set y position and returns itself', () => { - const ellipse = Ellipse.create(50, 50) - expect(ellipse.y(50)).toBe(ellipse) - expect(ellipse.bbox().y).toBe(50) - }) - - it('should get the y position', () => { - const ellipse = Ellipse.create(50, 50) - ellipse.y(50) - expect(ellipse.y()).toBe(50) - }) - }) - - describe('cx()', () => { - it('should call attribute with cx and returns itself', () => { - const ellipse = Ellipse.create(50, 50) - const spy = spyOn(ellipse, 'attr').and.callThrough() - expect(ellipse.cx(50)).toBe(ellipse) - expect(spy).toHaveBeenCalledWith('cx', 50) - }) - }) - - describe('cy()', () => { - it('should call attribute with cy and returns itself', () => { - const ellipse = Ellipse.create(50, 50) - const spy = spyOn(ellipse, 'attr').and.callThrough() - expect(ellipse.cy(50)).toBe(ellipse) - expect(spy).toHaveBeenCalledWith('cy', 50) - }) - }) - - describe('width()', () => { - it('should set rx by half the given width', () => { - const ellipse = Ellipse.create(50, 50) - const spy = spyOn(ellipse, 'rx').and.callThrough() - expect(ellipse.width(50)).toBe(ellipse) - expect(spy).toHaveBeenCalledWith(25) - }) - - it('should get the width of the element', () => { - const ellipse = Ellipse.create() - ellipse.width(100) - expect(ellipse.width()).toBe(100) - }) - }) - - describe('height()', () => { - it('should set ry by half the given height', () => { - const ellipse = Ellipse.create() - const spy = spyOn(ellipse, 'ry').and.callThrough() - expect(ellipse.height(50)).toBe(ellipse) - expect(spy).toHaveBeenCalledWith(25) - }) - - it('should get the height of the element', () => { - const ellipse = Ellipse.create() - ellipse.height(100) - expect(ellipse.height()).toBe(100) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/ellipse/ellipse.ts b/packages/x6-vector/src/vector/ellipse/ellipse.ts deleted file mode 100644 index 5327d3e270c..00000000000 --- a/packages/x6-vector/src/vector/ellipse/ellipse.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { UnitNumber } from '../../struct/unit-number' -import { Size } from '../common/size' -import { Shape } from '../common/shape' -import { SVGEllipseAttributes } from './types' - -@Ellipse.register('Ellipse') -export class Ellipse extends Shape { - rx(): number - rx(rx: string | number | null): this - rx(rx?: string | number | null) { - return this.attr('rx', rx) - } - - ry(): number - ry(ry: string | number | null): this - ry(ry?: string | number | null) { - return this.attr('ry', ry) - } - - radius(rx: string | number, ry: string | number = rx) { - return this.rx(rx).ry(ry) - } - - size(width: string | number, height: string | number): this - size(width: string | number, height: string | number | null | undefined): this - size(width: string | number | null | undefined, height: string | number): this - size(width?: string | number | null, height?: string | number | null) { - const size = Size.normalize(this, width, height) - const rx = UnitNumber.divide(size.width, 2) - const ry = UnitNumber.divide(size.height, 2) - return this.rx(rx).ry(ry) - } - - x(): number - x(x?: null): number - x(x: string | number): this - x(x?: string | number | null) { - return x == null - ? this.cx() - this.rx() - : this.cx(UnitNumber.plus(x, this.rx())) - } - - y(): number - y(x: null): number - y(x: string | number): this - y(y?: string | number | null) { - return y == null - ? this.cy() - this.ry() - : this.cy(UnitNumber.plus(y, this.ry())) - } - - cx(): number - cx(x: string | number | null): this - cx(x?: string | number | null) { - return this.attr('cx', x) - } - - cy(): number - cy(y: string | number | null): this - cy(y?: string | number | null) { - return this.attr('cy', y) - } - - width(): number - width(w: null): number - width(w: string | number): this - width(w?: string | number | null) { - return w == null ? this.rx() * 2 : this.rx(UnitNumber.divide(w, 2)) - } - - height(): number - height(h: null): number - height(h: string | number): this - height(h?: string | number | null) { - return h == null ? this.ry() * 2 : this.ry(UnitNumber.divide(h, 2)) - } -} - -export namespace Ellipse { - export function create( - attrs?: Attributes | null, - ): Ellipse - export function create( - size: number | string, - attrs?: Attributes | null, - ): Ellipse - export function create( - width: number | string, - height: number | string, - attrs?: Attributes | null, - ): Ellipse - export function create( - width?: number | string | Attributes | null, - height?: number | string | Attributes | null, - attrs?: Attributes | null, - ): Ellipse - export function create( - width?: number | string | Attributes | null, - height?: number | string | Attributes | null, - attrs?: Attributes | null, - ) { - const ellipse = new Ellipse() - if (width == null) { - ellipse.size(0, 0) - } else if (typeof width === 'object') { - ellipse.size(0, 0).attr(width) - } else if (height != null && typeof height === 'object') { - ellipse.size(width, width).attr(height) - } else { - if (typeof height === 'undefined') { - ellipse.size(width, width) - } else { - ellipse.size(width, height) - } - - if (attrs) { - ellipse.attr(attrs) - } - } - - return ellipse - } -} diff --git a/packages/x6-vector/src/vector/ellipse/ext.ts b/packages/x6-vector/src/vector/ellipse/ext.ts deleted file mode 100644 index fcba8e4ffd9..00000000000 --- a/packages/x6-vector/src/vector/ellipse/ext.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Base } from '../common/base' -import { Ellipse } from './ellipse' -import { SVGEllipseAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - ellipse( - attrs?: Attributes | null, - ): Ellipse - ellipse( - size: number | string, - attrs?: Attributes | null, - ): Ellipse - ellipse( - width: number | string, - height: string | number, - attrs?: Attributes | null, - ): Ellipse - ellipse( - width?: number | string | Attributes | null, - height?: number | string | Attributes | null, - attrs?: Attributes | null, - ) { - return Ellipse.create(width, height, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/ellipse/types.ts b/packages/x6-vector/src/vector/ellipse/types.ts deleted file mode 100644 index 2f254af4adc..00000000000 --- a/packages/x6-vector/src/vector/ellipse/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGEllipseAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - cx?: string | number - cy?: string | number - r?: string | number - pathLength?: number -} diff --git a/packages/x6-vector/src/vector/fe-base/fe-base.test.ts b/packages/x6-vector/src/vector/fe-base/fe-base.test.ts deleted file mode 100644 index 1f35c948ea9..00000000000 --- a/packages/x6-vector/src/vector/fe-base/fe-base.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEBlend } from '../fe-blend/fe-blend' - -describe('FeBase', () => { - describe('filter()', () => { - it('should return null when element not appended to filter', () => { - expect(FEBlend.create().filter()).toBeNull() - }) - - it('should return the parent filter element', () => { - const filter = new Filter() - expect(filter.feBlend().filter()).toEqual(filter) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-base/fe-base.ts b/packages/x6-vector/src/vector/fe-base/fe-base.ts deleted file mode 100644 index bfb62a2b96f..00000000000 --- a/packages/x6-vector/src/vector/fe-base/fe-base.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Base } from '../common/base' -import { Filter } from '../filter/filter' - -export class FEBase< - T extends - | SVGFEBlendElement - | SVGFEColorMatrixElement - | SVGFEComponentTransferElement - | SVGFECompositeElement - | SVGFEConvolveMatrixElement - | SVGFEDiffuseLightingElement - | SVGFEDisplacementMapElement - | SVGFEDistantLightElement - | SVGFEFloodElement - | SVGFEFuncAElement - | SVGFEFuncBElement - | SVGFEFuncGElement - | SVGFEFuncRElement - | SVGFEGaussianBlurElement - | SVGFEImageElement - | SVGFEMergeElement - | SVGFEMergeNodeElement - | SVGFEMorphologyElement - | SVGFEOffsetElement - | SVGFEPointLightElement - | SVGFESpecularLightingElement - | SVGFESpotLightElement - | SVGFETileElement - | SVGFETurbulenceElement, -> extends Base { - filter(): Filter | null { - return this.parent(Filter) - } -} diff --git a/packages/x6-vector/src/vector/fe-blend/exts.ts b/packages/x6-vector/src/vector/fe-blend/exts.ts deleted file mode 100644 index bc9acdc5424..00000000000 --- a/packages/x6-vector/src/vector/fe-blend/exts.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Base } from '../common/base' -import { FEBlend } from './fe-blend' -import { SVGFEBlendAttributes } from './types' - -export class FilterExtension extends Base { - feBlend(attrs?: Attributes | null) { - return FEBlend.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-blend/fe-blend.test.ts b/packages/x6-vector/src/vector/fe-blend/fe-blend.test.ts deleted file mode 100644 index 2978137759b..00000000000 --- a/packages/x6-vector/src/vector/fe-blend/fe-blend.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEBlend } from './fe-blend' - -describe('FeBlend', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEBlend.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feBlend()).toBeInstanceOf(FEBlend) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const feBlend = filter.feBlend({ id: 'foo' }) - expect(feBlend.id()).toEqual('foo') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const feBlend = new FEBlend() - feBlend.in('BackgroundAlpha') - expect(feBlend.in()).toEqual('BackgroundAlpha') - expect(feBlend.attr('in')).toEqual('BackgroundAlpha') - }) - }) - - describe('in2()', () => { - it('should set in2 attribute', () => { - const feBlend = new FEBlend() - feBlend.in2('BackgroundAlpha') - expect(feBlend.in2()).toEqual('BackgroundAlpha') - expect(feBlend.attr('in2')).toEqual('BackgroundAlpha') - }) - }) - - describe('mode()', () => { - it('should set mode attribute', () => { - const feBlend = new FEBlend() - feBlend.mode('color') - expect(feBlend.mode()).toEqual('color') - expect(feBlend.attr('mode')).toEqual('color') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-blend/fe-blend.ts b/packages/x6-vector/src/vector/fe-blend/fe-blend.ts deleted file mode 100644 index 81440b1a556..00000000000 --- a/packages/x6-vector/src/vector/fe-blend/fe-blend.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEBlendAttributes, In, BlendMode } from './types' - -@FEBlend.register('FeBlend') -export class FEBlend extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - in2(): In | string - in2(type: In): this - in2(type: string): this - in2(type: null): this - in2(type?: In | string | null) { - return this.attr('in2', type) - } - - mode(): BlendMode - mode(units: BlendMode | null): this - mode(units?: BlendMode | null) { - return this.attr('mode', units) - } -} - -export namespace FEBlend { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEBlend() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-blend/types.ts b/packages/x6-vector/src/vector/fe-blend/types.ts deleted file mode 100644 index 65d74f27503..00000000000 --- a/packages/x6-vector/src/vector/fe-blend/types.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -export type BlendMode = - | 'normal' - | 'multiply' - | 'screen' - | 'overlay' - | 'darken' - | 'lighten' - | 'color-dodge' - | 'color-burn' - | 'hard-light' - | 'soft-light' - | 'difference' - | 'exclusion' - | 'hue' - | 'saturation' - | 'color' - | 'luminosity' - -export type In = - | 'SourceGraphic' - | 'SourceAlpha' - | 'BackgroundImage' - | 'BackgroundAlpha' - | 'FillPaint' - | 'StrokePaint' - -export interface SVGFEBlendAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string - in2?: In | string - mode?: BlendMode -} diff --git a/packages/x6-vector/src/vector/fe-color-matrix/exts.ts b/packages/x6-vector/src/vector/fe-color-matrix/exts.ts deleted file mode 100644 index af3a01d19b6..00000000000 --- a/packages/x6-vector/src/vector/fe-color-matrix/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { FEColorMatrix } from './fe-color-matrix' -import { SVGFEColorMatrixAttributes } from './types' - -export class FilterExtension extends Base { - feColorMatrix( - attrs?: Attributes | null, - ) { - return FEColorMatrix.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-color-matrix/fe-color-matrix.test.ts b/packages/x6-vector/src/vector/fe-color-matrix/fe-color-matrix.test.ts deleted file mode 100644 index a58d6e6e99f..00000000000 --- a/packages/x6-vector/src/vector/fe-color-matrix/fe-color-matrix.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEColorMatrix } from './fe-color-matrix' - -describe('FeColorMatrix', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEColorMatrix.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feColorMatrix()).toBeInstanceOf(FEColorMatrix) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const feColorMatrix = filter.feColorMatrix({ id: 'foo' }) - expect(feColorMatrix.id()).toEqual('foo') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const feColorMatrix = new FEColorMatrix() - feColorMatrix.in('BackgroundAlpha') - expect(feColorMatrix.in()).toEqual('BackgroundAlpha') - expect(feColorMatrix.attr('in')).toEqual('BackgroundAlpha') - }) - }) - - describe('feType()', () => { - it('should set type attribute', () => { - const feColorMatrix = new FEColorMatrix() - feColorMatrix.feType('hueRotate') - expect(feColorMatrix.feType()).toEqual('hueRotate') - expect(feColorMatrix.attr('type')).toEqual('hueRotate') - }) - }) - - describe('values()', () => { - it('should set values attribute', () => { - const feColorMatrix = new FEColorMatrix() - const values = `0 0 0 0 0 - 1 1 1 1 0 - 0 0 0 0 0 - 0 0 0 1 0` - feColorMatrix.values(values) - expect(feColorMatrix.values()).toEqual(values) - expect(feColorMatrix.attr('values')).toEqual(values) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-color-matrix/fe-color-matrix.ts b/packages/x6-vector/src/vector/fe-color-matrix/fe-color-matrix.ts deleted file mode 100644 index 1f7ad2eca7f..00000000000 --- a/packages/x6-vector/src/vector/fe-color-matrix/fe-color-matrix.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEColorMatrixAttributes, In, Type } from './types' - -@FEColorMatrix.register('FeColorMatrix') -export class FEColorMatrix extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - feType(): Type - feType(type: Type | null): this - feType(type?: Type | null) { - return this.attr('type', type) - } - - values(): string - values(values: string | null): this - values(values?: string | null) { - return this.attr('values', values) - } -} - -export namespace FEColorMatrix { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEColorMatrix() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-color-matrix/types.ts b/packages/x6-vector/src/vector/fe-color-matrix/types.ts deleted file mode 100644 index ada6a46f114..00000000000 --- a/packages/x6-vector/src/vector/fe-color-matrix/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -import { In } from '../fe-blend/types' - -export type Type = 'matrix' | 'saturate' | 'hueRotate' | 'luminanceToAlpha' - -export interface SVGFEColorMatrixAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string - type?: Type - values?: string -} - -export { In } diff --git a/packages/x6-vector/src/vector/fe-component-transfer/exts.ts b/packages/x6-vector/src/vector/fe-component-transfer/exts.ts deleted file mode 100644 index f3b10094d6d..00000000000 --- a/packages/x6-vector/src/vector/fe-component-transfer/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVGFEComponentTransferAttributes } from './types' -import { FEComponentTransfer } from './fe-component-transfer' - -export class FilterExtension extends Base { - feComponentTransfer( - attrs?: Attributes | null, - ) { - return FEComponentTransfer.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-component-transfer/fe-component-transfer.test.ts b/packages/x6-vector/src/vector/fe-component-transfer/fe-component-transfer.test.ts deleted file mode 100644 index b93f5c209cf..00000000000 --- a/packages/x6-vector/src/vector/fe-component-transfer/fe-component-transfer.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { FEFuncA } from '../fe-func-a/fe-func-a' -import { FEFuncB } from '../fe-func-b/fe-func-b' -import { FEFuncG } from '../fe-func-g/fe-func-g' -import { FEFuncR } from '../fe-func-r/fe-func-r' -import { Filter } from '../filter/filter' -import { FEComponentTransfer } from './fe-component-transfer' - -describe('FeBlend', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEComponentTransfer.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feComponentTransfer()).toBeInstanceOf(FEComponentTransfer) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feComponentTransfer({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('feFuncA()', () => { - it('should create an instance of FEFuncA', () => { - const fe = new FEComponentTransfer() - const fun = fe.feFuncA() - expect(fun).toBeInstanceOf(FEFuncA) - }) - - it('should create an instance of FEFuncA with given attributes', () => { - const fe = new FEComponentTransfer() - const light = fe.feFuncA({ id: 'bar' }) - expect(light).toBeInstanceOf(FEFuncA) - expect(light.id()).toEqual('bar') - }) - }) - - describe('feFuncB()', () => { - it('should create an instance of FEFuncB', () => { - const fe = new FEComponentTransfer() - const fun = fe.feFuncB() - expect(fun).toBeInstanceOf(FEFuncB) - }) - - it('should create an instance of FEFuncB with given attributes', () => { - const fe = new FEComponentTransfer() - const light = fe.feFuncB({ id: 'bar' }) - expect(light).toBeInstanceOf(FEFuncB) - expect(light.id()).toEqual('bar') - }) - }) - - describe('feFuncG()', () => { - it('should create an instance of FEFuncG', () => { - const fe = new FEComponentTransfer() - const fun = fe.feFuncG() - expect(fun).toBeInstanceOf(FEFuncG) - }) - - it('should create an instance of FEFuncG with given attributes', () => { - const fe = new FEComponentTransfer() - const light = fe.feFuncG({ id: 'bar' }) - expect(light).toBeInstanceOf(FEFuncG) - expect(light.id()).toEqual('bar') - }) - }) - - describe('feFuncR()', () => { - it('should create an instance of FEFuncR', () => { - const fe = new FEComponentTransfer() - const fun = fe.feFuncR() - expect(fun).toBeInstanceOf(FEFuncR) - }) - - it('should create an instance of FEFuncR with given attributes', () => { - const fe = new FEComponentTransfer() - const light = fe.feFuncR({ id: 'bar' }) - expect(light).toBeInstanceOf(FEFuncR) - expect(light.id()).toEqual('bar') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FEComponentTransfer() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-component-transfer/fe-component-transfer.ts b/packages/x6-vector/src/vector/fe-component-transfer/fe-component-transfer.ts deleted file mode 100644 index 954a6149b5f..00000000000 --- a/packages/x6-vector/src/vector/fe-component-transfer/fe-component-transfer.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEFuncAAttributes } from '../fe-func-a/types' -import { SVGFEFuncBAttributes } from '../fe-func-b/types' -import { SVGFEFuncGAttributes } from '../fe-func-g/types' -import { SVGFEFuncRAttributes } from '../fe-func-r/types' -import { FEFuncA } from '../fe-func-a/fe-func-a' -import { FEFuncB } from '../fe-func-b/fe-func-b' -import { FEFuncG } from '../fe-func-g/fe-func-g' -import { FEFuncR } from '../fe-func-r/fe-func-r' -import { SVGFEComponentTransferAttributes, In } from './types' - -@FEComponentTransfer.register('FeComponentTransfer') -export class FEComponentTransfer extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - feFuncA(attrs?: Attributes | null) { - return FEFuncA.create(attrs).appendTo(this) - } - - feFuncB(attrs?: Attributes | null) { - return FEFuncB.create(attrs).appendTo(this) - } - - feFuncG(attrs?: Attributes | null) { - return FEFuncG.create(attrs).appendTo(this) - } - - feFuncR(attrs?: Attributes | null) { - return FEFuncR.create(attrs).appendTo(this) - } -} - -export namespace FEComponentTransfer { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEComponentTransfer() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-component-transfer/types.ts b/packages/x6-vector/src/vector/fe-component-transfer/types.ts deleted file mode 100644 index addd8505afc..00000000000 --- a/packages/x6-vector/src/vector/fe-component-transfer/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -import { In } from '../fe-blend/types' - -export interface SVGFEComponentTransferAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string -} - -export { In } diff --git a/packages/x6-vector/src/vector/fe-composite/exts.ts b/packages/x6-vector/src/vector/fe-composite/exts.ts deleted file mode 100644 index 3ecbaa645a4..00000000000 --- a/packages/x6-vector/src/vector/fe-composite/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVGFECompositeAttributes } from './types' -import { FEComposite } from './fe-composite' - -export class FilterExtension extends Base { - feComposite( - attrs?: Attributes | null, - ) { - return FEComposite.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-composite/fe-composite.test.ts b/packages/x6-vector/src/vector/fe-composite/fe-composite.test.ts deleted file mode 100644 index b81c6b8a50e..00000000000 --- a/packages/x6-vector/src/vector/fe-composite/fe-composite.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEComposite } from './fe-composite' - -describe('FEComposite', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEComposite.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feComposite()).toBeInstanceOf(FEComposite) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feComposite({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FEComposite() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) - - describe('in2()', () => { - it('should set in2 attribute', () => { - const fe = new FEComposite() - fe.in2('BackgroundAlpha') - expect(fe.in2()).toEqual('BackgroundAlpha') - expect(fe.attr('in2')).toEqual('BackgroundAlpha') - }) - }) - - describe('operator()', () => { - it('should set operator attribute', () => { - const fe = new FEComposite() - fe.operator('arithmetic') - expect(fe.operator()).toEqual('arithmetic') - expect(fe.attr('operator')).toEqual('arithmetic') - }) - }) - - describe('k1()', () => { - it('should set k1 attribute', () => { - const fe = new FEComposite() - fe.k1(1) - expect(fe.k1()).toEqual(1) - expect(fe.attr('k1')).toEqual(1) - }) - }) - - describe('k2()', () => { - it('should set k2 attribute', () => { - const fe = new FEComposite() - fe.k2(2) - expect(fe.k2()).toEqual(2) - expect(fe.attr('k2')).toEqual(2) - }) - }) - - describe('k3()', () => { - it('should set k3 attribute', () => { - const fe = new FEComposite() - fe.k3(3) - expect(fe.k3()).toEqual(3) - expect(fe.attr('k3')).toEqual(3) - }) - }) - - describe('k4()', () => { - it('should set k4 attribute', () => { - const fe = new FEComposite() - fe.k4(4) - expect(fe.k4()).toEqual(4) - expect(fe.attr('k4')).toEqual(4) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-composite/fe-composite.ts b/packages/x6-vector/src/vector/fe-composite/fe-composite.ts deleted file mode 100644 index 92418e5f030..00000000000 --- a/packages/x6-vector/src/vector/fe-composite/fe-composite.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFECompositeAttributes, In, Operator } from './types' - -@FEComposite.register('FeComposite') -export class FEComposite extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - in2(): In | string - in2(type: In): this - in2(type: string): this - in2(type: null): this - in2(type?: In | string | null) { - return this.attr('in2', type) - } - - operator(): Operator - operator(operator: Operator | null): this - operator(operator?: Operator | null) { - return this.attr('operator', operator) - } - - k1(): number - k1(v: number | null): this - k1(v?: number | null) { - return this.attr('k1', v) - } - - k2(): number - k2(v: number | null): this - k2(v?: number | null) { - return this.attr('k2', v) - } - - k3(): number - k3(v: number | null): this - k3(v?: number | null) { - return this.attr('k3', v) - } - - k4(): number - k4(v: number | null): this - k4(v?: number | null) { - return this.attr('k4', v) - } -} - -export namespace FEComposite { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEComposite() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-composite/types.ts b/packages/x6-vector/src/vector/fe-composite/types.ts deleted file mode 100644 index 05fc9159819..00000000000 --- a/packages/x6-vector/src/vector/fe-composite/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -import { In } from '../fe-blend/types' - -export type Operator = - | 'over' - | 'in' - | 'out' - | 'atop' - | 'xor' - | 'lighter' - | 'arithmetic' - -export interface SVGFECompositeAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string - in2?: In | string - operator?: Operator - k1?: number - k2?: number - k3?: number - k4?: number -} - -export { In } diff --git a/packages/x6-vector/src/vector/fe-convolve-matrix/exts.ts b/packages/x6-vector/src/vector/fe-convolve-matrix/exts.ts deleted file mode 100644 index 6a65ec42c7c..00000000000 --- a/packages/x6-vector/src/vector/fe-convolve-matrix/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVGFEConvolveMatrixAttributes } from './types' -import { FEConvolveMatrix } from './fe-convolve-matrix' - -export class FilterExtension extends Base { - feConvolveMatrix( - attrs?: Attributes | null, - ) { - return FEConvolveMatrix.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-convolve-matrix/fe-convolve-matrix.test.ts b/packages/x6-vector/src/vector/fe-convolve-matrix/fe-convolve-matrix.test.ts deleted file mode 100644 index 4b139527d91..00000000000 --- a/packages/x6-vector/src/vector/fe-convolve-matrix/fe-convolve-matrix.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEConvolveMatrix } from './fe-convolve-matrix' - -describe('FEConvolveMatrix', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEConvolveMatrix.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feConvolveMatrix()).toBeInstanceOf(FEConvolveMatrix) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feConvolveMatrix({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FEConvolveMatrix() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) - - describe('edgeMode()', () => { - it('should set edgeMode attribute', () => { - const fe = new FEConvolveMatrix() - fe.edgeMode('duplicate') - expect(fe.edgeMode()).toEqual('duplicate') - expect(fe.attr('edgeMode')).toEqual('duplicate') - }) - }) - - describe('targetX()', () => { - it('should set targetX attribute', () => { - const fe = new FEConvolveMatrix() - fe.targetX(1) - expect(fe.targetX()).toEqual(1) - expect(fe.attr('targetX')).toEqual(1) - }) - }) - - describe('targetY()', () => { - it('should set targetY attribute', () => { - const fe = new FEConvolveMatrix() - fe.targetY(1) - expect(fe.targetY()).toEqual(1) - expect(fe.attr('targetY')).toEqual(1) - }) - }) - - describe('order()', () => { - it('should set order attribute', () => { - const fe = new FEConvolveMatrix() - fe.order(0) - expect(fe.order()).toEqual(0) - expect(fe.attr('order')).toEqual(0) - }) - }) - - describe('divisor()', () => { - it('should set divisor attribute', () => { - const fe = new FEConvolveMatrix() - fe.divisor(0.5) - expect(fe.divisor()).toEqual(0.5) - expect(fe.attr('divisor')).toEqual(0.5) - }) - }) - - describe('bias()', () => { - it('should set bias attribute', () => { - const fe = new FEConvolveMatrix() - fe.bias(0.5) - expect(fe.bias()).toEqual(0.5) - expect(fe.attr('bias')).toEqual(0.5) - }) - }) - - describe('kernelMatrix()', () => { - it('should set kernelMatrix attribute', () => { - const fe = new FEConvolveMatrix() - const values = `0 0 0 0 0 - 1 1 1 1 0 - 0 0 0 0 0 - 0 0 0 1 0` - fe.kernelMatrix(values) - expect(fe.kernelMatrix()).toEqual(values) - expect(fe.attr('kernelMatrix')).toEqual(values) - }) - }) - - describe('kernelUnitLength()', () => { - it('should set kernelUnitLength attribute', () => { - const fe = new FEConvolveMatrix() - fe.kernelUnitLength(5) - expect(fe.kernelUnitLength()).toEqual(5) - expect(fe.attr('kernelUnitLength')).toEqual(5) - }) - }) - - describe('preserveAlpha()', () => { - it('should set preserveAlpha attribute', () => { - const fe = new FEConvolveMatrix() - fe.preserveAlpha(false) - expect(fe.preserveAlpha()).toBeFalse() - expect(fe.attr('preserveAlpha')).toBeFalse() - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-convolve-matrix/fe-convolve-matrix.ts b/packages/x6-vector/src/vector/fe-convolve-matrix/fe-convolve-matrix.ts deleted file mode 100644 index 0e7477ef5ee..00000000000 --- a/packages/x6-vector/src/vector/fe-convolve-matrix/fe-convolve-matrix.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEConvolveMatrixAttributes, In, EdgeMode } from './types' - -@FEConvolveMatrix.register('FeConvolveMatrix') -export class FEConvolveMatrix extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - edgeMode(): EdgeMode - edgeMode(mode: EdgeMode | null): this - edgeMode(mode?: EdgeMode | null) { - return this.attr('edgeMode', mode) - } - - targetX(): number - targetX(x: number | null): this - targetX(x?: number | null) { - return this.attr('targetX', x) - } - - targetY(): number - targetY(y: number | null): this - targetY(y?: number | null) { - return this.attr('targetY', y) - } - - order(): number - order(order: number | null): this - order(order?: number | null) { - return this.attr('order', order) - } - - divisor(): number - divisor(divisor: number | null): this - divisor(divisor?: number | null) { - return this.attr('divisor', divisor) - } - - bias(): number - bias(bias: number | null): this - bias(bias?: number | null) { - return this.attr('bias', bias) - } - - kernelMatrix(): string - kernelMatrix(matrix: string | null): this - kernelMatrix(matrix?: string | null) { - return this.attr('kernelMatrix', matrix) - } - - kernelUnitLength(): number - kernelUnitLength(len: number | null): this - kernelUnitLength(len?: number | null) { - return this.attr('kernelUnitLength', len) - } - - preserveAlpha(): boolean - preserveAlpha(preserveAlpha: boolean | null): this - preserveAlpha(preserveAlpha?: boolean | null) { - return this.attr('preserveAlpha', preserveAlpha) - } -} - -export namespace FEConvolveMatrix { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEConvolveMatrix() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-convolve-matrix/types.ts b/packages/x6-vector/src/vector/fe-convolve-matrix/types.ts deleted file mode 100644 index 0d0f2ccf0f6..00000000000 --- a/packages/x6-vector/src/vector/fe-convolve-matrix/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' -import { In } from '../fe-blend/types' - -export { In } - -export type EdgeMode = 'duplicate' | 'wrap' | 'none' - -export interface SVGFEConvolveMatrixAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string - order?: number - kernelMatrix?: string - divisor?: number - bias?: number - targetX?: number - targetY?: number - edgeMode?: EdgeMode - kernelUnitLength?: number - preserveAlpha?: boolean -} diff --git a/packages/x6-vector/src/vector/fe-diffuse-lighting/exts.ts b/packages/x6-vector/src/vector/fe-diffuse-lighting/exts.ts deleted file mode 100644 index c405ee211c8..00000000000 --- a/packages/x6-vector/src/vector/fe-diffuse-lighting/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVGFEDiffuseLightingAttributes } from './types' -import { FEDiffuseLighting } from './fe-diffuse-lighting' - -export class FilterExtension extends Base { - feDiffuseLighting( - attrs?: Attributes | null, - ) { - return FEDiffuseLighting.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-diffuse-lighting/fe-diffuse-lighting.test.ts b/packages/x6-vector/src/vector/fe-diffuse-lighting/fe-diffuse-lighting.test.ts deleted file mode 100644 index fb7f8e0baa3..00000000000 --- a/packages/x6-vector/src/vector/fe-diffuse-lighting/fe-diffuse-lighting.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { FEDistantLight } from '../fe-distant-light/fe-distant-light' -import { FEPointLight } from '../fe-point-light/fe-point-light' -import { FESpotLight } from '../fe-spot-light/fe-spot-light' -import { Filter } from '../filter/filter' -import { FEDiffuseLighting } from './fe-diffuse-lighting' - -describe('FEDiffuseLighting', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEDiffuseLighting.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feDiffuseLighting()).toBeInstanceOf(FEDiffuseLighting) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feDiffuseLighting({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('feDistantLight()', () => { - it('should create an instance of FEDistantLight', () => { - const fe = new FEDiffuseLighting() - const light = fe.feDistantLight() - expect(light).toBeInstanceOf(FEDistantLight) - }) - - it('should create an instance of FEDistantLight with given attributes', () => { - const fe = new FEDiffuseLighting() - const light = fe.feDistantLight({ id: 'bar' }) - expect(light).toBeInstanceOf(FEDistantLight) - expect(light.id()).toEqual('bar') - }) - }) - - describe('fePointLight()', () => { - it('should create an instance of FEPointLight', () => { - const fe = new FEDiffuseLighting() - const light = fe.fePointLight() - expect(light).toBeInstanceOf(FEPointLight) - }) - - it('should create an instance of FEPointLight with given attributes', () => { - const fe = new FEDiffuseLighting() - const light = fe.fePointLight({ id: 'bar' }) - expect(light).toBeInstanceOf(FEPointLight) - expect(light.id()).toEqual('bar') - }) - }) - - describe('feSpotLight()', () => { - it('should create an instance of FESpotLight', () => { - const fe = new FEDiffuseLighting() - const light = fe.feSpotLight() - expect(light).toBeInstanceOf(FESpotLight) - }) - - it('should create an instance of FESpotLight with given attributes', () => { - const fe = new FEDiffuseLighting() - const light = fe.feSpotLight({ id: 'bar' }) - expect(light).toBeInstanceOf(FESpotLight) - expect(light.id()).toEqual('bar') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FEDiffuseLighting() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) - - describe('surfaceScale()', () => { - it('should set surfaceScale attribute', () => { - const fe = new FEDiffuseLighting() - fe.surfaceScale(1) - expect(fe.surfaceScale()).toEqual(1) - expect(fe.attr('surfaceScale')).toEqual(1) - }) - }) - - describe('diffuseConstant()', () => { - it('should set diffuseConstant attribute', () => { - const fe = new FEDiffuseLighting() - fe.diffuseConstant(1) - expect(fe.diffuseConstant()).toEqual(1) - expect(fe.attr('diffuseConstant')).toEqual(1) - }) - }) - - describe('kernelUnitLength()', () => { - it('should set kernelUnitLength attribute', () => { - const fe = new FEDiffuseLighting() - fe.kernelUnitLength(0) - expect(fe.kernelUnitLength()).toEqual(0) - expect(fe.attr('kernelUnitLength')).toEqual(0) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-diffuse-lighting/fe-diffuse-lighting.ts b/packages/x6-vector/src/vector/fe-diffuse-lighting/fe-diffuse-lighting.ts deleted file mode 100644 index 04cf7ecd40a..00000000000 --- a/packages/x6-vector/src/vector/fe-diffuse-lighting/fe-diffuse-lighting.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEDiffuseLightingAttributes, In } from './types' -import { SVGFESpotLightAttributes } from '../fe-spot-light/types' -import { SVGFEPointLightAttributes } from '../fe-point-light/types' -import { SVGFEDistantLightAttributes } from '../fe-distant-light/types' -import { FESpotLight } from '../fe-spot-light/fe-spot-light' -import { FEPointLight } from '../fe-point-light/fe-point-light' -import { FEDistantLight } from '../fe-distant-light/fe-distant-light' - -@FEDiffuseLighting.register('FeDiffuseLighting') -export class FEDiffuseLighting extends FEBase { - feDistantLight( - attrs?: Attributes | null, - ) { - return FEDistantLight.create(attrs).appendTo(this) - } - - fePointLight( - attrs?: Attributes | null, - ) { - return FEPointLight.create(attrs).appendTo(this) - } - - feSpotLight( - attrs?: Attributes | null, - ) { - return FESpotLight.create(attrs).appendTo(this) - } - - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - surfaceScale(): number - surfaceScale(v: number | null): this - surfaceScale(v?: number | null) { - return this.attr('surfaceScale', v) - } - - diffuseConstant(): number - diffuseConstant(v: number | null): this - diffuseConstant(v?: number | null) { - return this.attr('diffuseConstant', v) - } - - kernelUnitLength(): number - kernelUnitLength(v: number | null): this - kernelUnitLength(v?: number | null) { - return this.attr('kernelUnitLength', v) - } -} - -export namespace FEDiffuseLighting { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEDiffuseLighting() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-diffuse-lighting/types.ts b/packages/x6-vector/src/vector/fe-diffuse-lighting/types.ts deleted file mode 100644 index 65c8cfcb99d..00000000000 --- a/packages/x6-vector/src/vector/fe-diffuse-lighting/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -import { In } from '../fe-blend/types' - -export { In } - -export interface SVGFEDiffuseLightingAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string - surfaceScale?: number - diffuseConstant?: number - kernelUnitLength?: number -} diff --git a/packages/x6-vector/src/vector/fe-displacement-map/exts.ts b/packages/x6-vector/src/vector/fe-displacement-map/exts.ts deleted file mode 100644 index 8cd6e189e90..00000000000 --- a/packages/x6-vector/src/vector/fe-displacement-map/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVGFEDisplacementMapAttributes } from './types' -import { FEDisplacementMap } from './fe-displacement-map' - -export class FilterExtension extends Base { - feDisplacementMap( - attrs?: Attributes | null, - ) { - return FEDisplacementMap.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-displacement-map/fe-displacement-map.test.ts b/packages/x6-vector/src/vector/fe-displacement-map/fe-displacement-map.test.ts deleted file mode 100644 index e772b4002d2..00000000000 --- a/packages/x6-vector/src/vector/fe-displacement-map/fe-displacement-map.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEDisplacementMap } from './fe-displacement-map' - -describe('FEDisplacementMap', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEDisplacementMap.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feDisplacementMap()).toBeInstanceOf(FEDisplacementMap) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feDisplacementMap({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FEDisplacementMap() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) - - describe('in2()', () => { - it('should set in2 attribute', () => { - const fe = new FEDisplacementMap() - fe.in2('BackgroundAlpha') - expect(fe.in2()).toEqual('BackgroundAlpha') - expect(fe.attr('in2')).toEqual('BackgroundAlpha') - }) - }) - - describe('feScale()', () => { - it('should set scale attribute', () => { - const fe = new FEDisplacementMap() - fe.feScale(1) - expect(fe.feScale()).toEqual(1) - expect(fe.attr('scale')).toEqual(1) - }) - }) - - describe('diffuseConstant()', () => { - it('should set diffuseConstant attribute', () => { - const fe = new FEDisplacementMap() - fe.xChannelSelector('A') - expect(fe.xChannelSelector()).toEqual('A') - expect(fe.attr('xChannelSelector')).toEqual('A') - }) - }) - - describe('yChannelSelector()', () => { - it('should set yChannelSelector attribute', () => { - const fe = new FEDisplacementMap() - fe.yChannelSelector('B') - expect(fe.yChannelSelector()).toEqual('B') - expect(fe.attr('yChannelSelector')).toEqual('B') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-displacement-map/fe-displacement-map.ts b/packages/x6-vector/src/vector/fe-displacement-map/fe-displacement-map.ts deleted file mode 100644 index 1368fc9fa04..00000000000 --- a/packages/x6-vector/src/vector/fe-displacement-map/fe-displacement-map.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEDisplacementMapAttributes, In, Channel } from './types' - -@FEDisplacementMap.register('FeDisplacementMap') -export class FEDisplacementMap extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - in2(): In | string - in2(type: In): this - in2(type: string): this - in2(type: null): this - in2(type?: In | string | null) { - return this.attr('in2', type) - } - - feScale(): number - feScale(v: number | null): this - feScale(v?: number | null) { - return this.attr('scale', v) - } - - xChannelSelector(): Channel - xChannelSelector(v: Channel | null): this - xChannelSelector(v?: Channel | null) { - return this.attr('xChannelSelector', v) - } - - yChannelSelector(): Channel - yChannelSelector(v: Channel | null): this - yChannelSelector(v?: Channel | null) { - return this.attr('yChannelSelector', v) - } -} - -export namespace FEDisplacementMap { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEDisplacementMap() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-displacement-map/types.ts b/packages/x6-vector/src/vector/fe-displacement-map/types.ts deleted file mode 100644 index a6f244eb0d7..00000000000 --- a/packages/x6-vector/src/vector/fe-displacement-map/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' -import { In } from '../fe-blend/types' - -export { In } - -export type Channel = 'R' | 'G' | 'B' | 'A' - -export interface SVGFEDisplacementMapAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string - in2?: In | string - scale?: number - xChannelSelector?: Channel - yChannelSelector?: Channel -} diff --git a/packages/x6-vector/src/vector/fe-distant-light/fe-distant-light.test.ts b/packages/x6-vector/src/vector/fe-distant-light/fe-distant-light.test.ts deleted file mode 100644 index 440fb18f0bf..00000000000 --- a/packages/x6-vector/src/vector/fe-distant-light/fe-distant-light.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { FEDistantLight } from './fe-distant-light' - -describe('FEDistantLight', () => { - describe('azimuth()', () => { - it('should set azimuth attribute', () => { - const fe = new FEDistantLight() - fe.azimuth(1) - expect(fe.azimuth()).toEqual(1) - expect(fe.attr('azimuth')).toEqual(1) - }) - }) - - describe('elevation()', () => { - it('should set elevation attribute', () => { - const fe = new FEDistantLight() - fe.elevation(1) - expect(fe.elevation()).toEqual(1) - expect(fe.attr('elevation')).toEqual(1) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-distant-light/fe-distant-light.ts b/packages/x6-vector/src/vector/fe-distant-light/fe-distant-light.ts deleted file mode 100644 index 9c0c4336b70..00000000000 --- a/packages/x6-vector/src/vector/fe-distant-light/fe-distant-light.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEDistantLightAttributes } from './types' - -@FEDistantLight.register('FeDistantLight') -export class FEDistantLight extends FEBase { - azimuth(): number - azimuth(v: number | null): this - azimuth(v?: number | null) { - return this.attr('azimuth', v) - } - - elevation(): number - elevation(v: number | null): this - elevation(v?: number | null) { - return this.attr('elevation', v) - } -} - -export namespace FEDistantLight { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEDistantLight() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-distant-light/types.ts b/packages/x6-vector/src/vector/fe-distant-light/types.ts deleted file mode 100644 index 907704c4a15..00000000000 --- a/packages/x6-vector/src/vector/fe-distant-light/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SVGCoreAttributes } from '../types/attributes-core' - -export interface SVGFEDistantLightAttributes - extends SVGCoreAttributes { - azimuth?: number - elevation?: number -} diff --git a/packages/x6-vector/src/vector/fe-flood/exts.ts b/packages/x6-vector/src/vector/fe-flood/exts.ts deleted file mode 100644 index 4902e6c71d3..00000000000 --- a/packages/x6-vector/src/vector/fe-flood/exts.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Base } from '../common/base' -import { FEFlood } from './fe-flood' -import { SVGFEFloodAttributes } from './types' - -export class FilterExtension extends Base { - feFlood(attrs?: Attributes | null) { - return FEFlood.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-flood/fe-flood.test.ts b/packages/x6-vector/src/vector/fe-flood/fe-flood.test.ts deleted file mode 100644 index 93a31a94ced..00000000000 --- a/packages/x6-vector/src/vector/fe-flood/fe-flood.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEFlood } from './fe-flood' - -describe('FEFlood', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEFlood.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feFlood()).toBeInstanceOf(FEFlood) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feFlood({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('opacity()', () => { - it('should set flood-opacity attribute', () => { - const fe = new FEFlood() - fe.opacity(0.1) - expect(fe.opacity()).toEqual(0.1) - expect(fe.attr('floodOpacity')).toEqual(0.1) - }) - }) - - describe('color()', () => { - it('should set flood-color attribute', () => { - const fe = new FEFlood() - fe.color('red') - expect(fe.color()).toEqual('red') - expect(fe.attr('floodColor')).toEqual('red') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-flood/fe-flood.ts b/packages/x6-vector/src/vector/fe-flood/fe-flood.ts deleted file mode 100644 index 560a9709876..00000000000 --- a/packages/x6-vector/src/vector/fe-flood/fe-flood.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEFloodAttributes } from './types' - -@FEFlood.register('FeFlood') -export class FEFlood extends FEBase { - color(): string - color(color: string | null): this - color(color?: string | null) { - return this.attr('floodColor', color) - } - - opacity(): number - opacity(opacity: number | null): this - opacity(opacity?: number | null) { - return this.attr('floodOpacity', opacity) - } -} - -export namespace FEFlood { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEFlood() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-flood/types.ts b/packages/x6-vector/src/vector/fe-flood/types.ts deleted file mode 100644 index ef769d1f4a7..00000000000 --- a/packages/x6-vector/src/vector/fe-flood/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -export interface SVGFEFloodAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - floodColor?: string - floodOpacity?: number -} diff --git a/packages/x6-vector/src/vector/fe-func-a/fe-func-a.ts b/packages/x6-vector/src/vector/fe-func-a/fe-func-a.ts deleted file mode 100644 index a0c7ed13af8..00000000000 --- a/packages/x6-vector/src/vector/fe-func-a/fe-func-a.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEFuncAAttributes } from './types' - -@FEFuncA.register('FeFuncA') -export class FEFuncA extends FEBase {} - -export namespace FEFuncA { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEFuncA() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-func-a/types.ts b/packages/x6-vector/src/vector/fe-func-a/types.ts deleted file mode 100644 index 2725b352c97..00000000000 --- a/packages/x6-vector/src/vector/fe-func-a/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { - SVGCommonAttributes, - SVGTransferFunctionAttributes, -} from '../types/attributes-core' - -export interface SVGFEFuncAAttributes - extends SVGCommonAttributes, - SVGTransferFunctionAttributes {} diff --git a/packages/x6-vector/src/vector/fe-func-b/fe-func-b.ts b/packages/x6-vector/src/vector/fe-func-b/fe-func-b.ts deleted file mode 100644 index 1f2614ebafe..00000000000 --- a/packages/x6-vector/src/vector/fe-func-b/fe-func-b.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEFuncBAttributes } from './types' - -@FEFuncB.register('FeFuncB') -export class FEFuncB extends FEBase {} - -export namespace FEFuncB { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEFuncB() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-func-b/types.ts b/packages/x6-vector/src/vector/fe-func-b/types.ts deleted file mode 100644 index f2764a76209..00000000000 --- a/packages/x6-vector/src/vector/fe-func-b/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { - SVGCommonAttributes, - SVGTransferFunctionAttributes, -} from '../types/attributes-core' - -export interface SVGFEFuncBAttributes - extends SVGCommonAttributes, - SVGTransferFunctionAttributes {} diff --git a/packages/x6-vector/src/vector/fe-func-g/fe-func-g.ts b/packages/x6-vector/src/vector/fe-func-g/fe-func-g.ts deleted file mode 100644 index 48d80f414ca..00000000000 --- a/packages/x6-vector/src/vector/fe-func-g/fe-func-g.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEFuncGAttributes } from './types' - -@FEFuncG.register('FeFuncG') -export class FEFuncG extends FEBase {} - -export namespace FEFuncG { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEFuncG() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-func-g/types.ts b/packages/x6-vector/src/vector/fe-func-g/types.ts deleted file mode 100644 index 95227289d29..00000000000 --- a/packages/x6-vector/src/vector/fe-func-g/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { - SVGCommonAttributes, - SVGTransferFunctionAttributes, -} from '../types/attributes-core' - -export interface SVGFEFuncGAttributes - extends SVGCommonAttributes, - SVGTransferFunctionAttributes {} diff --git a/packages/x6-vector/src/vector/fe-func-r/fe-func-r.ts b/packages/x6-vector/src/vector/fe-func-r/fe-func-r.ts deleted file mode 100644 index fb57abc0065..00000000000 --- a/packages/x6-vector/src/vector/fe-func-r/fe-func-r.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEFuncRAttributes } from './types' - -@FEFuncR.register('FeFuncR') -export class FEFuncR extends FEBase {} - -export namespace FEFuncR { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEFuncR() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-func-r/types.ts b/packages/x6-vector/src/vector/fe-func-r/types.ts deleted file mode 100644 index e8fa6b35248..00000000000 --- a/packages/x6-vector/src/vector/fe-func-r/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { - SVGCommonAttributes, - SVGTransferFunctionAttributes, -} from '../types/attributes-core' - -export interface SVGFEFuncRAttributes - extends SVGCommonAttributes, - SVGTransferFunctionAttributes {} diff --git a/packages/x6-vector/src/vector/fe-gaussian-blur/exts.ts b/packages/x6-vector/src/vector/fe-gaussian-blur/exts.ts deleted file mode 100644 index f93f7fe17d9..00000000000 --- a/packages/x6-vector/src/vector/fe-gaussian-blur/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVGFEGaussianBlurAttributes } from './types' -import { FEGaussianBlur } from './fe-gaussian-blur' - -export class FilterExtension extends Base { - feGaussianBlur( - attrs?: Attributes | null, - ) { - return FEGaussianBlur.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-gaussian-blur/fe-gaussian-blur.test.ts b/packages/x6-vector/src/vector/fe-gaussian-blur/fe-gaussian-blur.test.ts deleted file mode 100644 index 5bbe97cfa3f..00000000000 --- a/packages/x6-vector/src/vector/fe-gaussian-blur/fe-gaussian-blur.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEGaussianBlur } from './fe-gaussian-blur' - -describe('FEGaussianBlur', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEGaussianBlur.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feGaussianBlur()).toBeInstanceOf(FEGaussianBlur) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feGaussianBlur({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FEGaussianBlur() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) - - describe('stdDeviation()', () => { - it('should set stdDeviation attribute', () => { - const fe = new FEGaussianBlur() - fe.stdDeviation(1) - expect(fe.stdDeviation()).toEqual(1) - expect(fe.attr('stdDeviation')).toEqual(1) - }) - }) - - describe('edgeMode()', () => { - it('should set edgeMode attribute', () => { - const fe = new FEGaussianBlur() - fe.edgeMode('duplicate') - expect(fe.edgeMode()).toEqual('duplicate') - expect(fe.attr('edgeMode')).toEqual('duplicate') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-gaussian-blur/fe-gaussian-blur.ts b/packages/x6-vector/src/vector/fe-gaussian-blur/fe-gaussian-blur.ts deleted file mode 100644 index 31141ee2d86..00000000000 --- a/packages/x6-vector/src/vector/fe-gaussian-blur/fe-gaussian-blur.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEGaussianBlurAttributes, In, EdgeMode } from './types' - -@FEGaussianBlur.register('FeGaussianBlur') -export class FEGaussianBlur extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - stdDeviation(): number - stdDeviation(v: number | null): this - stdDeviation(v?: number | null) { - return this.attr('stdDeviation', v) - } - - edgeMode(): EdgeMode - edgeMode(mode: EdgeMode | null): this - edgeMode(mode?: EdgeMode | null) { - return this.attr('edgeMode', mode) - } -} - -export namespace FEGaussianBlur { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEGaussianBlur() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-gaussian-blur/types.ts b/packages/x6-vector/src/vector/fe-gaussian-blur/types.ts deleted file mode 100644 index 43bda94ee7a..00000000000 --- a/packages/x6-vector/src/vector/fe-gaussian-blur/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' -import { In } from '../fe-blend/types' - -export { In } - -export type EdgeMode = 'duplicate' | 'wrap' | 'none' - -export interface SVGFEGaussianBlurAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string - stdDeviation?: number - edgeMode?: EdgeMode -} diff --git a/packages/x6-vector/src/vector/fe-image/exts.ts b/packages/x6-vector/src/vector/fe-image/exts.ts deleted file mode 100644 index 290f5e2cc05..00000000000 --- a/packages/x6-vector/src/vector/fe-image/exts.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Base } from '../common/base' -import { SVGFEImageAttributes } from './types' -import { FEImage } from './fe-image' - -export class FilterExtension extends Base { - feImage(attrs?: Attributes | null) { - return FEImage.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-image/fe-image.test.ts b/packages/x6-vector/src/vector/fe-image/fe-image.test.ts deleted file mode 100644 index de6b0ba1066..00000000000 --- a/packages/x6-vector/src/vector/fe-image/fe-image.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEImage } from './fe-image' - -describe('FEImage', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEImage.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feImage()).toBeInstanceOf(FEImage) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feImage({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('href()', () => { - it('should set `xlink:href` attribute', () => { - const fe = new FEImage() - const url = '/files/6457/mdn_logo_only_color.png' - fe.href(url) - expect(fe.href()).toEqual(url) - expect(fe.attr('xlink:href')).toEqual(url) - }) - }) - - describe('preserveAspectRatio()', () => { - it('should set preserveAspectRatio attribute', () => { - const fe = new FEImage() - fe.preserveAspectRatio('xMidYMid meet') - expect(fe.preserveAspectRatio()).toEqual('xMidYMid meet') - expect(fe.attr('preserveAspectRatio')).toEqual('xMidYMid meet') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-image/fe-image.ts b/packages/x6-vector/src/vector/fe-image/fe-image.ts deleted file mode 100644 index 10f8912dfc9..00000000000 --- a/packages/x6-vector/src/vector/fe-image/fe-image.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEImageAttributes } from './types' - -@FEImage.register('FeImage') -export class FEImage extends FEBase { - preserveAspectRatio(): string - preserveAspectRatio(v: string | null): this - preserveAspectRatio(v?: string | null) { - return this.attr('preserveAspectRatio', v) - } - - href(): string - href(url: string | null): this - href(url?: string | null) { - return this.attr('xlink:href', url) - } -} - -export namespace FEImage { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEImage() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-image/types.ts b/packages/x6-vector/src/vector/fe-image/types.ts deleted file mode 100644 index da5cc0c1e93..00000000000 --- a/packages/x6-vector/src/vector/fe-image/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGFEImageAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, - SVGConditionalProcessingAttributes { - preserveAspectRatio?: string -} diff --git a/packages/x6-vector/src/vector/fe-merge-node/fe-merge-node.test.ts b/packages/x6-vector/src/vector/fe-merge-node/fe-merge-node.test.ts deleted file mode 100644 index adf1137ef3d..00000000000 --- a/packages/x6-vector/src/vector/fe-merge-node/fe-merge-node.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { FEMergeNode } from './fe-merge-node' - -describe('FEMergeNode', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEMergeNode.create({ id: 'foo' }).id()).toBe('foo') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FEMergeNode() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-merge-node/fe-merge-node.ts b/packages/x6-vector/src/vector/fe-merge-node/fe-merge-node.ts deleted file mode 100644 index dfc3a499c39..00000000000 --- a/packages/x6-vector/src/vector/fe-merge-node/fe-merge-node.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEMergeNodeAttributes, In } from './types' - -@FEMergeNode.register('FeMergeNode') -export class FEMergeNode extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } -} - -export namespace FEMergeNode { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEMergeNode() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-merge-node/types.ts b/packages/x6-vector/src/vector/fe-merge-node/types.ts deleted file mode 100644 index 751b4c53745..00000000000 --- a/packages/x6-vector/src/vector/fe-merge-node/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { SVGCoreAttributes } from '../types/attributes-core' -import { In } from '../fe-blend/types' - -export { In } - -export interface SVGFEMergeNodeAttributes - extends SVGCoreAttributes { - in?: In | string -} diff --git a/packages/x6-vector/src/vector/fe-merge/exts.ts b/packages/x6-vector/src/vector/fe-merge/exts.ts deleted file mode 100644 index 7fb24327abf..00000000000 --- a/packages/x6-vector/src/vector/fe-merge/exts.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Base } from '../common/base' -import { SVGFEMergeAttributes } from './types' -import { FEMerge } from './fe-merge' - -export class FilterExtension extends Base { - feMerge(attrs?: Attributes | null) { - return FEMerge.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-merge/fe-merge.test.ts b/packages/x6-vector/src/vector/fe-merge/fe-merge.test.ts deleted file mode 100644 index 2eed39ab3c7..00000000000 --- a/packages/x6-vector/src/vector/fe-merge/fe-merge.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { FEMergeNode } from '../fe-merge-node/fe-merge-node' -import { Filter } from '../filter/filter' -import { FEMerge } from './fe-merge' - -describe('FEMerge', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEMerge.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feMerge()).toBeInstanceOf(FEMerge) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feMerge({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('feMergeNode()', () => { - it('should create an FeMergeNode', () => { - const fe = new FEMerge() - const node = fe.feMergeNode() - expect(node).toBeInstanceOf(FEMergeNode) - }) - - it('should create an FeMergeNode with given attributes', () => { - const fe = new FEMerge() - const node = fe.feMergeNode({ id: 'bar' }) - expect(node).toBeInstanceOf(FEMergeNode) - expect(node.id()).toEqual('bar') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-merge/fe-merge.ts b/packages/x6-vector/src/vector/fe-merge/fe-merge.ts deleted file mode 100644 index 2e3b877ee18..00000000000 --- a/packages/x6-vector/src/vector/fe-merge/fe-merge.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEMergeAttributes } from './types' -import { FEMergeNode } from '../fe-merge-node/fe-merge-node' -import { SVGFEMergeNodeAttributes } from '../fe-merge-node/types' - -@FEMerge.register('FeMerge') -export class FEMerge extends FEBase { - feMergeNode( - attrs?: Attributes | null, - ) { - return FEMergeNode.create(attrs).appendTo(this) - } -} - -export namespace FEMerge { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEMerge() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-merge/types.ts b/packages/x6-vector/src/vector/fe-merge/types.ts deleted file mode 100644 index a0b7fbb0cab..00000000000 --- a/packages/x6-vector/src/vector/fe-merge/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -export interface SVGFEMergeAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes {} diff --git a/packages/x6-vector/src/vector/fe-morphology/exts.ts b/packages/x6-vector/src/vector/fe-morphology/exts.ts deleted file mode 100644 index 7f08f3bea45..00000000000 --- a/packages/x6-vector/src/vector/fe-morphology/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVGFEMorphologyAttributes } from './types' -import { FEMorphology } from './fe-morphology' - -export class FilterExtension extends Base { - feMorphology( - attrs?: Attributes | null, - ) { - return FEMorphology.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-morphology/fe-morphology.test.ts b/packages/x6-vector/src/vector/fe-morphology/fe-morphology.test.ts deleted file mode 100644 index e26ea11ed36..00000000000 --- a/packages/x6-vector/src/vector/fe-morphology/fe-morphology.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEMorphology } from './fe-morphology' - -describe('FEMorphology', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEMorphology.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feMorphology()).toBeInstanceOf(FEMorphology) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feMorphology({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FEMorphology() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) - - describe('radius()', () => { - it('should set radius attribute', () => { - const fe = new FEMorphology() - fe.radius(1) - expect(fe.radius()).toEqual(1) - expect(fe.attr('radius')).toEqual(1) - }) - }) - - describe('edgeMode()', () => { - it('should set edgeMode attribute', () => { - const fe = new FEMorphology() - fe.operator('arithmetic') - expect(fe.operator()).toEqual('arithmetic') - expect(fe.attr('operator')).toEqual('arithmetic') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-morphology/fe-morphology.ts b/packages/x6-vector/src/vector/fe-morphology/fe-morphology.ts deleted file mode 100644 index beac47a15b7..00000000000 --- a/packages/x6-vector/src/vector/fe-morphology/fe-morphology.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEMorphologyAttributes, In, Operator } from './types' - -@FEMorphology.register('FeMorphology') -export class FEMorphology extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - radius(): number - radius(r: number | null): this - radius(r?: number | null) { - return this.attr('radius', r) - } - - operator(): Operator - operator(o: Operator | null): this - operator(o?: Operator | null) { - return this.attr('operator', o) - } -} - -export namespace FEMorphology { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEMorphology() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-morphology/types.ts b/packages/x6-vector/src/vector/fe-morphology/types.ts deleted file mode 100644 index dd6b5a1391f..00000000000 --- a/packages/x6-vector/src/vector/fe-morphology/types.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -import { In } from '../fe-blend/types' - -export { In } - -export type Operator = - | 'over' - | 'in' - | 'out' - | 'atop' - | 'xor' - | 'lighter' - | 'arithmetic' - -export interface SVGFEMorphologyAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string - radius?: number - operator?: Operator -} diff --git a/packages/x6-vector/src/vector/fe-offset/exts.ts b/packages/x6-vector/src/vector/fe-offset/exts.ts deleted file mode 100644 index b62302dbe27..00000000000 --- a/packages/x6-vector/src/vector/fe-offset/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVGFEOffsetAttributes } from './types' -import { FEOffset } from './fe-offset' - -export class FilterExtension extends Base { - feOffset( - attrs?: Attributes | null, - ) { - return FEOffset.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-offset/fe-offset.test.ts b/packages/x6-vector/src/vector/fe-offset/fe-offset.test.ts deleted file mode 100644 index 917dd83131a..00000000000 --- a/packages/x6-vector/src/vector/fe-offset/fe-offset.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Filter } from '../filter/filter' -import { FEOffset } from './fe-offset' - -describe('FEOffset', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FEOffset.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feOffset()).toBeInstanceOf(FEOffset) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feOffset({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FEOffset() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) - - describe('dx()', () => { - it('should set dx attribute', () => { - const fe = new FEOffset() - fe.dx(1) - expect(fe.dx()).toEqual(1) - expect(fe.attr('dx')).toEqual(1) - }) - }) - - describe('dy()', () => { - it('should set dy attribute', () => { - const fe = new FEOffset() - fe.dy(1) - expect(fe.dy()).toEqual(1) - expect(fe.attr('dy')).toEqual(1) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-offset/fe-offset.ts b/packages/x6-vector/src/vector/fe-offset/fe-offset.ts deleted file mode 100644 index 889e1bc1053..00000000000 --- a/packages/x6-vector/src/vector/fe-offset/fe-offset.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEOffsetAttributes, In } from './types' - -@FEOffset.register('FeOffset') -export class FEOffset extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - dx(): number - dx(dx: number | null): this - dx(dx?: number | null) { - return this.attr('dx', dx) - } - - dy(): number - dy(dy: number | null): this - dy(dy?: number | null) { - return this.attr('dy', dy) - } -} - -export namespace FEOffset { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEOffset() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-offset/types.ts b/packages/x6-vector/src/vector/fe-offset/types.ts deleted file mode 100644 index 6361badd1ff..00000000000 --- a/packages/x6-vector/src/vector/fe-offset/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -import { In } from '../fe-blend/types' - -export { In } - -export interface SVGFEOffsetAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string - dx?: number | string - dy?: number | string -} diff --git a/packages/x6-vector/src/vector/fe-point-light/fe-point-light.test.ts b/packages/x6-vector/src/vector/fe-point-light/fe-point-light.test.ts deleted file mode 100644 index 0faf6dfafc8..00000000000 --- a/packages/x6-vector/src/vector/fe-point-light/fe-point-light.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { FEPointLight } from './fe-point-light' - -describe('FEPointLight', () => { - describe('x()', () => { - it('should set x attribute', () => { - const fe = new FEPointLight() - fe.x(1) - expect(fe.x()).toEqual(1) - expect(fe.attr('x')).toEqual(1) - }) - }) - - describe('y()', () => { - it('should set y attribute', () => { - const fe = new FEPointLight() - fe.y(1) - expect(fe.y()).toEqual(1) - expect(fe.attr('y')).toEqual(1) - }) - }) - - describe('z()', () => { - it('should set z attribute', () => { - const fe = new FEPointLight() - fe.z(1) - expect(fe.z()).toEqual(1) - expect(fe.attr('z')).toEqual(1) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-point-light/fe-point-light.ts b/packages/x6-vector/src/vector/fe-point-light/fe-point-light.ts deleted file mode 100644 index 98a2036beaa..00000000000 --- a/packages/x6-vector/src/vector/fe-point-light/fe-point-light.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFEPointLightAttributes } from './types' - -@FEPointLight.register('FePointLight') -export class FEPointLight extends FEBase { - x(): number - x(x: number | null): this - x(x?: number | null) { - return this.attr('x', x) - } - - y(): number - y(y: number | null): this - y(y?: number | null) { - return this.attr('y', y) - } - - z(): number - z(z: number | null): this - z(z?: number | null) { - return this.attr('z', z) - } -} - -export namespace FEPointLight { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FEPointLight() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-point-light/types.ts b/packages/x6-vector/src/vector/fe-point-light/types.ts deleted file mode 100644 index d7a2f4cd04e..00000000000 --- a/packages/x6-vector/src/vector/fe-point-light/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SVGCoreAttributes } from '../types/attributes-core' - -export interface SVGFEPointLightAttributes - extends SVGCoreAttributes { - x?: string | number - y?: string | number - z?: string | number -} diff --git a/packages/x6-vector/src/vector/fe-specular-lighting/exts.ts b/packages/x6-vector/src/vector/fe-specular-lighting/exts.ts deleted file mode 100644 index e58a1d0e424..00000000000 --- a/packages/x6-vector/src/vector/fe-specular-lighting/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVGFESpecularLightingAttributes } from './types' -import { FESpecularLighting } from './fe-specular-lighting' - -export class FilterExtension extends Base { - feSpecularLighting( - attrs?: Attributes | null, - ) { - return FESpecularLighting.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-specular-lighting/fe-specular-lighting.test.ts b/packages/x6-vector/src/vector/fe-specular-lighting/fe-specular-lighting.test.ts deleted file mode 100644 index 4d1c3cb6a5d..00000000000 --- a/packages/x6-vector/src/vector/fe-specular-lighting/fe-specular-lighting.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { FEDistantLight } from '../fe-distant-light/fe-distant-light' -import { FEPointLight } from '../fe-point-light/fe-point-light' -import { FESpotLight } from '../fe-spot-light/fe-spot-light' -import { Filter } from '../filter/filter' -import { FESpecularLighting } from './fe-specular-lighting' - -describe('FESpecularLighting', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FESpecularLighting.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feSpecularLighting()).toBeInstanceOf(FESpecularLighting) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feSpecularLighting({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('feDistantLight()', () => { - it('should create an instance of FEDistantLight', () => { - const fe = new FESpecularLighting() - const light = fe.feDistantLight() - expect(light).toBeInstanceOf(FEDistantLight) - }) - - it('should create an instance of FEDistantLight with given attributes', () => { - const fe = new FESpecularLighting() - const light = fe.feDistantLight({ id: 'bar' }) - expect(light).toBeInstanceOf(FEDistantLight) - expect(light.id()).toEqual('bar') - }) - }) - - describe('fePointLight()', () => { - it('should create an instance of FEPointLight', () => { - const fe = new FESpecularLighting() - const light = fe.fePointLight() - expect(light).toBeInstanceOf(FEPointLight) - }) - - it('should create an instance of FEPointLight with given attributes', () => { - const fe = new FESpecularLighting() - const light = fe.fePointLight({ id: 'bar' }) - expect(light).toBeInstanceOf(FEPointLight) - expect(light.id()).toEqual('bar') - }) - }) - - describe('feSpotLight()', () => { - it('should create an instance of FESpotLight', () => { - const fe = new FESpecularLighting() - const light = fe.feSpotLight() - expect(light).toBeInstanceOf(FESpotLight) - }) - - it('should create an instance of FESpotLight with given attributes', () => { - const fe = new FESpecularLighting() - const light = fe.feSpotLight({ id: 'bar' }) - expect(light).toBeInstanceOf(FESpotLight) - expect(light.id()).toEqual('bar') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FESpecularLighting() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) - - describe('surfaceScale()', () => { - it('should set surfaceScale attribute', () => { - const fe = new FESpecularLighting() - fe.surfaceScale(1) - expect(fe.surfaceScale()).toEqual(1) - expect(fe.attr('surfaceScale')).toEqual(1) - }) - }) - - describe('specularConstant()', () => { - it('should set specularConstant attribute', () => { - const fe = new FESpecularLighting() - fe.specularConstant(1) - expect(fe.specularConstant()).toEqual(1) - expect(fe.attr('specularConstant')).toEqual(1) - }) - }) - - describe('specularExponent()', () => { - it('should set specularExponent attribute', () => { - const fe = new FESpecularLighting() - fe.specularExponent(1) - expect(fe.specularExponent()).toEqual(1) - expect(fe.attr('specularExponent')).toEqual(1) - }) - }) - - describe('kernelUnitLength()', () => { - it('should set kernelUnitLength attribute', () => { - const fe = new FESpecularLighting() - fe.kernelUnitLength(1) - expect(fe.kernelUnitLength()).toEqual(1) - expect(fe.attr('kernelUnitLength')).toEqual(1) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-specular-lighting/fe-specular-lighting.ts b/packages/x6-vector/src/vector/fe-specular-lighting/fe-specular-lighting.ts deleted file mode 100644 index d9de7e589b4..00000000000 --- a/packages/x6-vector/src/vector/fe-specular-lighting/fe-specular-lighting.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFESpecularLightingAttributes, In } from './types' -import { SVGFESpotLightAttributes } from '../fe-spot-light/types' -import { SVGFEPointLightAttributes } from '../fe-point-light/types' -import { SVGFEDistantLightAttributes } from '../fe-distant-light/types' -import { FESpotLight } from '../fe-spot-light/fe-spot-light' -import { FEPointLight } from '../fe-point-light/fe-point-light' -import { FEDistantLight } from '../fe-distant-light/fe-distant-light' - -@FESpecularLighting.register('FeSpecularLighting') -export class FESpecularLighting extends FEBase { - feDistantLight( - attrs?: Attributes | null, - ) { - return FEDistantLight.create(attrs).appendTo(this) - } - - fePointLight( - attrs?: Attributes | null, - ) { - return FEPointLight.create(attrs).appendTo(this) - } - - feSpotLight( - attrs?: Attributes | null, - ) { - return FESpotLight.create(attrs).appendTo(this) - } - - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } - - surfaceScale(): number - surfaceScale(s: number | null): this - surfaceScale(s?: number | null) { - return this.attr('surfaceScale', s) - } - - specularConstant(): number - specularConstant(v: number | null): this - specularConstant(v?: number | null) { - return this.attr('specularConstant', v) - } - - specularExponent(): number - specularExponent(v: number | null): this - specularExponent(v?: number | null) { - return this.attr('specularExponent', v) - } - - kernelUnitLength(): number - kernelUnitLength(v: number | null): this - kernelUnitLength(v?: number | null) { - return this.attr('kernelUnitLength', v) - } -} - -export namespace FESpecularLighting { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FESpecularLighting() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-specular-lighting/types.ts b/packages/x6-vector/src/vector/fe-specular-lighting/types.ts deleted file mode 100644 index 7bcad4a51cb..00000000000 --- a/packages/x6-vector/src/vector/fe-specular-lighting/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -import { In } from '../fe-blend/types' - -export { In } - -export interface SVGFESpecularLightingAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string - surfaceScale?: number - specularConstant?: number - specularExponent?: number - kernelUnitLength?: number -} diff --git a/packages/x6-vector/src/vector/fe-spot-light/fe-spot-light.test.ts b/packages/x6-vector/src/vector/fe-spot-light/fe-spot-light.test.ts deleted file mode 100644 index 85651c62729..00000000000 --- a/packages/x6-vector/src/vector/fe-spot-light/fe-spot-light.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { FESpotLight } from './fe-spot-light' - -describe('FESpotLight', () => { - describe('x()', () => { - it('should set x attribute', () => { - const fe = new FESpotLight() - fe.x(1) - expect(fe.x()).toEqual(1) - expect(fe.attr('x')).toEqual(1) - }) - }) - - describe('y()', () => { - it('should set y attribute', () => { - const fe = new FESpotLight() - fe.y(1) - expect(fe.y()).toEqual(1) - expect(fe.attr('y')).toEqual(1) - }) - }) - - describe('z()', () => { - it('should set z attribute', () => { - const fe = new FESpotLight() - fe.z(1) - expect(fe.z()).toEqual(1) - expect(fe.attr('z')).toEqual(1) - }) - }) - - describe('pointsAtX()', () => { - it('should set pointsAtX attribute', () => { - const fe = new FESpotLight() - fe.pointsAtX(1) - expect(fe.pointsAtX()).toEqual(1) - expect(fe.attr('pointsAtX')).toEqual(1) - }) - }) - - describe('pointsAtY()', () => { - it('should set pointsAtY attribute', () => { - const fe = new FESpotLight() - fe.pointsAtY(1) - expect(fe.pointsAtY()).toEqual(1) - expect(fe.attr('pointsAtY')).toEqual(1) - }) - }) - - describe('pointsAtZ()', () => { - it('should set pointsAtZ attribute', () => { - const fe = new FESpotLight() - fe.pointsAtZ(1) - expect(fe.pointsAtZ()).toEqual(1) - expect(fe.attr('pointsAtZ')).toEqual(1) - }) - }) - - describe('specularExponent()', () => { - it('should set specularExponent attribute', () => { - const fe = new FESpotLight() - fe.specularExponent(1) - expect(fe.specularExponent()).toEqual(1) - expect(fe.attr('specularExponent')).toEqual(1) - }) - }) - - describe('limitingConeAngle()', () => { - it('should set limitingConeAngle attribute', () => { - const fe = new FESpotLight() - fe.limitingConeAngle(1) - expect(fe.limitingConeAngle()).toEqual(1) - expect(fe.attr('limitingConeAngle')).toEqual(1) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-spot-light/fe-spot-light.ts b/packages/x6-vector/src/vector/fe-spot-light/fe-spot-light.ts deleted file mode 100644 index 95db2913c83..00000000000 --- a/packages/x6-vector/src/vector/fe-spot-light/fe-spot-light.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFESpotLightAttributes } from './types' - -@FESpotLight.register('FeSpotLight') -export class FESpotLight extends FEBase { - x(): number | string - x(x: number | string | null): this - x(x?: number | string | null) { - return this.attr('x', x) - } - - y(): number | string - y(y: number | string | null): this - y(y?: number | string | null) { - return this.attr('y', y) - } - - z(): number | string - z(z: number | string | null): this - z(z?: number | string | null) { - return this.attr('z', z) - } - - pointsAtX(): number - pointsAtX(x: number | null): this - pointsAtX(x?: number | null) { - return this.attr('pointsAtX', x) - } - - pointsAtY(): number - pointsAtY(y: number | null): this - pointsAtY(y?: number | null) { - return this.attr('pointsAtY', y) - } - - pointsAtZ(): number - pointsAtZ(z: number | null): this - pointsAtZ(z?: number | null) { - return this.attr('pointsAtZ', z) - } - - specularExponent(): number - specularExponent(v: number | null): this - specularExponent(v?: number | null) { - return this.attr('specularExponent', v) - } - - limitingConeAngle(): number - limitingConeAngle(v: number | null): this - limitingConeAngle(v?: number | null) { - return this.attr('limitingConeAngle', v) - } -} - -export namespace FESpotLight { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FESpotLight() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-spot-light/types.ts b/packages/x6-vector/src/vector/fe-spot-light/types.ts deleted file mode 100644 index 7510e8d8a2a..00000000000 --- a/packages/x6-vector/src/vector/fe-spot-light/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SVGCoreAttributes } from '../types/attributes-core' - -export interface SVGFESpotLightAttributes - extends SVGCoreAttributes { - x?: string | number - y?: string | number - z?: string | number - pointsAtX?: number - pointsAtY?: number - pointsAtZ?: number - specularExponent?: number - limitingConeAngle?: number -} diff --git a/packages/x6-vector/src/vector/fe-tile/exts.ts b/packages/x6-vector/src/vector/fe-tile/exts.ts deleted file mode 100644 index bd8f281366e..00000000000 --- a/packages/x6-vector/src/vector/fe-tile/exts.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Base } from '../common/base' -import { SVGFETileAttributes } from './types' -import { FETile } from './fe-tile' - -export class FilterExtension extends Base { - feTile(attrs?: Attributes | null) { - return FETile.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-tile/fe-tile.test.ts b/packages/x6-vector/src/vector/fe-tile/fe-tile.test.ts deleted file mode 100644 index 6f129b1c752..00000000000 --- a/packages/x6-vector/src/vector/fe-tile/fe-tile.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Filter } from '../filter/filter' -import { FETile } from './fe-tile' - -describe('FETile', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FETile.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feTile()).toBeInstanceOf(FETile) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feTile({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('in()', () => { - it('should set in attribute', () => { - const fe = new FETile() - fe.in('BackgroundAlpha') - expect(fe.in()).toEqual('BackgroundAlpha') - expect(fe.attr('in')).toEqual('BackgroundAlpha') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-tile/fe-tile.ts b/packages/x6-vector/src/vector/fe-tile/fe-tile.ts deleted file mode 100644 index df46c1c6802..00000000000 --- a/packages/x6-vector/src/vector/fe-tile/fe-tile.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFETileAttributes, In } from './types' - -@FETile.register('FeTile') -export class FETile extends FEBase { - in(): In | string - in(type: In): this - in(type: string): this - in(type: null): this - in(type?: In | string | null) { - return this.attr('in', type) - } -} - -export namespace FETile { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FETile() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-tile/types.ts b/packages/x6-vector/src/vector/fe-tile/types.ts deleted file mode 100644 index ac570fe2732..00000000000 --- a/packages/x6-vector/src/vector/fe-tile/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -import { In } from '../fe-blend/types' - -export { In } - -export interface SVGFETileAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - in?: In | string -} diff --git a/packages/x6-vector/src/vector/fe-turbulence/exts.ts b/packages/x6-vector/src/vector/fe-turbulence/exts.ts deleted file mode 100644 index b022a4c5261..00000000000 --- a/packages/x6-vector/src/vector/fe-turbulence/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVGFETurbulenceAttributes } from './types' -import { FETurbulence } from './fe-turbulence' - -export class FilterExtension extends Base { - feTurbulence( - attrs?: Attributes | null, - ) { - return FETurbulence.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/fe-turbulence/fe-turbulence.test.ts b/packages/x6-vector/src/vector/fe-turbulence/fe-turbulence.test.ts deleted file mode 100644 index 295d571fb45..00000000000 --- a/packages/x6-vector/src/vector/fe-turbulence/fe-turbulence.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Filter } from '../filter/filter' -import { FETurbulence } from './fe-turbulence' - -describe('FETurbulence', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(FETurbulence.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from filter', () => { - const filter = new Filter() - expect(filter.feTurbulence()).toBeInstanceOf(FETurbulence) - }) - - it('should create an instance from filter with given attributes', () => { - const filter = new Filter() - const fe = filter.feTurbulence({ id: 'foo' }) - expect(fe.id()).toEqual('foo') - }) - }) - - describe('baseFrequency()', () => { - it('should set baseFrequency attribute', () => { - const fe = new FETurbulence() - fe.baseFrequency(1) - expect(fe.baseFrequency()).toEqual(1) - expect(fe.attr('baseFrequency')).toEqual(1) - }) - }) - - describe('numOctaves()', () => { - it('should set numOctaves attribute', () => { - const fe = new FETurbulence() - fe.numOctaves(1) - expect(fe.numOctaves()).toEqual(1) - expect(fe.attr('numOctaves')).toEqual(1) - }) - }) - - describe('seed()', () => { - it('should set seed attribute', () => { - const fe = new FETurbulence() - fe.seed(1) - expect(fe.seed()).toEqual(1) - expect(fe.attr('seed')).toEqual(1) - }) - }) - - describe('feType()', () => { - it('should set type attribute', () => { - const fe = new FETurbulence() - fe.feType('rotate') - expect(fe.feType()).toEqual('rotate') - expect(fe.attr('type')).toEqual('rotate') - }) - }) - - describe('stitchTiles()', () => { - it('should set stitchTiles attribute', () => { - const fe = new FETurbulence() - fe.stitchTiles('noStitch') - expect(fe.stitchTiles()).toEqual('noStitch') - expect(fe.attr('stitchTiles')).toEqual('noStitch') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fe-turbulence/fe-turbulence.ts b/packages/x6-vector/src/vector/fe-turbulence/fe-turbulence.ts deleted file mode 100644 index f18dff34635..00000000000 --- a/packages/x6-vector/src/vector/fe-turbulence/fe-turbulence.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { FEBase } from '../fe-base/fe-base' -import { SVGFETurbulenceAttributes, Type, StitchTiles } from './types' - -@FETurbulence.register('FeTurbulence') -export class FETurbulence extends FEBase { - baseFrequency(): number - baseFrequency(v: number | null): this - baseFrequency(v?: number | null) { - return this.attr('baseFrequency', v) - } - - numOctaves(): number - numOctaves(v: number | null): this - numOctaves(v?: number | null) { - return this.attr('numOctaves', v) - } - - seed(): number - seed(v: number | null): this - seed(v?: number | null) { - return this.attr('seed', v) - } - - feType(): Type - feType(type: Type | null): this - feType(type?: Type | null) { - return this.attr('type', type) - } - - stitchTiles(): StitchTiles - stitchTiles(v: StitchTiles | null): this - stitchTiles(v?: StitchTiles | null) { - return this.attr('stitchTiles', v) - } -} - -export namespace FETurbulence { - export function create( - attrs?: Attributes | null, - ) { - const elem = new FETurbulence() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/fe-turbulence/types.ts b/packages/x6-vector/src/vector/fe-turbulence/types.ts deleted file mode 100644 index 97dcc8f9c24..00000000000 --- a/packages/x6-vector/src/vector/fe-turbulence/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes, -} from '../types/attributes-core' - -export type StitchTiles = 'noStitch' | 'stitch' - -export type Type = 'translate' | 'scale' | 'rotate' | 'skewX' | 'skewY' - -export interface SVGFETurbulenceAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGFilterPrimitiveAttributes { - baseFrequency?: number - numOctaves?: number - seed?: number - type?: Type - stitchTiles?: StitchTiles -} diff --git a/packages/x6-vector/src/vector/filter/exts.ts b/packages/x6-vector/src/vector/filter/exts.ts deleted file mode 100644 index 8c00ff32d2d..00000000000 --- a/packages/x6-vector/src/vector/filter/exts.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Decorator } from '../common/decorator' -import { Base } from '../common/base' -import { Filter } from './filter' -import { SVGFilterAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - @Decorator.checkDefs - filter(attrs?: Attributes | null) { - return this.defs()!.filter(attrs) - } -} - -export class DefsExtension< - TSVGElement extends SVGElement, -> extends Base { - filter(attrs?: Attributes | null) { - return Filter.create(attrs).appendTo(this) - } -} - -export class ElementExtension< - TSVGElement extends SVGElement, -> extends Base { - filterRef() { - return this.reference('filter') - } - - filterWith(filter?: Filter | null) { - if (filter) { - this.attr('filter', filter.url()) - } else { - this.unfilter() - } - - return this - } - - unfilter() { - return this.attr('filter', null) - } -} diff --git a/packages/x6-vector/src/vector/filter/filter.test.ts b/packages/x6-vector/src/vector/filter/filter.test.ts deleted file mode 100644 index 1f94432a896..00000000000 --- a/packages/x6-vector/src/vector/filter/filter.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { G } from '../g/g' -import { SVG } from '../svg/svg' -import { Filter } from './filter' - -describe('Filter', () => { - describe('constructor()', () => { - it('should create a new object of type Filter', () => { - const filter = new Filter() - expect(filter).toBeInstanceOf(Filter) - }) - - it('should create an instance with given attributes', () => { - expect(Filter.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from container', () => { - const svg = new SVG() - expect(svg.filter()).toBeInstanceOf(Filter) - }) - - it('should create an instance from container with given attributes', () => { - const svg = new SVG() - const filter = svg.filter({ id: 'foo' }) - expect(filter.id()).toEqual('foo') - }) - }) - - describe('units()', () => { - it('should set units attributes', () => { - const filter = new Filter() - filter.units('userSpaceOnUse') - expect(filter.units()).toEqual('userSpaceOnUse') - expect(filter.attr('filterUnits')).toEqual('userSpaceOnUse') - }) - }) - - describe('primitiveUnits()', () => { - it('should set primitiveUnits attributes', () => { - const filter = new Filter() - filter.primitiveUnits('userSpaceOnUse') - expect(filter.primitiveUnits()).toEqual('userSpaceOnUse') - expect(filter.attr('primitiveUnits')).toEqual('userSpaceOnUse') - }) - }) - - describe('update()', () => { - it('should clear the element', () => { - const filter = new Filter() - filter.append(new G()) - expect(filter.update().children()).toEqual([]) - }) - - it('should execute a function in the context of the filter', () => { - const filter = new Filter() - const g = new G() - filter.update((instance) => instance.append(g)) - expect(filter.children()).toEqual([g]) - }) - }) - - describe('targets()', () => { - it('should get all targets of this filter', () => { - const svg = new SVG().appendTo(document.body) - const filter = svg.filter() - const rect = svg.rect(100, 100).filterWith(filter) - expect(filter.targets()).toEqual([rect]) - expect(rect.filterRef()).toEqual(filter) - - rect.filterWith(null) - expect(filter.targets()).toEqual([]) - expect(rect.filterRef()).toBeNull() - - svg.remove() - }) - }) - - describe('remove()', () => { - it('should unfilter all targets', () => { - const svg = new SVG().appendTo(document.body) - const filter = svg.filter() - const rect = svg.rect(100, 100).filterWith(filter) - expect(filter.targets()).toEqual([rect]) - expect(filter.remove()).toBe(filter) - expect(rect.attr('filter')).toBeUndefined() - svg.remove() - }) - }) -}) diff --git a/packages/x6-vector/src/vector/filter/filter.ts b/packages/x6-vector/src/vector/filter/filter.ts deleted file mode 100644 index 567be29f341..00000000000 --- a/packages/x6-vector/src/vector/filter/filter.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Vector } from '../vector/vector' -import { Referent } from '../container/referent' -import { FilterUnits, PrimitiveUnits, SVGFilterAttributes } from './types' - -@Filter.register('Filter') -export class Filter extends Referent { - units(): FilterUnits - units(units: FilterUnits | null): this - units(units?: FilterUnits | null) { - return this.attr('filterUnits', units) - } - - primitiveUnits(): PrimitiveUnits - primitiveUnits(units: PrimitiveUnits | null): this - primitiveUnits(units?: PrimitiveUnits | null) { - return this.attr('primitiveUnits', units) - } - - update(handler?: Filter.Update | null) { - this.clear() - - if (typeof handler === 'function') { - handler.call(this, this) - } - - return this - } - - remove() { - this.targets().forEach((target) => target.unfilter()) - return super.remove() - } - - targets() { - return this.findTargets(`filter`) - } -} - -export namespace Filter { - export type Update = (this: Filter, filter: Filter) => void - - export function create( - attrs?: Attributes | null, - ) { - const filter = new Filter() - if (attrs) { - filter.attr(attrs) - } - return filter - } -} diff --git a/packages/x6-vector/src/vector/filter/mixins.ts b/packages/x6-vector/src/vector/filter/mixins.ts deleted file mode 100644 index 55583a04035..00000000000 --- a/packages/x6-vector/src/vector/filter/mixins.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { applyMixins } from '../../util/mixin' -import { FilterExtension as BlendExtension } from '../fe-blend/exts' -import { FilterExtension as ColorMatrixExtension } from '../fe-color-matrix/exts' -import { FilterExtension as ComponentTransferExtension } from '../fe-component-transfer/exts' -import { FilterExtension as CompositeExtension } from '../fe-composite/exts' -import { FilterExtension as ConvolveMatrixExtension } from '../fe-convolve-matrix/exts' -import { FilterExtension as DiffuseLightingExtension } from '../fe-diffuse-lighting/exts' -import { FilterExtension as DisplacementMapExtension } from '../fe-displacement-map/exts' -import { FilterExtension as FloodExtension } from '../fe-flood/exts' -import { FilterExtension as GaussianBlurExtension } from '../fe-gaussian-blur/exts' -import { FilterExtension as ImageExtension } from '../fe-image/exts' -import { FilterExtension as MergeExtension } from '../fe-merge/exts' -import { FilterExtension as MorphologyExtension } from '../fe-morphology/exts' -import { FilterExtension as OffsetExtension } from '../fe-offset/exts' -import { FilterExtension as SpecularLightingExtension } from '../fe-specular-lighting/exts' -import { FilterExtension as TileExtension } from '../fe-tile/exts' -import { FilterExtension as TurbulenceExtension } from '../fe-turbulence/exts' -import { Filter } from './filter' - -declare module './filter' { - interface Filter - extends BlendExtension, - ColorMatrixExtension, - ComponentTransferExtension, - CompositeExtension, - ConvolveMatrixExtension, - DiffuseLightingExtension, - DisplacementMapExtension, - FloodExtension, - GaussianBlurExtension, - ImageExtension, - MergeExtension, - MorphologyExtension, - OffsetExtension, - SpecularLightingExtension, - TileExtension, - TurbulenceExtension {} -} - -applyMixins( - Filter, - BlendExtension, - ColorMatrixExtension, - ComponentTransferExtension, - CompositeExtension, - ConvolveMatrixExtension, - DiffuseLightingExtension, - DisplacementMapExtension, - FloodExtension, - GaussianBlurExtension, - ImageExtension, - MergeExtension, - MorphologyExtension, - OffsetExtension, - SpecularLightingExtension, - TileExtension, - TurbulenceExtension, -) diff --git a/packages/x6-vector/src/vector/filter/types.ts b/packages/x6-vector/src/vector/filter/types.ts deleted file mode 100644 index 2ff8d86fc35..00000000000 --- a/packages/x6-vector/src/vector/filter/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export type FilterUnits = 'userSpaceOnUse' | 'objectBoundingBox' -export type PrimitiveUnits = 'userSpaceOnUse' | 'objectBoundingBox' - -export interface SVGFilterAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes { - x?: number | string - y?: number | string - width?: number | string - height?: number | string - filterRes?: string - filterUnits?: FilterUnits - primitiveUnits?: PrimitiveUnits -} diff --git a/packages/x6-vector/src/vector/foreignobject/exts.ts b/packages/x6-vector/src/vector/foreignobject/exts.ts deleted file mode 100644 index d35ee1e6a91..00000000000 --- a/packages/x6-vector/src/vector/foreignobject/exts.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Base } from '../common/base' -import { ForeignObject } from './foreignobject' -import { SVGForeignObjectAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - foreignObject( - attrs?: Attributes | null, - ): ForeignObject - foreignObject( - size: number | string, - attrs?: Attributes | null, - ): ForeignObject - foreignObject( - width: number | string, - height: number | string, - attrs?: Attributes | null, - ): ForeignObject - foreignObject( - width?: number | string | Attributes | null, - height?: number | string | Attributes | null, - attrs?: Attributes | null, - ) { - return ForeignObject.create(width, height, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/foreignobject/foreignobject.test.ts b/packages/x6-vector/src/vector/foreignobject/foreignobject.test.ts deleted file mode 100644 index e98ae64d8c3..00000000000 --- a/packages/x6-vector/src/vector/foreignobject/foreignobject.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { G } from '../g/g' -import { ForeignObject } from './foreignobject' - -describe('ForeignObject', () => { - describe('constructor()', () => { - it('should create a instance with empty args', () => { - expect(ForeignObject.create()).toBeInstanceOf(ForeignObject) - }) - - it('should create an sintance with passed attributes', () => { - expect(ForeignObject.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance with given width and height', () => { - const group = new G() - const ellipse = group.foreignObject(100, 200) - expect(ellipse.attr(['width', 'height'])).toEqual({ - width: 100, - height: 200, - }) - }) - - it('should create an instance with given width, height and attributes', () => { - const group = new G() - const ellipse = group.foreignObject(100, 200, { id: 'foo' }) - expect(ellipse.attr(['width', 'height'])).toEqual({ - width: 100, - height: 200, - }) - expect(ellipse).toBeInstanceOf(ForeignObject) - expect(ellipse.id()).toBe('foo') - }) - - it('should create an instance with given size', () => { - const group = new G() - const ellipse = group.foreignObject(100) - expect(ellipse.attr(['width', 'height'])).toEqual({ - width: 100, - height: 100, - }) - expect(ellipse).toBeInstanceOf(ForeignObject) - }) - - it('should create an instance with given size and attributes', () => { - const group = new G() - const ellipse = group.foreignObject(100, { id: 'foo' }) - expect(ellipse.attr(['width', 'height'])).toEqual({ - width: 100, - height: 100, - }) - expect(ellipse).toBeInstanceOf(ForeignObject) - expect(ellipse.id()).toBe('foo') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/foreignobject/foreignobject.ts b/packages/x6-vector/src/vector/foreignobject/foreignobject.ts deleted file mode 100644 index 15b26ea8972..00000000000 --- a/packages/x6-vector/src/vector/foreignobject/foreignobject.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Vector } from '../vector/vector' -import { SVGForeignObjectAttributes } from './types' - -@ForeignObject.register('ForeignObject') -export class ForeignObject extends Vector {} - -export namespace ForeignObject { - export function create( - attrs?: Attributes | null, - ): ForeignObject - export function create( - size: number | string, - attrs?: Attributes | null, - ): ForeignObject - export function create( - width: number | string, - height: number | string, - attrs?: Attributes | null, - ): ForeignObject - export function create( - width?: number | string | Attributes | null, - height?: number | string | Attributes | null, - attrs?: Attributes | null, - ): ForeignObject - export function create( - width?: number | string | Attributes | null, - height?: number | string | Attributes | null, - attrs?: Attributes | null, - ) { - const fo = new ForeignObject() - if (width == null) { - fo.size(0, 0) - } else if (typeof width === 'object') { - fo.size(0, 0).attr(width) - } else if (height != null && typeof height === 'object') { - fo.size(width, width).attr(height) - } else { - if (typeof height === 'undefined') { - fo.size(width, width) - } else { - fo.size(width, height) - } - - if (attrs) { - fo.attr(attrs) - } - } - - return fo - } -} diff --git a/packages/x6-vector/src/vector/foreignobject/types.ts b/packages/x6-vector/src/vector/foreignobject/types.ts deleted file mode 100644 index 6992b1dec9a..00000000000 --- a/packages/x6-vector/src/vector/foreignobject/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGForeignObjectAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - x?: number | string - y?: number | string - width?: number | string - height?: number | string -} diff --git a/packages/x6-vector/src/vector/fragment/fragment.test.ts b/packages/x6-vector/src/vector/fragment/fragment.test.ts deleted file mode 100644 index 50804c8b4c0..00000000000 --- a/packages/x6-vector/src/vector/fragment/fragment.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Global } from '../../global' -import { namespaces } from '../../util/dom' -import { G } from '..' -import { Fragment } from './fragment' - -describe('Fragment', () => { - describe('constructor()', () => { - it('should creates a new object of type Fragment', () => { - expect(new Fragment()).toBeInstanceOf(Fragment) - }) - - it('should use passed node instead of creating', () => { - const fragment = Global.document.createDocumentFragment() - expect(new Fragment(fragment).node).toBe(fragment as any) - }) - - it('should have all Container methods available', () => { - const frag = new Fragment() - const rect = frag.rect(100, 100) - expect(frag.children()).toEqual([rect]) - }) - }) - - describe('xml()', () => { - describe('setter', () => { - it('should call parent method with `outerXML` is `false`', () => { - const frag = new Fragment() - frag.xml('', false, namespaces.svg) - }) - }) - - describe('getter', () => { - it('should call parent method with `outerXML` is false', () => { - const frag = new Fragment() - const group = new G().appendTo(frag) - group.rect(123.456, 234.567) - - expect(frag.xml(false)).toBe( - '', - ) - }) - - it('should call parent method with default option', () => { - const frag = new Fragment() - const group = new G().appendTo(frag) - group.rect(123.456, 234.567) - - expect(frag.xml()).toBe( - '', - ) - }) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/fragment/fragment.ts b/packages/x6-vector/src/vector/fragment/fragment.ts deleted file mode 100644 index 66d46f00f49..00000000000 --- a/packages/x6-vector/src/vector/fragment/fragment.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Global } from '../../global' -import { Registry } from '../../dom/common/registry' -import { namespaces, createNode } from '../../util/dom' -import { Dom } from '../../dom/dom' - -export class Fragment extends Dom { - constructor(node = Global.document.createDocumentFragment()) { - super(node) - } - - xml(): string - xml(process: (dom: Dom) => boolean | undefined | Dom): string - xml(content: string, outerXML?: false, ns?: string): this - xml( - process: (dom: Dom) => boolean | undefined | Dom, - outerXML?: false, - ): string - xml(outerXML: false): string - xml( - arg1?: boolean | string | ((dom: Dom) => boolean | undefined | Dom), - arg2?: boolean, - arg3?: string, - ) { - const content = typeof arg1 === 'boolean' ? null : arg1 - const ns = arg3 - - // Put all elements into a wrapper before we can get the innerXML from it - if (content == null || typeof content === 'function') { - const wrapper = new Dom( - createNode('wrapper', ns || namespaces.html), - ) - wrapper.add(this.node.cloneNode(true)) - - return wrapper.xml(false) - } - - return super.xml(content, false, ns) - } -} - -Registry.register(Fragment, 'Fragment') diff --git a/packages/x6-vector/src/vector/fragment/mixins.ts b/packages/x6-vector/src/vector/fragment/mixins.ts deleted file mode 100644 index 19dfcab678f..00000000000 --- a/packages/x6-vector/src/vector/fragment/mixins.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { applyMixins } from '../../util/mixin' - -// containers -import { ContainerExtension as AExtension } from '../a/exts' -import { ContainerExtension as GExtension } from '../g/exts' -import { ContainerExtension as SvgExtension } from '../svg/exts' -import { ContainerExtension as MaskExtension } from '../mask/exts' -import { ContainerExtension as MarkerExtension } from '../marker/exts' -import { ContainerExtension as PatternExtension } from '../pattern/exts' -import { ContainerExtension as ClipPathExtension } from '../clippath/exts' -import { ContainerExtension as GradientExtension } from '../gradient/exts' -import { ContainerExtension as LinearGradientExtension } from '../gradient/linear-exts' -import { ContainerExtension as RadialGradientExtension } from '../gradient/radial-exts' -import { ContainerExtension as SymbolExtension } from '../symbol/exts' -// shapes -import { ContainerExtension as CircleExtension } from '../circle/exts' -import { ContainerExtension as EllipseExtension } from '../ellipse/ext' -import { ContainerExtension as ForeignObjectExtension } from '../foreignobject/exts' -import { ContainerExtension as ImageExtension } from '../image/exts' -import { ContainerExtension as LineExtension } from '../line/exts' -import { ContainerExtension as PathExtension } from '../path/exts' -import { ContainerExtension as PolygonExtension } from '../polygon/exts' -import { ContainerExtension as PolylineExtension } from '../polyline/exts' -import { ContainerExtension as RectExtension } from '../rect/exts' -import { ContainerExtension as TextExtension } from '../text/exts' -import { ContainerExtension as UseExtension } from '../use/exts' -import { ContainerExtension as TextPathExtension } from '../textpath/exts' - -import { Fragment } from './fragment' - -declare module './fragment' { - interface Fragment - extends AExtension, - GExtension, - SvgExtension, - MaskExtension, - MarkerExtension, - SymbolExtension, - PatternExtension, - ClipPathExtension, - GradientExtension, - LinearGradientExtension, - RadialGradientExtension, - // shapes - UseExtension, - RectExtension, - LineExtension, - TextExtension, - PathExtension, - ImageExtension, - CircleExtension, - EllipseExtension, - PolygonExtension, - PolylineExtension, - TextPathExtension, - ForeignObjectExtension {} -} - -applyMixins( - Fragment, - // containers - AExtension, - GExtension, - SvgExtension, - MaskExtension, - MarkerExtension, - SymbolExtension, - PatternExtension, - ClipPathExtension, - GradientExtension, - LinearGradientExtension, - RadialGradientExtension, - // shapes - UseExtension, - RectExtension, - LineExtension, - TextExtension, - PathExtension, - ImageExtension, - CircleExtension, - EllipseExtension, - PolygonExtension, - PolylineExtension, - TextPathExtension, - ForeignObjectExtension, -) diff --git a/packages/x6-vector/src/vector/g/exts.ts b/packages/x6-vector/src/vector/g/exts.ts deleted file mode 100644 index 18ce6cbec61..00000000000 --- a/packages/x6-vector/src/vector/g/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { G } from './g' -import { SVGGAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - group(attrs?: Attributes) { - return G.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/g/g.test.ts b/packages/x6-vector/src/vector/g/g.test.ts deleted file mode 100644 index 69a4d303420..00000000000 --- a/packages/x6-vector/src/vector/g/g.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { G } from './g' - -describe('G', () => { - describe('constructor()', () => { - it('should create an instance with empty args', () => { - expect(G.create()).toBeInstanceOf(G) - }) - - it('should create an sintance with passed attributes', () => { - expect(G.create({ id: 'foo' }).id()).toBe('foo') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/g/g.ts b/packages/x6-vector/src/vector/g/g.ts deleted file mode 100644 index 2c8cbea55ae..00000000000 --- a/packages/x6-vector/src/vector/g/g.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ContainerGeometry } from '../container/geometry' -import { SVGGAttributes } from './types' - -@G.register('G') -export class G extends ContainerGeometry {} - -export namespace G { - export function create( - attrs?: Attributes | null, - ) { - const g = new G() - if (attrs) { - g.attr(attrs) - } - return g - } -} diff --git a/packages/x6-vector/src/vector/g/types.ts b/packages/x6-vector/src/vector/g/types.ts deleted file mode 100644 index 0dffac08988..00000000000 --- a/packages/x6-vector/src/vector/g/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGGAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes {} diff --git a/packages/x6-vector/src/vector/gradient/exts.ts b/packages/x6-vector/src/vector/gradient/exts.ts deleted file mode 100644 index d43d3f42b23..00000000000 --- a/packages/x6-vector/src/vector/gradient/exts.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Base } from '../common/base' -import { Decorator } from '../common/decorator' -import { Gradient } from './gradient' -import { LinearGradient } from './linear' -import { RadialGradient } from './radial' -import { - SVGLinearGradientAttributes, - SVGRadialGradientAttributes, -} from './types' - -export interface SVGGradientAttributesMap { - linear: SVGLinearGradientAttributes - radial: SVGRadialGradientAttributes -} - -export interface SVGGradientElementMap { - linear: SVGLinearGradientElement - radial: SVGRadialGradientElement -} - -type GradientType = keyof SVGGradientAttributesMap - -type GradientMethod = { - gradient< - Type extends GradientType, - Attributes extends SVGGradientAttributesMap[Type], - >( - type: Type, - attrs?: Attributes | null, - ): SVGGradientElementMap[Type] - gradient< - Type extends GradientType, - Attributes extends SVGGradientAttributesMap[Type], - >( - type: Type, - update: Gradient.Update, - attrs?: Attributes | null, - ): SVGGradientElementMap[Type] - gradient< - Type extends GradientType, - Attributes extends SVGGradientAttributesMap[Type], - >( - type: Type, - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): SVGGradientElementMap[Type] -} - -export class ContainerExtension - extends Base - implements GradientMethod -{ - @Decorator.checkDefs - gradient< - Type extends GradientType, - Attributes extends SVGGradientAttributesMap[Type], - >( - type: Type, - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): SVGGradientElementMap[Type] { - return this.defs()!.gradient(type, update, attrs) - } -} - -export class DefsExtension - extends Base - implements GradientMethod -{ - gradient< - Type extends GradientType, - Attributes extends SVGGradientAttributesMap[Type], - >( - type: Type, - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): SVGGradientElementMap[Type] { - return type === 'linear' - ? (LinearGradient.create( - update as Gradient.Update, - attrs, - ).appendTo(this) as any) - : (RadialGradient.create( - update as Gradient.Update, - attrs, - ).appendTo(this) as any) - } -} diff --git a/packages/x6-vector/src/vector/gradient/gradient.ts b/packages/x6-vector/src/vector/gradient/gradient.ts deleted file mode 100644 index 36181dc1620..00000000000 --- a/packages/x6-vector/src/vector/gradient/gradient.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Box } from '../../struct/box' -import { UnitNumber } from '../../struct/unit-number' -import { Vector } from '../vector/vector' -import { Referent } from '../container/referent' -import { Stop } from './stop' - -export abstract class Gradient< - TSVGGradientElement extends SVGGradientElement, -> extends Referent { - abstract from(x: number | string, y: number | string): this - - abstract to(x: number | string, y: number | string): this - - stop( - offset?: number | string | UnitNumber, - color?: string, - opacity?: number | string | UnitNumber, - ): Stop - stop(options: Stop.Options): Stop - stop( - offset?: Stop.Options | number | string | UnitNumber, - color?: string, - opacity?: number | string | UnitNumber, - ) { - return new Stop().update(offset, color, opacity).appendTo(this) - } - - bbox() { - return new Box() - } - - update(handler?: Gradient.Update | null) { - this.clear() - - if (typeof handler === 'function') { - handler.call(this, this) - } - - return this - } - - remove() { - this.targets().forEach((target) => target.attr('fill', null)) - return super.remove() - } - - targets(): TVector[] { - return this.findTargets('fill') - } -} - -export namespace Gradient { - export type Update = ( - this: Gradient, - gradient: Gradient, - ) => void -} diff --git a/packages/x6-vector/src/vector/gradient/linear-exts.ts b/packages/x6-vector/src/vector/gradient/linear-exts.ts deleted file mode 100644 index f2b8358f2df..00000000000 --- a/packages/x6-vector/src/vector/gradient/linear-exts.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Base } from '../common/base' -import { Decorator } from '../common/decorator' -import { Gradient } from './gradient' -import { LinearGradient } from './linear' -import { SVGLinearGradientAttributes } from './types' - -type GradientMethod = { - linearGradient( - attrs?: Attributes | null, - ): LinearGradient - linearGradient( - update: Gradient.Update, - attrs?: Attributes | null, - ): LinearGradient - linearGradient( - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): LinearGradient -} - -export class ContainerExtension - extends Base - implements GradientMethod -{ - @Decorator.checkDefs - linearGradient( - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): LinearGradient { - return this.defs()!.linearGradient(update, attrs) - } -} - -export class DefsExtension - extends Base - implements GradientMethod -{ - linearGradient( - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): LinearGradient { - return LinearGradient.create(update, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/gradient/linear.test.ts b/packages/x6-vector/src/vector/gradient/linear.test.ts deleted file mode 100644 index 2a062bbdc9e..00000000000 --- a/packages/x6-vector/src/vector/gradient/linear.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { G } from '../g/g' -import { SVG } from '../svg/svg' -import { defaultAttributes } from '../vector/overrides' -import { LinearGradient } from './linear' - -describe('LinearGradient', () => { - describe('constructor()', () => { - it('should create a new object of type LinearGradient', () => { - const gradient = new LinearGradient() - expect(gradient).toBeInstanceOf(LinearGradient) - expect(gradient.type).toBe('linearGradient') - }) - - it('should create an instance with given attributes', () => { - expect(LinearGradient.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from container', () => { - const svg = new SVG() - expect(svg.linearGradient()).toBeInstanceOf(LinearGradient) - expect(svg.gradient('linear')).toBeInstanceOf(LinearGradient) - }) - - it('should create an instance from container with given attributes', () => { - const svg = new SVG() - const gradient = svg.linearGradient({ id: 'foo' }) - expect(gradient.id()).toEqual('foo') - }) - - it('should create an instance from container with given update function and attributes', () => { - const svg = new SVG() - - const gradient1 = svg.linearGradient((instance) => instance.stop(0.1), { - id: 'foo', - }) - expect(gradient1.id()).toEqual('foo') - expect(gradient1.children().length).toEqual(1) - - const gradient2 = svg.linearGradient(null, { id: 'foo' }) - expect(gradient2.id()).toEqual('foo') - expect(gradient2.children().length).toEqual(0) - }) - - it('should throw an error when container do not in svg context', () => { - const g = new G() - expect(() => g.linearGradient()).toThrowError() - }) - }) - - describe('from()', () => { - it('should set x1, y1 attributes', () => { - const gradient = new LinearGradient() - gradient.from(0, 1) - expect(gradient.attr('x1')).toEqual(0) - expect(gradient.attr('y1')).toEqual(1) - }) - }) - - describe('to()', () => { - it('should set x2, y2 attributes', () => { - const gradient = new LinearGradient() - gradient.to(2, 3) - expect(gradient.attr('x2')).toEqual(2) - expect(gradient.attr('y2')).toEqual(3) - }) - }) - - describe('attr()', () => { - it('should relay to parents attr method for any call except transformation', () => { - const gradient = new LinearGradient() - gradient.attr('tabIndex', 1) - gradient.attr('transform', 'foo') - - expect(gradient.attr('tabIndex')).toEqual(1) - expect(gradient.attr('gradientTransform')).toEqual('foo') - }) - }) - - describe('bbox()', () => { - it('should return an empty box', () => { - const bbox = new LinearGradient().bbox() - expect(bbox.x).toEqual(0) - expect(bbox.y).toEqual(0) - expect(bbox.w).toEqual(0) - expect(bbox.h).toEqual(0) - }) - }) - - describe('targets()', () => { - it('should get all targets of this pattern', () => { - const svg = new SVG().appendTo(document.body) - const gradient = svg.linearGradient() - const rect = svg.rect(100, 100).fill(gradient) - expect(gradient.targets()).toEqual([rect]) - svg.remove() - }) - }) - - describe('remove()', () => { - it('should ungradient all targets', () => { - const svg = new SVG().appendTo(document.body) - const gradient = svg.linearGradient() - const rect = svg.rect(100, 100).fill(gradient) - expect(gradient.targets()).toEqual([rect]) - expect(gradient.remove()).toBe(gradient) - expect(rect.attr('fill')).toBe(defaultAttributes.fill) - svg.remove() - }) - }) - - describe('update()', () => { - it('should clear the element', () => { - const gradient = new LinearGradient() - gradient.stop(0.1, '#fff', 0.5) - expect(gradient.update().children()).toEqual([]) - }) - - it('should execute a function in the context of the gradient', () => { - const gradient = new LinearGradient() - gradient.update((instance) => - instance.stop({ offset: 10, color: 'red', opacity: 0.1 }), - ) - expect(gradient.update().children()).toEqual([]) - }) - }) - - describe('url()', () => { - it('should return "url(#id)"', () => { - const gradient = new LinearGradient().id('foo') - expect(gradient.url()).toBe('url(#foo)') - }) - }) - - describe('toString()', () => { - it('should call `url()` and returns the result', () => { - const gradient = new LinearGradient() - expect(gradient.toString()).toBe(gradient.url()) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/gradient/linear.ts b/packages/x6-vector/src/vector/gradient/linear.ts deleted file mode 100644 index 351fac832df..00000000000 --- a/packages/x6-vector/src/vector/gradient/linear.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { UnitNumber } from '../../struct/unit-number' -import { AttrOverride } from './override' -import { SVGLinearGradientAttributes } from './types' -import { Gradient } from './gradient' - -@LinearGradient.register('LinearGradient') -@LinearGradient.mixin(AttrOverride) -export class LinearGradient extends Gradient { - from(x: number | string, y: number | string) { - return this.attr({ - x1: UnitNumber.create(x).toString(), - y1: UnitNumber.create(y).toString(), - }) - } - - to(x: number | string, y: number | string) { - return this.attr({ - x2: UnitNumber.create(x).toString(), - y2: UnitNumber.create(y).toString(), - }) - } -} - -export namespace LinearGradient { - export function create( - attrs?: Attributes | null, - ): LinearGradient - export function create( - update: Gradient.Update, - attrs?: Attributes | null, - ): LinearGradient - export function create( - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): LinearGradient - export function create( - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ) { - const gradient = new LinearGradient() - if (update) { - if (typeof update === 'function') { - gradient.update(update) - if (attrs) { - gradient.attr(attrs) - } - } else { - gradient.attr(update) - } - } else if (attrs) { - gradient.attr(attrs) - } - return gradient - } -} diff --git a/packages/x6-vector/src/vector/gradient/override.ts b/packages/x6-vector/src/vector/gradient/override.ts deleted file mode 100644 index ae035413778..00000000000 --- a/packages/x6-vector/src/vector/gradient/override.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AttributesBase } from '../../dom/attributes' - -export class AttrOverride extends AttributesBase { - attr(attr: any, value: any) { - return super.attr(attr === 'transform' ? 'gradientTransform' : attr, value) - } -} diff --git a/packages/x6-vector/src/vector/gradient/radial-exts.ts b/packages/x6-vector/src/vector/gradient/radial-exts.ts deleted file mode 100644 index 3af5ec09b1b..00000000000 --- a/packages/x6-vector/src/vector/gradient/radial-exts.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Base } from '../common/base' -import { Decorator } from '../common/decorator' -import { Gradient } from './gradient' -import { RadialGradient } from './radial' -import { SVGRadialGradientAttributes } from './types' - -type GradientMethod = { - radialGradient( - attrs?: Attributes | null, - ): RadialGradient - radialGradient( - block: Gradient.Update, - attrs?: Attributes | null, - ): RadialGradient - radialGradient( - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): RadialGradient -} - -export class ContainerExtension - extends Base - implements GradientMethod -{ - @Decorator.checkDefs - radialGradient( - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): RadialGradient { - return this.defs()!.radialGradient(update, attrs) - } -} - -export class DefsExtension - extends Base - implements GradientMethod -{ - radialGradient( - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): RadialGradient { - return RadialGradient.create(update, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/gradient/radial.test.ts b/packages/x6-vector/src/vector/gradient/radial.test.ts deleted file mode 100644 index 5bbcfd7172e..00000000000 --- a/packages/x6-vector/src/vector/gradient/radial.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { G } from '../g/g' -import { SVG } from '../svg/svg' -import { RadialGradient } from './radial' - -describe('RadialGradient', () => { - describe('constructor()', () => { - it('should create a new object of type RadialGradient', () => { - const gradient = new RadialGradient() - expect(gradient).toBeInstanceOf(RadialGradient) - expect(gradient.type).toBe('radialGradient') - }) - - it('should create an instance with given attributes', () => { - expect(RadialGradient.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from container', () => { - const svg = new SVG() - expect(svg.radialGradient()).toBeInstanceOf(RadialGradient) - expect(svg.gradient('radial')).toBeInstanceOf(RadialGradient) - }) - - it('should create an instance from container with given attributes', () => { - const svg = new SVG() - const gradient = svg.radialGradient({ id: 'foo' }) - expect(gradient.id()).toEqual('foo') - }) - - it('should create an instance from container with given update function and attributes', () => { - const svg = new SVG() - - const gradient1 = svg.radialGradient((instance) => instance.stop(0.1), { - id: 'foo', - }) - expect(gradient1.id()).toEqual('foo') - expect(gradient1.children().length).toEqual(1) - - const gradient2 = svg.radialGradient(null, { id: 'foo' }) - expect(gradient2.id()).toEqual('foo') - expect(gradient2.children().length).toEqual(0) - }) - - it('should throw an error when container do not in svg context', () => { - const g = new G() - expect(() => g.radialGradient()).toThrowError() - }) - }) - - describe('from()', () => { - it('should set fx, fy attributes', () => { - const gradient = new RadialGradient() - gradient.from(0, 1) - expect(gradient.attr('fx')).toEqual(0) - expect(gradient.attr('fy')).toEqual(1) - }) - }) - - describe('to()', () => { - it('should set cx, cy attributes', () => { - const gradient = new RadialGradient() - gradient.to(2, 3) - expect(gradient.attr('cx')).toEqual(2) - expect(gradient.attr('cy')).toEqual(3) - }) - }) - - describe('attr()', () => { - it('should relay to parents attr method for any call except transformation', () => { - const gradient = new RadialGradient() - gradient.attr('tabIndex', 1) - gradient.attr('transform', 'foo') - - expect(gradient.attr('tabIndex')).toEqual(1) - expect(gradient.attr('gradientTransform')).toEqual('foo') - }) - }) - - describe('bbox()', () => { - it('should return an empty box', () => { - const bbox = new RadialGradient().bbox() - expect(bbox.x).toEqual(0) - expect(bbox.y).toEqual(0) - expect(bbox.w).toEqual(0) - expect(bbox.h).toEqual(0) - }) - }) - - describe('targets()', () => { - it('should get all targets of this pattern', () => { - const svg = new SVG().appendTo(document.body) - const gradient = svg.radialGradient() - const rect = svg.rect(100, 100).fill(gradient) - expect(gradient.targets()).toEqual([rect]) - svg.remove() - }) - }) - - describe('update()', () => { - it('should clear the element', () => { - const gradient = new RadialGradient() - gradient.stop(0.1, '#fff') - expect(gradient.update().children()).toEqual([]) - }) - - it('should execute a function in the context of the gradient', () => { - const gradient = new RadialGradient() - gradient.update((instance) => instance.stop(0.1, '#fff')) - expect(gradient.update().children()).toEqual([]) - }) - }) - - describe('url()', () => { - it('should return "url(#id)"', () => { - const gradient = new RadialGradient().id('foo') - expect(gradient.url()).toBe('url(#foo)') - }) - }) - - describe('toString()', () => { - it('should call `url()` and returns the result', () => { - const gradient = new RadialGradient() - expect(gradient.toString()).toBe(gradient.url()) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/gradient/radial.ts b/packages/x6-vector/src/vector/gradient/radial.ts deleted file mode 100644 index bf81e729dfd..00000000000 --- a/packages/x6-vector/src/vector/gradient/radial.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { UnitNumber } from '../../struct/unit-number' -import { AttrOverride } from './override' -import { SVGRadialGradientAttributes } from './types' -import { Gradient } from './gradient' - -@RadialGradient.register('RadialGradient') -@RadialGradient.mixin(AttrOverride) -export class RadialGradient extends Gradient { - from(x: number | string, y: number | string) { - return this.attr({ - fx: UnitNumber.create(x).toString(), - fy: UnitNumber.create(y).toString(), - }) - } - - to(cx: number | string, cy: number | string) { - return this.attr({ - cx: UnitNumber.create(cx).toString(), - cy: UnitNumber.create(cy).toString(), - }) - } -} - -export namespace RadialGradient { - export function create( - attrs?: Attributes | null, - ): RadialGradient - export function create( - update: Gradient.Update, - attrs?: Attributes | null, - ): RadialGradient - export function create( - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ): RadialGradient - export function create( - update?: Gradient.Update | Attributes | null, - attrs?: Attributes | null, - ) { - const gradient = new RadialGradient() - if (update) { - if (typeof update === 'function') { - gradient.update(update) - if (attrs) { - gradient.attr(attrs) - } - } else { - gradient.attr(update) - } - } else if (attrs) { - gradient.attr(attrs) - } - return gradient - } -} diff --git a/packages/x6-vector/src/vector/gradient/stop.ts b/packages/x6-vector/src/vector/gradient/stop.ts deleted file mode 100644 index aa63158d014..00000000000 --- a/packages/x6-vector/src/vector/gradient/stop.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { UnitNumber } from '../../struct/unit-number' -import { Base } from '../common/base' - -@Stop.register('Stop') -export class Stop extends Base { - update( - offset?: number | string | UnitNumber, - color?: string, - opacity?: number | string | UnitNumber, - ): this - update(options: Stop.Options): this - update( - offset?: Stop.Options | number | string | UnitNumber, - color?: string, - opacity?: number | string | UnitNumber, - ): this - update( - offset?: Stop.Options | number | string | UnitNumber, - color?: string, - opacity?: number | string | UnitNumber, - ) { - const options: { - offset?: number - color?: string - opacity?: number - } = {} - - if ( - offset == null || - typeof offset === 'number' || - typeof offset === 'string' || - offset instanceof UnitNumber - ) { - if (offset != null) { - options.offset = UnitNumber.create(offset).value - } - if (color != null) { - options.color = color - } - if (opacity != null) { - options.opacity = UnitNumber.create(opacity).value - } - } else { - if (offset.offset != null) { - options.offset = UnitNumber.create(offset.offset).value - } - if (offset.color != null) { - options.color = offset.color - } - if (offset.opacity != null) { - options.opacity = UnitNumber.create(offset.opacity).value - } - } - - if (options.opacity != null) { - this.attr('stop-opacity', options.opacity) - } - if (options.color != null) { - this.attr('stop-color', options.color) - } - if (options.offset != null) { - this.attr('offset', options.offset) - } - - return this - } -} - -export namespace Stop { - export interface Options { - offset?: number | string | UnitNumber - color?: string - opacity?: number | string | UnitNumber - } -} diff --git a/packages/x6-vector/src/vector/gradient/types.ts b/packages/x6-vector/src/vector/gradient/types.ts deleted file mode 100644 index d30e24aed10..00000000000 --- a/packages/x6-vector/src/vector/gradient/types.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGXLinkAttributes, -} from '../types/attributes-core' - -export interface SVGStopAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes { - offset?: string | number - stopColor?: string - stopOpacity?: number -} - -export interface SVGRadialGradientAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGXLinkAttributes { - cx?: string | number - cy?: string | number - fr?: string | number - fx?: string | number - fy?: string | number - r?: string | number - href?: string - gradientTransform?: string - spreadMethod?: 'pad' | 'reflect' | 'repeat' - gradientUnits?: 'userSpaceOnUse' | 'objectBoundingBox' -} - -export interface SVGLinearGradientAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGXLinkAttributes { - x1?: string | number - y1?: string | number - x2?: string | number - y2?: string | number - href?: string - gradientTransform?: string - spreadMethod?: 'pad' | 'reflect' | 'repeat' - gradientUnits?: 'userSpaceOnUse' | 'objectBoundingBox' -} diff --git a/packages/x6-vector/src/vector/image/exts.ts b/packages/x6-vector/src/vector/image/exts.ts deleted file mode 100644 index 0f7be71c06e..00000000000 --- a/packages/x6-vector/src/vector/image/exts.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Base } from '../common/base' -import { Image } from './image' -import { SVGImageAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - image(attrs?: Attributes | null): Image - image( - url?: string, - attrs?: Attributes | null, - ): Image - image( - url?: string, - callback?: Image.Callback, - attrs?: Attributes | null, - ): Image - image( - url?: string | Attributes | null, - callback?: Image.Callback | Attributes | null, - attrs?: Attributes | null, - ) { - return Image.create(url, callback, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/image/image.test.ts b/packages/x6-vector/src/vector/image/image.test.ts deleted file mode 100644 index 379ba6b10fd..00000000000 --- a/packages/x6-vector/src/vector/image/image.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Pattern } from '../pattern/pattern' -import { SVG } from '../svg/svg' -import { Image } from './image' - -describe('Image', () => { - const url = - 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*bSBhSbhNr2QAAAAAAAAAAAAAARQnAQ' - - describe('constructor()', () => { - it('should create an instance of Image', () => { - expect(new Image()).toBeInstanceOf(Image) - }) - - it('should create an instance with given attributes', () => { - expect(Image.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance with given url and attributes', () => { - const image = Image.create(url, { - id: 'foo', - }) - expect(image.id()).toBe('foo') - expect(image.attr('href')).toEqual(url) - }) - - it('should create an instance with given url, callback and attributes', (done) => { - const image = Image.create( - url, - function () { - expect(this.width()).toEqual(120) - expect(this.height()).toEqual(80) - done() - }, - { - id: 'foo', - }, - ) - expect(image.id()).toBe('foo') - expect(image.attr('href')).toEqual(url) - }) - - it('should create an instance in the container', () => { - const svg = new SVG() - const image = svg.image() - expect(image).toBeInstanceOf(Image) - }) - }) - - describe('load()', () => { - it('should no nothing when url is falsy and returns itself', () => { - const image = new Image() - expect(image.load()).toBe(image) - }) - - it('should execute a callback when the image is loaded', (done) => { - new Image().load(url, (e: any) => { - expect(e.target.complete).toBe(true) - done() - }) - }) - - it('should set width and height automatically if no size is given', (done) => { - const image = new Image().load(url, () => { - expect(image.attr('width')).toBe(120) - expect(image.attr('height')).toBe(80) - done() - }) - }) - - it('should not change with and height when size already set', (done) => { - const image = new Image() - .load(url, () => { - expect(image.attr('width')).toBe(100) - expect(image.attr('height')).toBe(100) - done() - }) - .size(100, 100) - }) - - it('should change the size of pattern to image size if parent is pattern and size is 0', (done) => { - const pattern = new Pattern().size(0, 0) - new Image() - .load(url, () => { - expect(pattern.attr('width')).toBe(100) - expect(pattern.attr('height')).toBe(100) - done() - }) - .size(100, 100) - .appendTo(pattern) - }) - - it('should not change the size of pattern if pattern has a size set', (done) => { - const pattern = new Pattern().size(50, 50) - new Image().load(url, () => { - expect(pattern.attr('width')).toBe(50) - expect(pattern.attr('height')).toBe(50) - done() - }) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/image/image.ts b/packages/x6-vector/src/vector/image/image.ts deleted file mode 100644 index 9d6aa8ba1a7..00000000000 --- a/packages/x6-vector/src/vector/image/image.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Global } from '../../global' -import { Shape } from '../common/shape' -import { Vector } from '../vector/vector' -import { Pattern } from '../pattern/pattern' -import { SVGImageAttributes } from './types' - -@Image.register('Image') -export class Image extends Shape { - load(url?: string, callback?: Image.Callback | null) { - if (!url) { - return this - } - - const img = new Global.window.Image() - - img.addEventListener('load', (e) => { - // ensure image size - if (this.width() === 0 && this.height() === 0) { - this.size(img.width, img.height) - } - - const p = this.parent(Pattern) - if ( - p instanceof Pattern && // ensure pattern size if not set - p.width() === 0 && - p.height() === 0 - ) { - p.size(this.width(), this.height()) - } - - if (typeof callback === 'function') { - callback.call(this, e) - } - }) - - img.src = url - - return this.attr('href', url) - } -} - -export namespace Image { - export function create( - attrs?: Attributes | null, - ): Image - export function create( - url?: string, - attrs?: Attributes | null, - ): Image - export function create( - url?: string, - callback?: Image.Callback, - attrs?: Attributes | null, - ): Image - export function create( - url?: string | Attributes | null, - callback?: Image.Callback | Attributes | null, - attrs?: Attributes | null, - ): Image - export function create( - url?: string | Attributes | null, - callback?: Image.Callback | Attributes | null, - attrs?: Attributes | null, - ) { - const image = new Image().size(0, 0) - if (url) { - if (typeof url === 'string') { - if (typeof callback === 'function' || callback == null) { - if (attrs) { - image.attr(attrs) - } - image.load(url, callback) - } else { - image.attr(callback).load(url) - } - } else { - image.attr(url) - } - } - return image - } -} - -export namespace Image { - export type Callback = (this: Image, e: Event) => void - - const attributeNames = ['fill', 'stroke'] - attributeNames.forEach((attr) => - Image.registerAttributeHook(attr, { - set(node, url: Image | string) { - const elem = Image.adopt(node) - if (elem && elem.root != null) { - const root = elem.root() - const defs = root && root.defs() - if (defs) { - let image: Image | null = null - - if (typeof url === 'string') { - const isImage = /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i - if (isImage.test(url)) { - image = defs.image(url) - } - } else { - image = url - } - - if (image != null) { - return defs.pattern(0, 0, (pattern) => pattern.add(image!)).url() - } - } - } - - return url - }, - }), - ) -} diff --git a/packages/x6-vector/src/vector/image/types.ts b/packages/x6-vector/src/vector/image/types.ts deleted file mode 100644 index 93469575a39..00000000000 --- a/packages/x6-vector/src/vector/image/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGImageAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes { - x?: number | string - y?: number | string - width?: number | string - height?: number | string - href?: string - preserveAspectRatio?: string - crossorigin?: string -} diff --git a/packages/x6-vector/src/vector/index.ts b/packages/x6-vector/src/vector/index.ts deleted file mode 100644 index ee580f4cd02..00000000000 --- a/packages/x6-vector/src/vector/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import './vector/mixins' - -import './container/container-mixins' -import './common/shape-mixins' -import './fragment/mixins' -import './defs/mixins' -import './line/mixins' -import './path/mixins' -import './poly/mixins' -import './text/mixins' -import './filter/mixins' - -export * from './fragment/fragment' -export * from './vector/vector' - -export * from './a/a' -export * from './animate/animate' -export * from './animate-motion/animate-motion' -export * from './animate-transform/animate-transform' -export * from './circle/circle' -export * from './clippath/clippath' -export * from './defs/defs' -export * from './desc/desc' -export * from './ellipse/ellipse' - -export * from './fe-blend/fe-blend' -export * from './fe-color-matrix/fe-color-matrix' -export * from './fe-component-transfer/fe-component-transfer' -export * from './fe-composite/fe-composite' -export * from './fe-convolve-matrix/fe-convolve-matrix' -export * from './fe-diffuse-lighting/fe-diffuse-lighting' -export * from './fe-displacement-map/fe-displacement-map' -export * from './fe-flood/fe-flood' -export * from './fe-gaussian-blur/fe-gaussian-blur' -export * from './fe-image/fe-image' -export * from './fe-merge/fe-merge' -export * from './fe-morphology/fe-morphology' -export * from './fe-offset/fe-offset' -export * from './fe-specular-lighting/fe-specular-lighting' -export * from './fe-tile/fe-tile' -export * from './fe-turbulence/fe-turbulence' - -export * from './foreignobject/foreignobject' -export * from './g/g' -export * from './gradient/linear' -export * from './gradient/radial' -export * from './image/image' -export * from './line/line' -export * from './marker/marker' -export * from './mask/mask' -export * from './path/path' -export * from './pattern/pattern' -export * from './polygon/polygon' -export * from './polyline/polyline' -export * from './rect/rect' -export * from './style/style' -export * from './svg/svg' -export * from './switch/switch' -export * from './symbol/symbol' -export * from './title/title' -export * from './text/text' -export * from './textpath/textpath' -export * from './tspan/tspan' -export * from './use/use' diff --git a/packages/x6-vector/src/vector/line/exts.ts b/packages/x6-vector/src/vector/line/exts.ts deleted file mode 100644 index 90bfe7e3d9e..00000000000 --- a/packages/x6-vector/src/vector/line/exts.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Base } from '../common/base' -import { Line } from './line' -import { SVGLineAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - line(attrs?: Attributes | null): Line - line( - points: [[number, number], [number, number]], - attrs?: Attributes | null, - ): Line - line( - x1: number, - y1: number, - x2: number, - y2: number, - attrs?: Attributes | null, - ): Line - line( - x1?: [[number, number], [number, number]] | number | Attributes | null, - y1?: number | Attributes | null, - x2?: number, - y2?: number, - attrs?: Attributes | null, - ) { - return Line.create(x1, y1, x2, y2, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/line/line.test.ts b/packages/x6-vector/src/vector/line/line.test.ts deleted file mode 100644 index 90de33c62af..00000000000 --- a/packages/x6-vector/src/vector/line/line.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { PointArray } from '../../struct/point-array' -import { SVG } from '../svg/svg' -import { Line } from './line' - -describe('Line', () => { - describe('constructor()', () => { - it('should create an instance of Line', () => { - expect(new Line()).toBeInstanceOf(Line) - }) - - it('should create an instance with default points', () => { - const line = Line.create() - expect(line.attr('x1')).toEqual(0) - expect(line.attr('y1')).toEqual(0) - expect(line.attr('x2')).toEqual(0) - expect(line.attr('y2')).toEqual(0) - }) - - it('should create an instance with given attributes', () => { - expect(Line.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance with given points and attributes', () => { - const line = Line.create(1, 2, 3, 4, { id: 'foo' }) - expect(line.id()).toEqual('foo') - expect(line.attr('x1')).toEqual(1) - expect(line.attr('y1')).toEqual(2) - expect(line.attr('x2')).toEqual(3) - expect(line.attr('y2')).toEqual(4) - }) - - it('should create an instance with given array of points and attributes', () => { - const line = Line.create( - [ - [1, 2], - [3, 4], - ], - { id: 'foo' }, - ) - expect(line.id()).toEqual('foo') - expect(line.attr('x1')).toEqual(1) - expect(line.attr('y1')).toEqual(2) - expect(line.attr('x2')).toEqual(3) - expect(line.attr('y2')).toEqual(4) - }) - }) - - describe('toArray()', () => { - it('should return an array containing the points of the line', () => { - const line = Line.create() - const array = line.plot(1, 2, 3, 4).toArray() - expect(array).toEqual([ - [1, 2], - [3, 4], - ]) - }) - }) - - describe('toPointArray()', () => { - it('should return a PointArray containing the points of the line', () => { - const line = Line.create() - const array = line.plot(1, 2, 3, 4).toPointArray() - expect(array).toBeInstanceOf(PointArray) - expect(array.slice()).toEqual([ - [1, 2], - [3, 4], - ]) - }) - }) - - describe('move()', () => { - it('should move the line along x and y axis', () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line(1, 2, 3, 4) - line.move(50, 50) - const bbox = line.bbox() - expect(bbox.x).toEqual(50) - expect(bbox.y).toEqual(50) - expect(bbox.width).toEqual(2) - expect(bbox.height).toEqual(2) - svg.remove() - }) - }) - - describe('plot()', () => { - it('should work as a getter', () => { - const line = Line.create() - expect(line.plot()).toEqual([ - [0, 0], - [0, 0], - ]) - }) - - it('should plot line with points', () => { - const line = Line.create() - line.plot(1, 2, 3, 4) - expect(line.attr('x1')).toEqual(1) - expect(line.attr('y1')).toEqual(2) - expect(line.attr('x2')).toEqual(3) - expect(line.attr('y2')).toEqual(4) - }) - - it('should polt line with points string', () => { - const line = Line.create('1, 2, 3, 4') - expect(line.attr('x1')).toEqual(1) - expect(line.attr('y1')).toEqual(2) - expect(line.attr('x2')).toEqual(3) - expect(line.attr('y2')).toEqual(4) - }) - - it('should polt line with points array', () => { - const line = Line.create() - line.plot([ - [1, 2], - [3, 4], - ]) - expect(line.attr('x1')).toEqual(1) - expect(line.attr('y1')).toEqual(2) - expect(line.attr('x2')).toEqual(3) - expect(line.attr('y2')).toEqual(4) - }) - - it('should polt line with PointArray', () => { - const line = Line.create() - line.plot( - new PointArray([ - [1, 2], - [3, 4], - ]), - ) - expect(line.attr('x1')).toEqual(1) - expect(line.attr('y1')).toEqual(2) - expect(line.attr('x2')).toEqual(3) - expect(line.attr('y2')).toEqual(4) - }) - }) - - describe('size()', () => { - it('should set the size of the line', () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line(1, 2, 3, 4) - line.size(50, 50) - const bbox = line.bbox() - expect(bbox.x).toEqual(1) - expect(bbox.y).toEqual(2) - expect(bbox.width).toEqual(50) - expect(bbox.height).toEqual(50) - svg.remove() - }) - - it('should change the height proportionally', () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line(1, 2, 3, 4) - line.size(50, null) - const bbox = line.bbox() - expect(bbox.x).toEqual(1) - expect(bbox.y).toEqual(2) - expect(bbox.width).toEqual(50) - expect(bbox.height).toEqual(50) - svg.remove() - }) - - it('should change the width proportionally', () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line(1, 2, 3, 4) - line.size(null, 50) - const bbox = line.bbox() - expect(bbox.x).toEqual(1) - expect(bbox.y).toEqual(2) - expect(bbox.width).toEqual(50) - expect(bbox.height).toEqual(50) - svg.remove() - }) - }) - - describe('x()', () => { - it(`should set the x value of the line and returns itself`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line() - - expect(line.x(50)).toBe(line) - expect(line.bbox().x).toBe(50) - - svg.remove() - }) - - it(`should get the x value of the line`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line() - expect(line.x(50).x()).toBe(50) - - svg.remove() - }) - }) - - describe('y()', () => { - it(`should set the y value of the lineand returns itself`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line() - expect(line.y(50)).toBe(line) - expect(line.bbox().y).toBe(50) - - svg.remove() - }) - - it(`should get the y value of the line`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line() - expect(line.y(50).y()).toBe(50) - - svg.remove() - }) - }) - - describe('width()', () => { - it(`should set the width of the lineand returns itself`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line(1, 2, 3, 4) - expect(line.width(50)).toBe(line) - expect(line.bbox().width).toBe(50) - - svg.remove() - }) - - it(`should get the width of the line`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line(1, 2, 3, 4) - expect(line.width(50).width()).toBe(50) - - svg.remove() - }) - }) - - describe('height()', () => { - it(`should set the height of the lineand returns itself`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line(1, 2, 3, 4) - expect(line.height(50)).toBe(line) - expect(line.bbox().height).toBe(50) - - svg.remove() - }) - - it(`should get the height of the line`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.line(1, 2, 3, 4) - expect(line.height(50).height()).toBe(50) - - svg.remove() - }) - }) -}) diff --git a/packages/x6-vector/src/vector/line/line.ts b/packages/x6-vector/src/vector/line/line.ts deleted file mode 100644 index 2dd3d5b26d2..00000000000 --- a/packages/x6-vector/src/vector/line/line.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { PointArray } from '../../struct/point-array' -import { Size } from '../common/size' -import { Shape } from '../common/shape' -import { SVGLineAttributes } from './types' - -@Line.register('Line') -export class Line extends Shape { - x(): number - x(x: number | string): this - x(x?: number | string) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - } - - y(): number - y(y: number | string): this - y(y?: number | string) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - } - - width(): number - width(w: number | string): this - width(w?: number | string) { - return w == null ? this.bbox().width : this.size(w, this.bbox().height) - } - - height(): number - height(h: number | string): this - height(h?: number | string) { - return h == null ? this.bbox().height : this.size(this.bbox().width, h) - } - - size(width: string | number, height: string | number): this - size(width: string | number, height: string | number | null | undefined): this - size(width: string | number | null | undefined, height: string | number): this - size(width?: string | number | null, height?: string | number | null) { - const p = Size.normalize(this, width, height) - return this.plot(this.toPointArray().size(p.width, p.height)) - } - - move(x: number | string, y: number | string) { - return this.plot(this.toPointArray().move(x, y)) - } - - plot(): Line.Array - plot(points: Line.Array | PointArray | string): this - plot(x1: number, y1: number, x2: number, y2: number): this - plot( - x1?: Line.Array | PointArray | string | number, - y1?: number, - x2?: number, - y2?: number, - ): this - plot( - x1?: Line.Array | PointArray | string | number, - y1?: number, - x2?: number, - y2?: number, - ) { - if (x1 == null) { - return this.toArray() - } - - let arr: [number, number][] - - if (typeof x1 === 'string') { - arr = new PointArray(x1) - } else if (x1 instanceof PointArray || Array.isArray(x1)) { - arr = x1 - } else { - arr = [ - [x1 as number, y1 as number], - [x2 as number, y2 as number], - ] - } - - return this.attr({ - x1: arr[0][0], - y1: arr[0][1], - x2: arr[1][0], - y2: arr[1][1], - }) - } - - toArray(): Line.Array { - return [ - [+this.attr('x1')!, +this.attr('y1')!], - [+this.attr('x2')!, +this.attr('y2')!], - ] - } - - toPointArray() { - return new PointArray(this.toArray()) - } -} - -export namespace Line { - export type Array = [[number, number], [number, number]] - - export function create( - attrs?: Attributes | null, - ): Line - export function create( - points: Array | PointArray | string, - attrs?: Attributes | null, - ): Line - export function create( - x1: number, - y1: number, - x2: number, - y2: number, - attrs?: Attributes | null, - ): Line - export function create( - x1?: Array | PointArray | string | number | Attributes | null, - y1?: number | Attributes | null, - x2?: number, - y2?: number, - attrs?: Attributes | null, - ): Line - export function create( - x1?: Array | PointArray | string | number | Attributes | null, - y1?: number | Attributes | null, - x2?: number, - y2?: number, - attrs?: Attributes | null, - ) { - const line = new Line() - if (x1 == null) { - line.plot(0, 0, 0, 0) - } else if (Array.isArray(x1)) { - line.plot(x1) - if (y1 != null && typeof y1 === 'object') { - line.attr(y1) - } - } else if (typeof x1 === 'object') { - line.plot(0, 0, 0, 0).attr(x1) - } else { - line.plot(x1, y1 as number, x2 as number, y2 as number) - if (attrs) { - line.attr(attrs) - } - } - - return line - } -} diff --git a/packages/x6-vector/src/vector/line/mixins.ts b/packages/x6-vector/src/vector/line/mixins.ts deleted file mode 100644 index a67dbbe3732..00000000000 --- a/packages/x6-vector/src/vector/line/mixins.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { applyMixins } from '../../util/mixin' -import { LineExtension as MarkerLineExtension } from '../marker/exts' -import { Line } from './line' - -declare module './line' { - interface Line extends MarkerLineExtension {} -} - -applyMixins(Line, MarkerLineExtension) diff --git a/packages/x6-vector/src/vector/line/types.ts b/packages/x6-vector/src/vector/line/types.ts deleted file mode 100644 index eca1d6e384c..00000000000 --- a/packages/x6-vector/src/vector/line/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGLineAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - x1?: string | number - y1?: string | number - x2?: string | number - y2?: string | number - pathLength?: number -} diff --git a/packages/x6-vector/src/vector/marker/exts.ts b/packages/x6-vector/src/vector/marker/exts.ts deleted file mode 100644 index 4b5f6dc8c15..00000000000 --- a/packages/x6-vector/src/vector/marker/exts.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Decorator } from '../common/decorator' -import { Base } from '../common/base' -import { Marker } from './marker' -import { SVGMarkerAttributes, MarkerType } from './types' - -type MarkerMethod = { - marker( - attrs?: Attributes | null, - ): Marker - marker( - size: number | string, - attrs?: Attributes | null, - ): Marker - marker( - size: number | string, - update: Marker.Update, - attrs?: Attributes | null, - ): Marker - marker( - width: number | string, - height: number | string, - attrs?: Attributes | null, - ): Marker - marker( - width: number | string, - height: number | string, - update: Marker.Update, - attrs?: Attributes | null, - ): Marker - marker( - width?: number | string | Attributes | null, - height?: number | string | Marker.Update | Attributes | null, - update?: Marker.Update | Attributes | null, - attrs?: Attributes | null, - ): Marker -} - -export class ContainerExtension - extends Base - implements MarkerMethod -{ - @Decorator.checkDefs - marker( - width?: number | string | Attributes | null, - height?: number | string | Marker.Update | Attributes | null, - update?: Marker.Update | Attributes | null, - attrs?: Attributes | null, - ) { - return this.defs()!.marker(width, height, update, attrs) - } -} - -export class DefsExtension - extends Base - implements MarkerMethod -{ - marker( - width?: number | string | Attributes | null, - height?: number | string | Marker.Update | Attributes | null, - update?: Marker.Update | Attributes | null, - attrs?: Attributes | null, - ) { - return Marker.create(width, height, update, attrs).appendTo(this) - } -} - -export class LineExtension< - TSVGLineElement extends - | SVGLineElement - | SVGPathElement - | SVGPolygonElement - | SVGPolylineElement, -> extends Base { - marker(type: MarkerType, marker: Marker): this - marker( - type: MarkerType, - size: number | string, - attrs?: Attributes | null, - ): this - marker( - type: MarkerType, - size: number | string, - update: Marker.Update, - attrs?: Attributes | null, - ): this - marker( - type: MarkerType, - width: number | string, - height: number | string, - attrs?: Attributes | null, - ): this - marker( - type: MarkerType, - width: number | string, - height: number | string, - update: Marker.Update, - attrs?: Attributes | null, - ): this - @Decorator.checkDefs - marker( - type: MarkerType, - width: Marker | number | string, - height?: number | string | Marker.Update | Attributes | null, - update?: Marker.Update | Attributes | null, - attrs?: Attributes | null, - ) { - let attr = 'marker' - if (type !== 'all') { - attr += `-${type}` - } - - const marker = - width instanceof Marker - ? width - : this.defs()!.marker(width, height as number, update, attrs) - - this.attr(attr, marker.url()) - - return this - } -} diff --git a/packages/x6-vector/src/vector/marker/marker.test.ts b/packages/x6-vector/src/vector/marker/marker.test.ts deleted file mode 100644 index 20b3820b024..00000000000 --- a/packages/x6-vector/src/vector/marker/marker.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { G } from '../g/g' -import { Path } from '../path/path' -import { SVG } from '../svg/svg' -import { Marker } from './marker' - -describe('Marker', () => { - describe('constructor()', () => { - it('should create an instance of Marker', () => { - expect(new Marker()).toBeInstanceOf(Marker) - }) - - it('should set passed attributes on the element', () => { - expect(new Marker({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance with given attributes', () => { - const marker = Marker.create({ id: 'foo' }) - expect(marker.id()).toBe('foo') - }) - - it('should create an instance with given width and height', () => { - const marker = Marker.create(100, 200) - expect(marker.width()).toBe(100) - expect(marker.height()).toBe(200) - }) - - it('should create an instance with given width, height and attributes', () => { - const marker = Marker.create(100, 200, { id: 'foo' }) - expect(marker.width()).toBe(100) - expect(marker.height()).toBe(200) - expect(marker.id()).toBe('foo') - }) - - it('should create an instance with given width, height, update function and attributes', () => { - const marker = Marker.create(100, 200, (m) => m.rect(100, 100), { - id: 'foo', - }) - expect(marker.width()).toBe(100) - expect(marker.height()).toBe(200) - expect(marker.id()).toBe('foo') - expect(marker.children().length).toBe(1) - }) - - it('should create an instance with given size', () => { - const marker = Marker.create(100) - expect(marker.width()).toBe(100) - expect(marker.height()).toBe(100) - }) - - it('should create an instance with given size, update function and attributes', () => { - const marker = Marker.create(100, (m) => m.rect(100, 100), { id: 'foo' }) - - expect(marker.width()).toBe(100) - expect(marker.height()).toBe(100) - expect(marker.id()).toBe('foo') - expect(marker.children().length).toBe(1) - }) - - it('should create an instance with given size and attributes', () => { - const marker = Marker.create(100, { id: 'foo' }) - expect(marker.width()).toBe(100) - expect(marker.height()).toBe(100) - expect(marker.id()).toBe('foo') - }) - - it('should create an instance from container', () => { - const svg = new SVG() - const g = svg.group() - const marker = g.marker() - expect(marker).toBeInstanceOf(Marker) - }) - - it('should throw an error when container do not in svg context', () => { - const g = new G() - expect(() => g.marker()).toThrowError() - }) - }) - - describe('Line extension', () => { - let path: Path - let svg: SVG - - beforeEach(() => { - // because we use `reference` here we need to put it into the live dom - svg = new SVG().addTo(document.body) - path = svg.path( - 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100', - ) - }) - - afterEach(() => { - svg.remove() - }) - - it('should create an instance of Marker', () => { - path.marker('mid', 10, 12, function (m) { - m.rect(10, 12) - this.ref(5, 6) - }) - - const marker = path.reference('marker-mid')! - - expect(marker.children().length).toBe(1) - expect(marker.attr('refX')).toBe(5) - expect(marker).toBeInstanceOf(Marker) - - expect(path.reference('marker-end')).toBeNull() - }) - - it('should create a marker and applies it to the marker-start attribute', () => { - path.marker('start', 10, 12) - const marker = path.reference('marker-start')! - expect(path.node.getAttribute('marker-start')).toBe(marker.toString()) - }) - - it('should create a marker and applies it to the marker-mid attribute', () => { - path.marker('mid', 10, 12) - const marker = path.reference('marker-mid')! - expect(path.node.getAttribute('marker-mid')).toBe(marker.toString()) - }) - - it('should create a marker and applies it to the marker-end attribute', () => { - path.marker('end', 10, 12) - const marker = path.reference('marker-end')! - expect(path.node.getAttribute('marker-end')).toBe(marker.toString()) - }) - - it('should create a marker and applies it to the marker attribute', () => { - path.marker('all', 10, 12) - const marker = path.reference('marker')! - expect(path.node.getAttribute('marker')).toBe(marker.toString()) - }) - - it('accepts an instance of an existing marker element as the second argument', () => { - const marker = new Marker().size(11, 11) - path.marker('mid', marker) - expect(path.node.getAttribute('marker-mid')).toBe(marker.toString()) - }) - }) - - describe('width()', () => { - it('should set the markerWidth attribute', () => { - const marker = new Marker().width(100) - expect(marker.attr('markerWidth')).toBe(100) - }) - }) - - describe('height()', () => { - it('should set the markerHeight attribute', () => { - const marker = new Marker().height(100) - expect(marker.attr('markerHeight')).toBe(100) - }) - }) - - describe('orient()', () => { - it('should set the orient attribute', () => { - const marker = new Marker().orient('auto') - expect(marker.attr('orient')).toBe('auto') - }) - }) - - describe('units()', () => { - it('should set the units attribute', () => { - const marker = new Marker().units('userSpaceOnUse') - expect(marker.attr('markerUnits')).toBe('userSpaceOnUse') - }) - }) - - describe('ref()', () => { - it('should set the refX and refY attribute', () => { - const marker = new Marker().ref(10, 20) - expect(marker.attr('refX')).toBe(10) - expect(marker.attr('refY')).toBe(20) - }) - }) - - describe('update()', () => { - it('should update the marker', (done) => { - const marker = new Marker() - marker.rect(100, 100) - marker.update(function (m) { - m.rect(100, 100) - expect(this).toBe(marker) - expect(m).toBe(marker) - done() - }) - expect(marker.children().length).toBe(1) - }) - }) - - describe('toString()', () => { - it('should return the url identifier for this marker', () => { - const marker = new Marker() - expect(marker.toString()).toBe(`url(#${marker.id()})`) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/marker/marker.ts b/packages/x6-vector/src/vector/marker/marker.ts deleted file mode 100644 index 163acd025eb..00000000000 --- a/packages/x6-vector/src/vector/marker/marker.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { UnitNumber } from '../../struct/unit-number' -import { Vessel } from '../container/vessel' -import { Viewbox } from '../container/viewbox' -import { MarkerOrient, MarkerUnits, SVGMarkerAttributes } from './types' - -@Marker.register('Marker') -@Marker.mixin(Viewbox) -export class Marker extends Vessel { - height(): number - height(h: number | string | null): this - height(h?: number | string | null) { - return this.attr('markerHeight', h) - } - - width(): number - width(w?: number | string | null): this - width(w?: number | string | null) { - return this.attr('markerWidth', w) - } - - units(): MarkerUnits - units(units: MarkerUnits | null): this - units(units?: MarkerUnits | null) { - return this.attr('markerUnits', units) - } - - orient(): MarkerOrient - orient(orient: MarkerOrient | null): this - orient(orient?: MarkerOrient | null) { - return this.attr('orient', orient) - } - - ref(x: string | number, y: string | number) { - this.attr('refX', x) - return this.attr('refX', x).attr('refY', y) - } - - update(handler: Marker.Update) { - this.clear() - - if (typeof handler === 'function') { - handler.call(this, this) - } - - return this - } -} - -export interface Marker extends Viewbox {} - -export namespace Marker { - export type Update = (this: Marker, marker: Marker) => void -} - -export namespace Marker { - export function create( - attrs?: Attributes | null, - ): Marker - export function create( - size: number | string, - attrs?: Attributes | null, - ): Marker - export function create( - size: number | string, - update: Update, - attrs?: Attributes | null, - ): Marker - export function create( - width: number | string, - height: number | string, - attrs?: Attributes | null, - ): Marker - export function create( - width: number | string, - height: number | string, - update: Update, - attrs?: Attributes | null, - ): Marker - export function create( - width?: number | string | Attributes | null, - height?: number | string | Update | Attributes | null, - update?: Update | Attributes | null, - attrs?: Attributes | null, - ): Marker - export function create( - width?: number | string | Attributes | null, - height?: number | string | Update | Attributes | null, - update?: Update | Attributes | null, - attrs?: Attributes | null, - ): Marker { - const marker = new Marker() - - marker.attr('orient', 'auto') - - if (width != null) { - if (typeof width === 'object') { - marker.size(0, 0).ref(0, 0).viewbox(0, 0, 0, 0).attr(width) - } else { - const w = UnitNumber.toNumber(width) - if (height != null) { - if (typeof height === 'function') { - marker - .update(height) - .size(w, w) - .ref(w / 2, w / 2) - .viewbox(0, 0, w, w) - - if (update) { - marker.attr(update as Attributes) - } - } else if (typeof height === 'object') { - marker - .size(w, w) - .ref(w / 2, w / 2) - .viewbox(0, 0, w, w) - .attr(height) - } else { - const h = UnitNumber.toNumber(height) - marker - .size(w, h) - .ref(w / 2, h / 2) - .viewbox(0, 0, w, h) - if (update != null) { - if (typeof update === 'function') { - marker.update(update) - if (attrs) { - marker.attr(attrs) - } - } else { - marker.attr(update) - } - } - } - } else { - marker - .size(w, w) - .ref(w / 2, w / 2) - .viewbox(0, 0, w, w) - } - } - } - - return marker - } -} diff --git a/packages/x6-vector/src/vector/marker/types.ts b/packages/x6-vector/src/vector/marker/types.ts deleted file mode 100644 index fd6cd486a18..00000000000 --- a/packages/x6-vector/src/vector/marker/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export type MarkerType = 'start' | 'end' | 'mid' | 'all' -export type MarkerUnits = 'userSpaceOnUse' | 'objectBoundingBox' -export type MarkerOrient = 'auto' | 'auto-start-reverse' | number -export type MarkerRefX = 'left' | 'center' | 'right' | number -export type MarkerRefY = 'top' | 'center' | 'bottom' | number - -export interface SVGMarkerAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - markerHeight?: string | number - markerWidth?: string | number - markerUnits?: MarkerUnits - orient?: MarkerOrient - preserveAspectRatio?: string - refX?: MarkerRefX - refY?: MarkerRefY - viewBox?: string -} diff --git a/packages/x6-vector/src/vector/mask/exts.test.ts b/packages/x6-vector/src/vector/mask/exts.test.ts deleted file mode 100644 index 7d86de25392..00000000000 --- a/packages/x6-vector/src/vector/mask/exts.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Rect } from '../rect/rect' -import { SVG } from '../svg/svg' -import { Mask } from './mask' - -describe('Mask Extension', () => { - describe('masker()', () => { - it('should return the instance of Mask the current element is maskped with', () => { - const svg = new SVG().appendTo(document.body) - const mask = svg.mask() - const rect = svg.rect(100, 100).maskWith(mask) - expect(rect.masker()).toEqual(mask) - svg.remove() - }) - - it('should return null if no maskPath was found', () => { - expect(new Rect().masker()).toBe(null) - }) - }) - - describe('maskWith()', () => { - it('should set the mask attribute on the element to the id of the maskPath', () => { - const mask = new Mask().id('foo') - const rect = new Rect().maskWith(mask) - expect(rect.attr('mask')).toBe('url("#foo")') - }) - - it('should create a maskPath and appends the passed element to it to mask current element', () => { - const svg = new SVG().appendTo(document.body) - const circle = svg.circle(40) - const rect = svg.rect(100, 100).maskWith(circle) - expect(circle.parent()).toBeInstanceOf(Mask) - expect(rect.attr('mask')).toBe(`url("#${circle.parent()!.id()}")`) - svg.remove() - }) - }) - - describe('unmask()', () => { - it('should set the mask-target attribute to null and returns itself', () => { - const mask = new Mask().id('foo') - const rect = new Rect().maskWith(mask) - expect(rect.unmask()).toBe(rect) - expect(rect.attr('mask')).toBe(undefined) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/mask/exts.ts b/packages/x6-vector/src/vector/mask/exts.ts deleted file mode 100644 index b5422c47305..00000000000 --- a/packages/x6-vector/src/vector/mask/exts.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Adopter } from '../../dom/common/adopter' -import { Decorator } from '../common/decorator' -import { Base } from '../common/base' -import { Vector } from '../vector/vector' -import { Container } from '../container/container' -import { Mask } from './mask' -import { SVGMaskAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - @Decorator.checkDefs - mask(attrs?: Attributes | null) { - return this.defs()!.mask(attrs) - } -} - -export class DefsExtension< - TSVGElement extends SVGElement, -> extends Base { - mask(attrs?: Attributes | null) { - return Mask.create(attrs).appendTo(this) - } -} - -export class ElementExtension< - TSVGElement extends SVGElement, -> extends Base { - masker() { - return this.reference('mask') - } - - maskWith(element: Mask | Adopter.Target) { - let masker: Mask | undefined | null - if (element instanceof Mask) { - masker = element - } else { - const parent = this.parent() - masker = parent && parent.mask() - if (masker) { - masker.add(element) - } - } - - if (masker) { - this.attr('mask', `url("#${masker.id()}")`) - } - - return this - } - - unmask() { - return this.attr('mask', null) - } -} diff --git a/packages/x6-vector/src/vector/mask/mask.test.ts b/packages/x6-vector/src/vector/mask/mask.test.ts deleted file mode 100644 index eb4dac5f830..00000000000 --- a/packages/x6-vector/src/vector/mask/mask.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { SVG } from '../svg/svg' -import { Mask } from './mask' - -describe('Mask', () => { - describe('constructor()', () => { - it('should create an instance of Mask', () => { - expect(new Mask()).toBeInstanceOf(Mask) - }) - - it('should set passed attributes on the element', () => { - expect(Mask.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create a maskPath in the container', () => { - const svg = new SVG().appendTo(document.body) - const mask = svg.mask() - expect(mask).toBeInstanceOf(Mask) - expect(svg.defs().children()).toEqual([mask]) - svg.remove() - }) - }) - - describe('remove()', () => { - it('should unmask all targets', () => { - const svg = new SVG().appendTo(document.body) - const mask = svg.mask() - const rect = svg.rect(100, 100).maskWith(mask) - expect(mask.remove()).toBe(mask) - expect(rect.masker()).toBe(null) - svg.remove() - }) - }) - - describe('targets()', () => { - it('should get all targets of this maskPath', () => { - const svg = new SVG().appendTo(document.body) - const mask = svg.mask() - const rect = svg.rect(100, 100).maskWith(mask) - expect(mask.targets()).toEqual([rect]) - svg.remove() - }) - }) -}) diff --git a/packages/x6-vector/src/vector/mask/mask.ts b/packages/x6-vector/src/vector/mask/mask.ts deleted file mode 100644 index f497eb41f19..00000000000 --- a/packages/x6-vector/src/vector/mask/mask.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Vector } from '../vector/vector' -import { Vessel } from '../container/vessel' -import { SVGMaskAttributes } from './types' - -@Mask.register('Mask') -export class Mask extends Vessel { - remove() { - this.targets().forEach((target) => target.unmask()) - return super.remove() - } - - targets(): TVector[] { - return this.findTargets('mask') - } -} - -export namespace Mask { - export function create( - attrs?: Attributes | null, - ) { - const mask = new Mask() - if (attrs) { - mask.attr(attrs) - } - return mask - } -} diff --git a/packages/x6-vector/src/vector/mask/types.ts b/packages/x6-vector/src/vector/mask/types.ts deleted file mode 100644 index 6852ab21583..00000000000 --- a/packages/x6-vector/src/vector/mask/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGMaskAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - x?: string | number - y?: string | number - width?: string | number - height?: string | number - markerUnits?: 'userSpaceOnUse' | 'objectBoundingBox' - maskContentUnits?: 'userSpaceOnUse' | 'objectBoundingBox' -} diff --git a/packages/x6-vector/src/vector/metadata/types.ts b/packages/x6-vector/src/vector/metadata/types.ts deleted file mode 100644 index ff7e407f57e..00000000000 --- a/packages/x6-vector/src/vector/metadata/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { SVGCoreAttributes } from '../types/attributes-core' - -export interface SVGMetadataAttributes - extends SVGCoreAttributes {} diff --git a/packages/x6-vector/src/vector/path/exts.ts b/packages/x6-vector/src/vector/path/exts.ts deleted file mode 100644 index a0a5556e2c7..00000000000 --- a/packages/x6-vector/src/vector/path/exts.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { PathArray } from '../../struct/path-array' -import { Base } from '../common/base' -import { Path } from './path' -import { SVGPathAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - path(attrs?: Attributes | null): Path - path( - d: string | Path.Segment[] | PathArray, - attrs?: Attributes | null, - ): Path - path( - d?: string | Path.Segment[] | PathArray | Attributes | null, - attrs?: Attributes | null, - ) { - return Path.create(d, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/path/mixins.ts b/packages/x6-vector/src/vector/path/mixins.ts deleted file mode 100644 index 18837680ae2..00000000000 --- a/packages/x6-vector/src/vector/path/mixins.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { applyMixins } from '../../util/mixin' -import { PathExtension as TextpathExtension } from '../textpath/exts' -import { LineExtension as MarkerLineExtension } from '../marker/exts' -import { Path } from './path' - -declare module './path' { - interface Path - extends MarkerLineExtension, - TextpathExtension {} -} - -applyMixins(Path, TextpathExtension, MarkerLineExtension) diff --git a/packages/x6-vector/src/vector/path/path.test.ts b/packages/x6-vector/src/vector/path/path.test.ts deleted file mode 100644 index 8811f201f12..00000000000 --- a/packages/x6-vector/src/vector/path/path.test.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { PathArray } from '../../struct/path-array' -import { SVG } from '../svg/svg' -import { Path } from './path' - -describe('Path', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(Path.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance with path string and attributes', () => { - const path = Path.create('M1 2 3 4', { id: 'foo' }) - expect(path.toArray()).toEqual([ - ['M', 1, 2], - ['L', 3, 4], - ]) - }) - }) - - describe('toArray()', () => { - it('should return the underlying array', () => { - const path = Path.create() - const array = path.plot('M1 2 3 4').toArray() - expect(array).toEqual([ - ['M', 1, 2], - ['L', 3, 4], - ]) - }) - }) - - describe('toPathArray()', () => { - it('should return the underlying toPathArray', () => { - const path = Path.create() - const array = path.plot('M1 2 3 4').toPathArray() - expect(array).toBeInstanceOf(PathArray) - expect(array.slice()).toEqual([ - ['M', 1, 2], - ['L', 3, 4], - ]) - }) - }) - - describe('x()', () => { - it('should get the x position of the path', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M10 10 50, 50') - expect(path.x()).toBe(10) - - svg.remove() - }) - - it('should set the x position of the path and returns itself', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M0 0 50, 50') - expect(path.x(100)).toBe(path) - expect(path.x()).toBe(100) - - svg.remove() - }) - }) - - describe('y()', () => { - it('gets the y position of the path', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M10 10 50, 50') - expect(path.y()).toBe(10) - - svg.remove() - }) - - it('sets the y position of the path and returns itself', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M0 0 50, 50') - expect(path.y(100)).toBe(path) - expect(path.y()).toBe(100) - - svg.remove() - }) - }) - - describe('width()', () => { - it('should get the width of the path', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M0 0 50, 50') - expect(path.width()).toBe(50) - - svg.remove() - }) - - it('should set the width of the path and returns itself', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M0 0 50, 50') - expect(path.width(100)).toBe(path) - expect(path.width()).toBe(100) - - svg.remove() - }) - }) - - describe('height()', () => { - it('should get the height of the path', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M0 0 50, 50') - expect(path.height()).toBe(50) - - svg.remove() - }) - - it('should set the height of the path and returns itself', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M0 0 50, 50') - expect(path.height(100)).toBe(path) - expect(path.height()).toBe(100) - - svg.remove() - }) - }) - - describe('size()', () => { - it('should set the size of the path', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M0 0 50, 50') - path.size(100, 100) - expect(path.bbox().toArray()).toEqual([0, 0, 100, 100]) - - svg.remove() - }) - - it('should change height proportionally', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M0 0 50, 50') - path.size(100, null) - expect(path.bbox().toArray()).toEqual([0, 0, 100, 100]) - - svg.remove() - }) - - it('should change width proportionally', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M0 0 50, 50') - path.size(null, 100) - expect(path.bbox().toArray()).toEqual([0, 0, 100, 100]) - - svg.remove() - }) - }) - - describe('move()', () => { - it('should move the path along x and y axis', () => { - const svg = new SVG().appendTo(document.body) - - const path = svg.path('M0 0 50, 50') - path.move(50, 50) - expect(path.bbox().toArray()).toEqual([50, 50, 50, 50]) - - svg.remove() - }) - }) - - describe('plot()', () => { - it('should work as a getter', () => { - const path = Path.create('M1 2 3 4') - expect(path.plot()).toEqual([ - ['M', 1, 2], - ['L', 3, 4], - ]) - }) - - it('should plot with a path string', () => { - const path = Path.create('M1 2 3 4') - path.plot('M0 0L50 50') - expect(path.attr('d')).toEqual('M0 0L50 50') - }) - - it('should plot with path segment', () => { - const path = Path.create('M1 2 3 4') - path.plot([ - ['M', 0, 0], - ['L', 50, 50], - ]) - expect(path.attr('d')).toEqual('M0 0L50 50') - }) - - it('should plot with PathArray', () => { - const path = Path.create('M1 2 3 4') - path.plot( - new PathArray([ - ['M', 0, 0], - ['L', 50, 50], - ]), - ) - expect(path.attr('d')).toEqual('M0 0L50 50') - }) - }) - - describe('length()', () => { - it('should return the path length', () => { - expect(Path.create('M0 0 0, 50').length()).toEqual(50) - }) - }) - - describe('pointAt()', () => { - it('should return a point on the path', () => { - const p = Path.create('M0 0 50, 50').pointAt(0) - expect(p.x).toEqual(0) - expect(p.y).toEqual(0) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/path/path.ts b/packages/x6-vector/src/vector/path/path.ts deleted file mode 100644 index b155562f4c8..00000000000 --- a/packages/x6-vector/src/vector/path/path.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Point } from '../../struct/point' -import { PathArray } from '../../struct/path-array' -import { Size } from '../common/size' -import { Shape } from '../common/shape' -import { SVGPathAttributes } from './types' -import * as Helper from './util' - -@Path.register('Path') -export class Path extends Shape { - protected arr: PathArray | null - - x(): number - x(x: number | string): this - x(x?: number | string) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - } - - y(): number - y(y: number | string): this - y(y?: number | string) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - } - - width(): number - width(w: number | string): this - width(w?: number | string) { - return w == null ? this.bbox().width : this.size(w, this.bbox().height) - } - - height(): number - height(h: number | string): this - height(h?: number | string) { - return h == null ? this.bbox().height : this.size(this.bbox().width, h) - } - - move(x: number | string, y: number | string) { - return this.attr('d', this.toPathArray().move(x, y).toString()) - } - - plot(): Path.Segment[] - plot(d: string | Path.Segment[] | PathArray): this - plot(d?: string | Path.Segment[] | PathArray) { - if (d == null) { - return this.toArray() - } - - this.arr = null - - if (typeof d === 'string') { - this.attr('d', d) - } else { - this.arr = new PathArray(d) - this.attr('d', this.arr.toString()) - } - - return this - } - - size(width: string | number, height: string | number): this - size(width: string | number, height: string | number | null | undefined): this - size(width: string | number | null | undefined, height: string | number): this - size(width?: string | number | null, height?: string | number | null) { - const p = Size.normalize(this, width, height) - return this.attr('d', this.toPathArray().size(p.width, p.height).toString()) - } - - length() { - return this.node.getTotalLength() - } - - pointAt(length: number) { - return new Point(this.node.getPointAtLength(length)) - } - - toArray() { - return this.toPathArray().toArray() - } - - toPathArray() { - if (this.arr == null) { - this.arr = new PathArray(this.attr('d')) - } - return this.arr - } -} - -export namespace Path { - export function create( - attrs?: Attributes | null, - ): Path - export function create( - d: string | Path.Segment[] | PathArray, - attrs?: Attributes | null, - ): Path - export function create( - d?: string | Path.Segment[] | PathArray | Attributes | null, - attrs?: Attributes | null, - ): Path - export function create( - d?: string | Path.Segment[] | PathArray | Attributes | null, - attrs?: Attributes | null, - ) { - const path = new Path() - if (d != null) { - if (typeof d === 'string' || Array.isArray(d)) { - path.plot(d) - if (attrs) { - path.attr(attrs) - } - } else { - path.attr(d) - } - } - return path - } -} - -export namespace Path { - export type Segment = Helper.Segment - - export const parse = Helper.parse - export const toString = Helper.toString -} diff --git a/packages/x6-vector/src/vector/path/types.ts b/packages/x6-vector/src/vector/path/types.ts deleted file mode 100644 index ba16c819a50..00000000000 --- a/packages/x6-vector/src/vector/path/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGPathAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - d?: string - pathLength?: string | number -} diff --git a/packages/x6-vector/src/vector/path/util.test.ts b/packages/x6-vector/src/vector/path/util.test.ts deleted file mode 100644 index 54e6ba1d01e..00000000000 --- a/packages/x6-vector/src/vector/path/util.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { parse } from './util' - -describe('Path', () => { - describe('parse()', () => { - it('should parse path string', () => { - const paths = [ - [ - 'M 10 10 H 20', - [ - ['M', 10, 10], - ['H', 20], - ], - ], - [ - 'M 10 10 V 20', - [ - ['M', 10, 10], - ['V', 20], - ], - ], - [ - 'M 10 20 C 10 10 25 10 25 20 S 40 30 40 20', - [ - ['M', 10, 20], - ['C', 10, 10, 25, 10, 25, 20], - ['S', 40, 30, 40, 20], - ], - ], - [ - 'M 20 20 Q 40 0 60 20', - [ - ['M', 20, 20], - ['Q', 40, 0, 60, 20], - ], - ], - [ - 'M 20 20 Q 40 0 60 20 T 100 20', - [ - ['M', 20, 20], - ['Q', 40, 0, 60, 20], - ['T', 100, 20], - ], - ], - [ - 'M 30 15 A 15 15 0 0 0 15 30', - [ - ['M', 30, 15], - ['A', 15, 15, 0, 0, 0, 15, 30], - ], - ], - ['m 10 10', [['M', 10, 10]]], - [ - 'M 10 10 m 10 10', - [ - ['M', 10, 10], - ['M', 20, 20], - ], - ], - [ - 'M 10 10 l 10 10', - [ - ['M', 10, 10], - ['L', 20, 20], - ], - ], - [ - 'M 10 10 c 0 10 10 10 10 0', - [ - ['M', 10, 10], - ['C', 10, 20, 20, 20, 20, 10], - ], - ], - ['M 10 10 z', [['M', 10, 10], ['Z']]], - [ - 'M 10 10 20 20', - [ - ['M', 10, 10], - ['L', 20, 20], - ], - ], - [ - 'M 10 10 L 20 20 30 30', - [ - ['M', 10, 10], - ['L', 20, 20], - ['L', 30, 30], - ], - ], - [ - 'M 10 10 C 10 20 20 20 20 10 20 0 30 0 30 10', - [ - ['M', 10, 10], - ['C', 10, 20, 20, 20, 20, 10], - ['C', 20, 0, 30, 0, 30, 10], - ], - ], - - // edge cases - ['L 10 10', [['L', 10, 10]]], - ['C 10 20 20 20 20 10', [['C', 10, 20, 20, 20, 20, 10]]], - ['Z', [['Z']]], - ['M 10 10 Z L 20 20', [['M', 10, 10], ['Z'], ['L', 20, 20]]], - [ - 'M 10 10 Z C 10 20 20 20 20 10', - [['M', 10, 10], ['Z'], ['C', 10, 20, 20, 20, 20, 10]], - ], - ['M 10 10 Z Z', [['M', 10, 10], ['Z'], ['Z']]], - - // empty string - ['', []], - - // invalid command - ['X 10 10', []], - - // no arguments for a command that needs them - ['M', []], - - // too few arguments - ['M 10', []], - - // too many arguments - ['M 10 10 20', [['M', 10, 10]]], - - // mixing invalid and valid commands - ['X M 10 10', [['M', 10, 10]]], - - // invalid commands interspersed with valid commands - [ - 'X m 10 10 X 20 20', - [ - ['M', 10, 10], - ['L', 30, 30], - ], - ], - - [ - 'M 0. .0 A 1e1 1 0 1 0 -1.5 -1', - [ - ['M', 0, 0], - ['A', 1e1, 1, 0, 1, 0, -1.5, -1], - ], - ], - [ - 'M 14.4 29.52 a .72 .72 0 1 0 -.72 -.72 A .72 .72 0 0 0 14.4 29.52Z', - [ - ['M', 14.4, 29.52], - ['A', 0.72, 0.72, 0, 1, 0, 13.68, 28.8], - ['A', 0.72, 0.72, 0, 0, 0, 14.4, 29.52], - ['Z'], - ], - ], - ['M 1.5.8', [['M', 1.5, 0.8]]], - ['M 1.5-8', [['M', 1.5, -8]]], - ] - - paths.forEach((item) => { - expect(parse(item[0] as string)).toEqual(item[1] as any) - }) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/path/util.ts b/packages/x6-vector/src/vector/path/util.ts deleted file mode 100644 index c5a3d0d6ced..00000000000 --- a/packages/x6-vector/src/vector/path/util.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { Point } from '../../struct/point' - -type CommandUpperCase = - | 'M' - | 'L' - | 'H' - | 'V' - | 'C' - | 'S' - | 'Q' - | 'T' - | 'A' - | 'Z' - -type CommandLowerCase = - | 'm' - | 'l' - | 'h' - | 'v' - | 'c' - | 's' - | 'q' - | 't' - | 'a' - | 'z' - -type Command = CommandUpperCase | CommandLowerCase - -export type Segment = [Command, ...number[]] - -interface Options { - segment: Segment - segments: Segment[] - - lastToken: string - lastCommand: Command - - inNumber: boolean - number: string - inSegment: boolean - hasDecimal: boolean - hasExponent: boolean - - absolute: boolean - p0: Point - p: Point -} - -type Hnadler = (parameters: number[], p: Point, p0: Point) => Segment - -const upperHandler: { [key in CommandUpperCase]: Hnadler } = { - M(c, p, p0) { - p.x = p0.x = c[0] - p.y = p0.y = c[1] - return ['M', p.x, p.y] - }, - L(c, p) { - p.x = c[0] - p.y = c[1] - return ['L', c[0], c[1]] - }, - H(c, p) { - p.x = c[0] - return ['H', c[0]] - }, - V(c, p) { - p.y = c[0] - return ['V', c[0]] - }, - C(c, p) { - p.x = c[4] - p.y = c[5] - return ['C', c[0], c[1], c[2], c[3], c[4], c[5]] - }, - S(c, p) { - p.x = c[2] - p.y = c[3] - return ['S', c[0], c[1], c[2], c[3]] - }, - Q(c, p) { - p.x = c[2] - p.y = c[3] - return ['Q', c[0], c[1], c[2], c[3]] - }, - T(c, p) { - p.x = c[0] - p.y = c[1] - return ['T', c[0], c[1]] - }, - Z(c, p, p0) { - p.x = p0.x - p.y = p0.y - return ['Z'] - }, - A(c, p) { - p.x = c[5] - p.y = c[6] - return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]] - }, -} - -const handlers: { [key in Command]: Hnadler } = { ...upperHandler } as any - -Object.keys(upperHandler).forEach((upper: CommandUpperCase) => { - const lower = upper.toLowerCase() as CommandLowerCase - handlers[lower] = (c: number[], p: Point, p0: Point) => { - if (upper === 'H') { - c[0] += p.x - } else if (upper === 'V') { - c[0] += p.y - } else if (upper === 'A') { - c[5] += p.x - c[6] += p.y - } else { - for (let i = 0, l = c.length; i < l; i += 1) { - c[i] += i % 2 ? p.y : p.x - } - } - - return handlers[upper](c, p, p0) - } -}) - -function isCommand(token: string): token is Command { - return /[achlmqstvz]/i.test(token) -} - -function isSegmentComplete(parser: Options) { - const count = parser.segment.length - if (count > 0) { - const parameterMap = { - M: 2, - L: 2, - H: 1, - V: 1, - C: 6, - S: 4, - Q: 4, - T: 2, - A: 7, - Z: 0, - } - const cmd = parser.segment[0].toUpperCase() as CommandUpperCase - return count === parameterMap[cmd] + 1 - } - return false -} - -function startNewSegment(parser: Options, token: string) { - if (parser.inNumber) { - finalizeNumber(parser, false) - } - - const isNewCommand = isCommand(token) - if (isNewCommand) { - parser.segment = [token as Command] - } else { - const { lastCommand } = parser - const small = lastCommand.toLowerCase() - const isSmall = lastCommand === small - parser.segment = [small === 'm' ? (isSmall ? 'l' : 'L') : lastCommand] - } - - parser.inSegment = true - parser.lastCommand = parser.segment[0] - - return isNewCommand -} - -function finalizeSegment(parser: Options) { - parser.inSegment = false - if (parser.absolute) { - parser.segment = toAbsolut(parser) - } - parser.segments.push(parser.segment) -} - -function finalizeNumber(parser: Options, inNumber: boolean) { - if (!parser.inNumber) { - throw new Error('Parser Error') - } - - if (parser.number) { - parser.segment.push(Number.parseFloat(parser.number)) - } - - parser.number = '' - parser.inNumber = inNumber - parser.hasDecimal = false - parser.hasExponent = false - - if (isSegmentComplete(parser)) { - finalizeSegment(parser) - } -} - -function toAbsolut(parser: Options) { - const command = parser.segment[0] - const args = parser.segment.slice(1) as number[] - return handlers[command](args, parser.p, parser.p0) -} - -function isArcFlag(parser: Options) { - if (parser.segment.length === 0) { - return false - } - const isArc = parser.segment[0].toUpperCase() === 'A' - const { length } = parser.segment - return isArc && (length === 4 || length === 5) -} - -function isExponential(parser: Options) { - return parser.lastToken.toUpperCase() === 'E' -} - -export function parse(d: string, toAbsolute = true) { - let index = 0 - let token = '' - - const parser: Options = { - segment: null as any, - segments: [], - inNumber: false, - number: '', - lastToken: '', - lastCommand: null as any, - inSegment: false, - hasDecimal: false, - hasExponent: false, - absolute: toAbsolute, - p0: new Point(), - p: new Point(), - } - - while (((parser.lastToken = token), (token = d.charAt(index)))) { - index += 1 - - if (!parser.inSegment) { - if ( - (!isCommand(token) && !parser.lastCommand) || - startNewSegment(parser, token) - ) { - continue - } - } - - if (token === '.') { - if (parser.hasDecimal || parser.hasExponent) { - finalizeNumber(parser, false) - index -= 1 - continue - } - parser.inNumber = true - parser.hasDecimal = true - parser.number += token - continue - } - - if (!Number.isNaN(Number.parseInt(token, 10))) { - if (parser.number === '0' || (parser.inNumber && isArcFlag(parser))) { - finalizeNumber(parser, true) - } - - parser.inNumber = true - parser.number += token - continue - } - - if (token === ' ' || token === ',') { - if (parser.inNumber) { - finalizeNumber(parser, false) - } - continue - } - - if (token === '-') { - if (parser.inNumber && !isExponential(parser)) { - finalizeNumber(parser, false) - index -= 1 - continue - } - parser.number += token - parser.inNumber = true - continue - } - - if (token.toUpperCase() === 'E') { - parser.number += token - parser.hasExponent = true - continue - } - - if (isCommand(token)) { - if (parser.inNumber) { - finalizeNumber(parser, false) - } else if (!isSegmentComplete(parser)) { - throw new Error('parser Error') - } else { - finalizeSegment(parser) - } - index -= 1 - } - } - - if (parser.inNumber) { - finalizeNumber(parser, false) - } - - if (parser.inSegment && isSegmentComplete(parser)) { - finalizeSegment(parser) - } - - return parser.segments -} - -export function toString(segments: Segment[]) { - return segments.reduce((memo, seg) => { - let ret = memo - - ret += seg[0] - - if (seg[1] != null) { - ret += seg[1] - - if (seg[2] != null) { - ret += ' ' - ret += seg[2] - - if (seg[3] != null) { - ret += ' ' - ret += seg[3] - ret += ' ' - ret += seg[4] - - if (seg[5] != null) { - ret += ' ' - ret += seg[5] - ret += ' ' - ret += seg[6] - - if (seg[7] != null) { - ret += ' ' - ret += seg[7] - } - } - } - } - } - - return ret - }, '') -} diff --git a/packages/x6-vector/src/vector/pattern/exts.ts b/packages/x6-vector/src/vector/pattern/exts.ts deleted file mode 100644 index fb78ff9ae07..00000000000 --- a/packages/x6-vector/src/vector/pattern/exts.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Base } from '../common/base' -import { Decorator } from '../common/decorator' -import { Pattern } from './pattern' -import { SVGPatternAttributes } from './types' - -type PatternMethod = { - pattern( - attrs?: Attributes | null, - ): Pattern - pattern( - size: number | string, - attrs?: Attributes | null, - ): Pattern - pattern( - size: number | string, - update: Pattern.Update, - attrs?: Attributes | null, - ): Pattern - pattern( - width: number | string, - height: number | string, - update: Pattern.Update, - attrs?: Attributes | null, - ): Pattern - pattern( - width: number | string, - height: number | string, - attrs?: Attributes | null, - ): Pattern - pattern( - width?: number | string | Attributes | null, - height?: number | string | Pattern.Update | Attributes | null, - update?: Pattern.Update | Attributes | null, - attrs?: Attributes | null, - ): Pattern -} - -export class ContainerExtension - extends Base - implements PatternMethod -{ - @Decorator.checkDefs - pattern( - width?: number | string | Attributes | null, - height?: number | string | Pattern.Update | Attributes | null, - update?: Pattern.Update | Attributes | null, - attrs?: Attributes | null, - ) { - return this.defs()!.pattern(width, height, update, attrs) - } -} - -export class DefsExtension - extends Base - implements PatternMethod -{ - pattern( - width?: number | string | Attributes | null, - height?: number | string | Pattern.Update | Attributes | null, - update?: Pattern.Update | Attributes | null, - attrs?: Attributes | null, - ) { - return Pattern.create(width, height, update, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/pattern/override.ts b/packages/x6-vector/src/vector/pattern/override.ts deleted file mode 100644 index 663f4f84159..00000000000 --- a/packages/x6-vector/src/vector/pattern/override.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AttributesBase } from '../../dom/attributes' - -export class AttrOverride extends AttributesBase { - attr(attr: any, value: any) { - return super.attr(attr === 'transform' ? 'patternTransform' : attr, value) - } -} diff --git a/packages/x6-vector/src/vector/pattern/pattern.test.ts b/packages/x6-vector/src/vector/pattern/pattern.test.ts deleted file mode 100644 index b67c891e2e7..00000000000 --- a/packages/x6-vector/src/vector/pattern/pattern.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { G } from '../g/g' -import { SVG } from '../svg/svg' -import { Pattern } from './pattern' - -describe('Pattern', () => { - describe('constructor()', () => { - it('should create a new object of type Pattern', () => { - expect(Pattern.create()).toBeInstanceOf(Pattern) - }) - - it('should create an instance with given attributes', () => { - expect(Pattern.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance with given width and height', () => { - const pattern = Pattern.create(100, 200) - expect(pattern.width()).toBe(100) - expect(pattern.height()).toBe(200) - }) - - it('should create an instance with given width, height and attributes', () => { - const pattern = Pattern.create(100, 200, { id: 'foo' }) - expect(pattern.width()).toBe(100) - expect(pattern.height()).toBe(200) - expect(pattern.id()).toBe('foo') - }) - - it('should create an instance with given width, height, update function and attributes', () => { - const pattern = Pattern.create(100, 200, (m) => m.rect(100, 100), { - id: 'foo', - }) - expect(pattern.width()).toBe(100) - expect(pattern.height()).toBe(200) - expect(pattern.id()).toBe('foo') - expect(pattern.children().length).toBe(1) - }) - - it('should create an instance with given size', () => { - const pattern = Pattern.create(100) - expect(pattern.width()).toBe(100) - expect(pattern.height()).toBe(100) - }) - - it('should create an instance with given size, update function and attributes', () => { - const pattern = Pattern.create(100, (m) => m.rect(100, 100), { - id: 'foo', - }) - - expect(pattern.width()).toBe(100) - expect(pattern.height()).toBe(100) - expect(pattern.id()).toBe('foo') - expect(pattern.children().length).toBe(1) - }) - - it('should create an instance with given size and attributes', () => { - const pattern = Pattern.create(100, { id: 'foo' }) - expect(pattern.width()).toBe(100) - expect(pattern.height()).toBe(100) - expect(pattern.id()).toBe('foo') - }) - - it('should create an instance from container', () => { - const svg = new SVG() - const g = svg.group() - const pattern = g.pattern() - expect(pattern).toBeInstanceOf(Pattern) - }) - - it('should throw an error when container do not in svg context', () => { - const g = new G() - expect(() => g.pattern()).toThrowError() - }) - }) - - describe('attr()', () => { - it('should relay to parents attr method for any call except transformation', () => { - const pattern = new Pattern() - pattern.attr('tabIndex', 1) - pattern.attr('transform', 'foo') - - expect(pattern.attr('tabIndex')).toEqual(1) - expect(pattern.attr('patternTransform')).toEqual('foo') - }) - }) - - describe('bbox()', () => { - it('should return an empty box', () => { - const bbox = new Pattern().bbox() - expect(bbox.x).toEqual(0) - expect(bbox.y).toEqual(0) - expect(bbox.w).toEqual(0) - expect(bbox.h).toEqual(0) - }) - }) - - describe('targets()', () => { - it('should get all targets of this pattern', () => { - const svg = new SVG().appendTo(document.body) - const pattern = svg.pattern() - const rect = svg.rect(100, 100).fill(pattern) - expect(pattern.targets()).toEqual([rect]) - svg.remove() - }) - }) - - describe('update()', () => { - it('should clear the element', () => { - const pattern = new Pattern() - pattern.rect(100, 100) - expect(pattern.update().children()).toEqual([]) - }) - }) - - describe('url()', () => { - it('should return "url(#id)"', () => { - const pattern = new Pattern().id('foo') - expect(pattern.url()).toBe('url(#foo)') - }) - }) - - describe('toString()', () => { - it('should call `url()` and returns the result', () => { - const pattern = new Pattern() - expect(pattern.toString()).toBe(pattern.url()) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/pattern/pattern.ts b/packages/x6-vector/src/vector/pattern/pattern.ts deleted file mode 100644 index 60569b82229..00000000000 --- a/packages/x6-vector/src/vector/pattern/pattern.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Box } from '../../struct/box' -import { Vector } from '../vector/vector' -import { Vessel } from '../container/vessel' -import { Viewbox } from '../container/viewbox' -import { AttrOverride } from './override' -import { SVGPatternAttributes } from './types' - -@Pattern.register('Pattern') -@Pattern.mixin(Viewbox, AttrOverride) -export class Pattern extends Vessel { - bbox() { - return new Box() - } - - targets(): TVector[] { - return this.findTargets('fill') - } - - update(handler?: Pattern.Update) { - this.clear() - - if (typeof handler === 'function') { - handler.call(this, this) - } - - return this - } -} - -export interface Pattern extends Viewbox {} - -export namespace Pattern { - export type Update = (this: Pattern, pattern: Pattern) => void - export function create( - attrs?: Attributes | null, - ): Pattern - export function create( - size: number | string, - attrs?: Attributes | null, - ): Pattern - export function create( - size: number | string, - update: Update, - attrs?: Attributes | null, - ): Pattern - export function create( - width: number | string, - height: number | string, - attrs?: Attributes | null, - ): Pattern - export function create( - width: number | string, - height: number | string, - update: Update, - attrs?: Attributes | null, - ): Pattern - export function create( - width?: number | string | Attributes | null, - height?: number | string | Update | Attributes | null, - update?: Update | Attributes | null, - attrs?: Attributes | null, - ): Pattern - export function create( - width?: number | string | Attributes | null, - height?: number | string | Update | Attributes | null, - update?: Update | Attributes | null, - attrs?: Attributes | null, - ): Pattern { - const pattern = new Pattern() - const base = { - x: 0, - y: 0, - patternUnits: 'userSpaceOnUse', - } - - if (width != null) { - if (typeof width === 'object') { - pattern.attr(width) - } else if (height != null) { - if (typeof height === 'function') { - pattern.update(height).attr({ - ...base, - width, - height: width, - ...update, - }) - } else if (typeof height === 'object') { - pattern.attr({ - ...base, - width, - height: width, - ...height, - }) - } else if (update != null) { - if (typeof update === 'function') { - pattern.update(update).attr({ - ...base, - width, - height, - ...attrs, - }) - } else { - pattern.attr({ - ...base, - width, - height, - ...update, - }) - } - } else { - pattern.attr({ - ...base, - width, - height, - }) - } - } else { - pattern.attr({ - ...base, - width, - height: width, - }) - } - } - - return pattern - } -} diff --git a/packages/x6-vector/src/vector/pattern/types.ts b/packages/x6-vector/src/vector/pattern/types.ts deleted file mode 100644 index b16c97b88b7..00000000000 --- a/packages/x6-vector/src/vector/pattern/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGPatternAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes, - SVGXLinkAttributes { - x?: string | number - y?: string | number - width?: string | number - height?: string | number - href?: string - patternUnits?: 'userSpaceOnUse' | 'objectBoundingBox' - patternContentUnits?: 'userSpaceOnUse' | 'objectBoundingBox' - patternTransform?: string - preserveAspectRatio?: string - viewBox?: string -} diff --git a/packages/x6-vector/src/vector/poly/mixins.ts b/packages/x6-vector/src/vector/poly/mixins.ts deleted file mode 100644 index 4a859279731..00000000000 --- a/packages/x6-vector/src/vector/poly/mixins.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { applyMixins } from '../../util/mixin' -import { Poly } from '../poly/poly' -import { LineExtension as MarkerLineExtension } from '../marker/exts' - -declare module './poly' { - interface Poly - extends MarkerLineExtension {} -} - -applyMixins(Poly, MarkerLineExtension) diff --git a/packages/x6-vector/src/vector/poly/poly.ts b/packages/x6-vector/src/vector/poly/poly.ts deleted file mode 100644 index 086b12cbb7a..00000000000 --- a/packages/x6-vector/src/vector/poly/poly.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { PointArray } from '../../struct/point-array' -import { Size } from '../common/size' -import { Shape } from '../common/shape' - -export class Poly< - TSVGPolyElement extends SVGPolygonElement | SVGPolylineElement, -> extends Shape { - protected arr: PointArray | null - - x(): number - x(x: number | string): this - x(x?: number | string) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - } - - y(): number - y(y: number | string): this - y(y?: number | string) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - } - - width(): number - width(w: number | string): this - width(w?: number | string) { - return w == null ? this.bbox().width : this.size(w, this.bbox().height) - } - - height(): number - height(h: number | string): this - height(h?: number | string) { - return h == null ? this.bbox().height : this.size(this.bbox().width, h) - } - - move(x: number | string, y: number | string) { - return this.attr('points', this.toPointArray().move(x, y).toString()) - } - - plot(): [number, number][] - plot(d: string): this - plot(points: number[]): this - plot(points: [number, number][]): this - plot(points: string | number[] | [number, number][]): this - plot(d?: string | number[] | [number, number][]) { - if (d == null) { - return this.toArray() - } - - this.arr = null - - if (typeof d === 'string') { - this.attr('points', d) - } else { - this.arr = new PointArray(d) - this.attr('points', this.arr.toString()) - } - - return this - } - - size(width: string | number, height: string | number): this - size(width: string | number, height: string | number | null | undefined): this - size(width: string | number | null | undefined, height: string | number): this - size(width?: string | number | null, height?: string | number | null) { - const s = Size.normalize(this, width, height) - return this.attr( - 'points', - this.toPointArray().size(s.width, s.height).toString(), - ) - } - - toArray() { - return this.toPointArray().toArray() - } - - toPointArray() { - if (this.arr == null) { - this.arr = new PointArray(this.attr('points')) - } - return this.arr - } -} diff --git a/packages/x6-vector/src/vector/polygon/exts.ts b/packages/x6-vector/src/vector/polygon/exts.ts deleted file mode 100644 index 230b2b24d6f..00000000000 --- a/packages/x6-vector/src/vector/polygon/exts.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Base } from '../common/base' -import { Polygon } from './polygon' -import { SVGPolygonAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - polygon( - attrs?: Attributes | null, - ): Polygon - polygon( - points: string, - attrs?: Attributes | null, - ): Polygon - polygon( - points: [number, number][], - attrs?: Attributes | null, - ): Polygon - polygon( - points?: string | [number, number][] | Attributes | null, - attrs?: Attributes | null, - ) { - return Polygon.create(points, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/polygon/polygon.test.ts b/packages/x6-vector/src/vector/polygon/polygon.test.ts deleted file mode 100644 index e32e2fa8326..00000000000 --- a/packages/x6-vector/src/vector/polygon/polygon.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { G } from '../g/g' -import { SVG } from '../svg/svg' -import { Polygon } from './polygon' - -describe('Polygon', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(Polygon.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance with path string and attributes', () => { - const line = Polygon.create('1 2 3 4', { id: 'foo' }) - expect(line.toArray()).toEqual([ - [1, 2], - [3, 4], - ]) - expect(line.id()).toEqual('foo') - }) - - it('should create an instance from container', () => { - const g = new G() - const line = g.polygon('1 2 3 4', { id: 'foo' }) - expect(line.toArray()).toEqual([ - [1, 2], - [3, 4], - ]) - expect(line.id()).toEqual('foo') - }) - }) - - describe('x()', () => { - it(`should set the x value of the polygon and returns itself`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.polygon('1, 2, 3, 4') - expect(line.x(50)).toBe(line) - expect(line.bbox().x).toBe(50) - svg.remove() - }) - - it(`should get the x value of the polygon`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.polygon('1, 2, 3, 4') - expect(line.x(50).x()).toBe(50) - svg.remove() - }) - }) - - describe('y()', () => { - it(`should set the y value of the polygonand returns itself`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.polygon('1, 2, 3, 4') - expect(line.y(50)).toBe(line) - expect(line.bbox().y).toBe(50) - svg.remove() - }) - - it(`should get the y value of the polygon`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.polygon('1, 2, 3, 4') - expect(line.y(50).y()).toBe(50) - svg.remove() - }) - }) - - describe('width()', () => { - it(`should set the width of the polygonand returns itself`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.polygon('1, 2, 3, 4') - expect(line.width(50)).toBe(line) - expect(line.bbox().width).toBe(50) - svg.remove() - }) - - it(`should get the width of the polygon`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.polygon('1, 2, 3, 4') - expect(line.width(50).width()).toBe(50) - svg.remove() - }) - }) - - describe('height()', () => { - it(`should set the height of the polygonand returns itself`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.polygon('1, 2, 3, 4') - expect(line.height(50)).toBe(line) - expect(line.bbox().height).toBe(50) - svg.remove() - }) - - it(`should get the height of the polygon`, () => { - const svg = new SVG().appendTo(document.body) - const line = svg.polygon('1, 2, 3, 4') - expect(line.height(50).height()).toBe(50) - svg.remove() - }) - }) - - describe('plot()', () => { - it('should work as a getter', () => { - const line = Polygon.create('1 2 3 4') - expect(line.plot()).toEqual([ - [1, 2], - [3, 4], - ]) - }) - - it('should plot with a string', () => { - const path = Polygon.create('1 2 3 4') - path.plot('0,0 50,50') - expect(path.attr('points')).toEqual('0,0 50,50') - }) - - it('should plot with number array', () => { - const line = Polygon.create() - line.plot([0, 0, 50, 50]) - expect(line.attr('points')).toEqual('0,0 50,50') - }) - - it('should plot with point array', () => { - const line = Polygon.create() - line.plot([ - [0, 0], - [50, 50], - ]) - expect(line.attr('points')).toEqual('0,0 50,50') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/polygon/polygon.ts b/packages/x6-vector/src/vector/polygon/polygon.ts deleted file mode 100644 index fa13f7da568..00000000000 --- a/packages/x6-vector/src/vector/polygon/polygon.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Poly } from '../poly/poly' -import { SVGPolygonAttributes } from './types' - -@Polygon.register('Polygon') -export class Polygon extends Poly {} - -export namespace Polygon { - export function create( - attrs?: Attributes | null, - ): Polygon - export function create( - points: string, - attrs?: Attributes | null, - ): Polygon - export function create( - points: number[], - attrs?: Attributes | null, - ): Polygon - export function create( - points: [number, number][], - attrs?: Attributes | null, - ): Polygon - export function create( - points?: string | [number, number][] | Attributes | null, - attrs?: Attributes | null, - ): Polygon - export function create( - points?: string | [number, number][] | null | Attributes, - attrs?: Attributes, - ) { - const poly = new Polygon() - if (points != null) { - if (Array.isArray(points) || typeof points === 'string') { - poly.plot(points) - if (attrs) { - poly.attr(attrs) - } - } else { - poly.attr(points) - } - } - - return poly - } -} diff --git a/packages/x6-vector/src/vector/polygon/types.ts b/packages/x6-vector/src/vector/polygon/types.ts deleted file mode 100644 index fcb22761b11..00000000000 --- a/packages/x6-vector/src/vector/polygon/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGPolygonAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - points?: string - pathLength?: number - requiredExtensions?: string - systemLanguage?: string -} diff --git a/packages/x6-vector/src/vector/polyline/exts.ts b/packages/x6-vector/src/vector/polyline/exts.ts deleted file mode 100644 index 852a37e7850..00000000000 --- a/packages/x6-vector/src/vector/polyline/exts.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Base } from '../common/base' -import { Polyline } from './polyline' -import { SVGPolylineAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - polyline( - attrs?: Attributes | null, - ): Polyline - polyline( - points: string, - attrs?: Attributes | null, - ): Polyline - polyline( - points: [number, number][], - attrs?: Attributes | null, - ): Polyline - polyline( - points?: string | [number, number][] | Attributes | null, - attrs?: Attributes | null, - ) { - return Polyline.create(points, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/polyline/polyline.test.ts b/packages/x6-vector/src/vector/polyline/polyline.test.ts deleted file mode 100644 index 4baf92ef1ef..00000000000 --- a/packages/x6-vector/src/vector/polyline/polyline.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { G } from '../g/g' -import { Polyline } from './polyline' - -describe('Polyline', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(Polyline.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance with path string and attributes', () => { - const line = Polyline.create('1 2 3 4', { id: 'foo' }) - expect(line.toArray()).toEqual([ - [1, 2], - [3, 4], - ]) - expect(line.id()).toEqual('foo') - }) - - it('should create an instance from container', () => { - const g = new G() - const line = g.polyline('1 2 3 4', { id: 'foo' }) - expect(line.toArray()).toEqual([ - [1, 2], - [3, 4], - ]) - expect(line.id()).toEqual('foo') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/polyline/polyline.ts b/packages/x6-vector/src/vector/polyline/polyline.ts deleted file mode 100644 index 1e54eed526c..00000000000 --- a/packages/x6-vector/src/vector/polyline/polyline.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Poly } from '../poly/poly' -import { SVGPolylineAttributes } from './types' - -@Polyline.register('Polyline') -export class Polyline extends Poly {} - -export namespace Polyline { - export function create( - attrs?: Attributes | null, - ): Polyline - export function create( - points: string, - attrs?: Attributes | null, - ): Polyline - export function create( - points: number[], - attrs?: Attributes | null, - ): Polyline - export function create( - points: [number, number][], - attrs?: Attributes | null, - ): Polyline - export function create( - points?: string | [number, number][] | Attributes | null, - attrs?: Attributes | null, - ): Polyline - export function create( - points?: string | [number, number][] | Attributes | null, - attrs?: Attributes | null, - ) { - const poly = new Polyline() - if (points != null) { - if (Array.isArray(points) || typeof points === 'string') { - poly.plot(points) - if (attrs) { - poly.attr(attrs) - } - } else { - poly.attr(points) - } - } - - return poly - } -} diff --git a/packages/x6-vector/src/vector/polyline/types.ts b/packages/x6-vector/src/vector/polyline/types.ts deleted file mode 100644 index 920573a827e..00000000000 --- a/packages/x6-vector/src/vector/polyline/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGPolylineAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - points?: string - pathLength?: number - requiredExtensions?: string - systemLanguage?: string -} diff --git a/packages/x6-vector/src/vector/rect/exts.ts b/packages/x6-vector/src/vector/rect/exts.ts deleted file mode 100644 index 1919f6601aa..00000000000 --- a/packages/x6-vector/src/vector/rect/exts.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Base } from '../common/base' -import { Rect } from './rect' -import { SVGRectAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - rect(attrs?: Attributes | null): Rect - rect( - size: number | string, - attrs?: Attributes | null, - ): Rect - rect( - width: number | string, - height: string | number, - attrs?: Attributes | null, - ): Rect - rect( - width?: number | string | Attributes | null, - height?: number | string | Attributes | null, - attrs?: Attributes | null, - ) { - return Rect.create(width, height, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/rect/rect.test.ts b/packages/x6-vector/src/vector/rect/rect.test.ts deleted file mode 100644 index 92c90d8e145..00000000000 --- a/packages/x6-vector/src/vector/rect/rect.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { G } from '../g/g' -import { Rect } from './rect' - -describe('Rect', () => { - describe('constructor()', () => { - it('should create a rect with given attributes', () => { - expect(Rect.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create a rect with given width and height', () => { - const group = new G() - const rect = group.rect(100, 200) - expect(rect.attr(['width', 'height'])).toEqual({ - width: 100, - height: 200, - }) - expect(rect).toBeInstanceOf(Rect) - }) - - it('should create a rect with given width, height and attributes', () => { - const group = new G() - const rect = group.rect(100, 200, { id: 'foo' }) - expect(rect.attr(['width', 'height'])).toEqual({ - width: 100, - height: 200, - }) - expect(rect).toBeInstanceOf(Rect) - expect(rect.id()).toBe('foo') - }) - - it('should create a rect with given size', () => { - const group = new G() - const rect = group.rect(100) - expect(rect.attr(['width', 'height'])).toEqual({ - width: 100, - height: 100, - }) - expect(rect).toBeInstanceOf(Rect) - }) - - it('should create a rect with given size and attributes', () => { - const group = new G() - const rect = group.rect(100, { id: 'foo' }) - expect(rect.attr(['width', 'height'])).toEqual({ - width: 100, - height: 100, - }) - expect(rect).toBeInstanceOf(Rect) - expect(rect.id()).toBe('foo') - }) - }) - - describe('rx()', () => { - it('should call attribute with rx and return itself', () => { - const rect = new Rect() - const spy = spyOn(rect, 'attr').and.callThrough() - expect(rect.rx(50)).toBe(rect) - expect(spy).toHaveBeenCalledWith('rx', 50) - }) - }) - - describe('ry()', () => { - it('should call attribute with ry and return itself', () => { - const rect = new Rect() - const spy = spyOn(rect, 'attr').and.callThrough() - expect(rect.ry(50)).toBe(rect) - expect(spy).toHaveBeenCalledWith('ry', 50) - }) - }) - - describe('radius()', () => { - it('should set rx and ry on the rectangle', () => { - const rect = new Rect().radius(5, 10) - expect(rect.attr('rx')).toEqual(5) - expect(rect.attr('ry')).toEqual(10) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/rect/rect.ts b/packages/x6-vector/src/vector/rect/rect.ts deleted file mode 100644 index b0fb2090f2b..00000000000 --- a/packages/x6-vector/src/vector/rect/rect.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Shape } from '../common/shape' -import { SVGRectAttributes } from './types' - -@Rect.register('Rect') -export class Rect extends Shape { - rx(): number - rx(rx: string | number | null): this - rx(rx?: string | number | null) { - return this.attr('rx', rx) - } - - ry(): number - ry(ry: string | number | null): this - ry(ry?: string | number | null) { - return this.attr('ry', ry) - } - - radius(rx: string | number, ry: string | number = rx) { - return this.rx(rx).ry(ry) - } -} - -export namespace Rect { - export function create( - attrs?: Attributes | null, - ): Rect - export function create( - size: number | string, - attrs?: Attributes | null, - ): Rect - export function create( - width: number | string, - height: string | number, - attrs?: Attributes | null, - ): Rect - export function create( - width?: number | string | Attributes | null, - height?: number | string | Attributes | null, - attrs?: Attributes | null, - ): Rect - export function create( - width?: number | string | Attributes | null, - height?: number | string | Attributes | null, - attrs?: Attributes | null, - ) { - const rect = new Rect() - if (width == null) { - rect.size(0, 0) - } else if (typeof width === 'object') { - rect.size(0, 0).attr(width) - } else if (height != null && typeof height === 'object') { - rect.size(width, width).attr(height) - } else { - if (typeof height === 'undefined') { - rect.size(width, width) - } else { - rect.size(width, height) - } - - if (attrs) { - rect.attr(attrs) - } - } - - return rect - } -} diff --git a/packages/x6-vector/src/vector/rect/types.ts b/packages/x6-vector/src/vector/rect/types.ts deleted file mode 100644 index fc3952af021..00000000000 --- a/packages/x6-vector/src/vector/rect/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGRectAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - x?: string | number - y?: string | number - width?: string | number - height?: string | number - rx?: string | number - ry?: string | number - pathLength?: number -} diff --git a/packages/x6-vector/src/vector/script/types.ts b/packages/x6-vector/src/vector/script/types.ts deleted file mode 100644 index 7bafeca2a5e..00000000000 --- a/packages/x6-vector/src/vector/script/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, -} from '../types/attributes-core' - -export interface SVGScriptAttributes - extends SVGCoreAttributes, - SVGStyleAttributes, - SVGXLinkAttributes { - href?: string - crossorigin?: string - type?: string -} diff --git a/packages/x6-vector/src/vector/style/exts.ts b/packages/x6-vector/src/vector/style/exts.ts deleted file mode 100644 index 071a16d35ac..00000000000 --- a/packages/x6-vector/src/vector/style/exts.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Base } from '../common/base' -import { Style } from './style' - -export class ElementExtension< - TSVGElement extends SVGElement = SVGElement, -> extends Base { - style( - selector: string, - style?: Record, - ): Style { - return new Style().addRule(selector, style).appendTo(this) - } - - fontFace( - name: string, - source: string, - parameters: Record, - ): Style { - return new Style().addFont(name, source, parameters).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/style/style.test.ts b/packages/x6-vector/src/vector/style/style.test.ts deleted file mode 100644 index 556a2e30d7f..00000000000 --- a/packages/x6-vector/src/vector/style/style.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { G } from '../g/g' -import { Style } from './style' - -describe('Style', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(new Style({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create a style element in the container and adds a rule', () => { - const g = new G() - const style = g.style('#id', { fontSize: 15 }) - expect(style.node.textContent).toBe('#id {font-size: 15;}') - }) - - it('should create a style element in the container and adds a font-face rule', () => { - const g = new G() - const style = g.fontFace('fontName', 'url', { foo: 'bar' }) - expect(style.node.textContent).toBe( - '@font-face {font-family: fontName;src: url;foo: bar;}', - ) - }) - }) - - describe('addText()', () => { - it('should append a string to the current textContent and returns itself', () => { - const style = new Style() - expect(style.addText('foo').node.textContent).toBe('foo') - expect(style.addText('bar').node.textContent).toBe('foobar') - expect(style.addText('foobar')).toBe(style) - }) - - it('should append an empty string if nothing passed', () => { - const style = new Style() - expect(style.addText().node.textContent).toBe('') - }) - }) - - describe('addFont()', () => { - it('should add a font-face rule to load a custom font and returns itself', () => { - const style = new Style() - expect(style.addFont('fontName', 'url')).toBe(style) - expect(style.node.textContent).toBe( - '@font-face {font-family: fontName;src: url;}', - ) - }) - - it('should add extra parameters if wanted', () => { - const style = new Style() - style.addFont('fontName', 'url', { foo: 'bar' }) - expect(style.node.textContent).toBe( - '@font-face {font-family: fontName;src: url;foo: bar;}', - ) - }) - }) - - describe('addRule()', () => { - it('should add a css rule', () => { - const style = new Style() - expect(style.addRule('#id', { fontSize: 15 })).toBe(style) - expect(style.node.textContent).toBe('#id {font-size: 15;}') - }) - - it('should add only selector when no obj was given', () => { - const style = new Style() - style.addRule('#id') - expect(style.node.textContent).toBe('#id') - }) - - it('should add nothing if no selector was given', () => { - const style = new Style() - style.addRule() - expect(style.node.textContent).toBe('') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/style/style.ts b/packages/x6-vector/src/vector/style/style.ts deleted file mode 100644 index 5f7c312a5f2..00000000000 --- a/packages/x6-vector/src/vector/style/style.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Vector } from '../vector/vector' - -@Style.register('Style') -export class Style extends Vector { - addText(content = '') { - this.node.textContent += content - return this - } - - addFont( - name: string, - source: string, - parameters: Record = {}, - ) { - return this.addRule('@font-face', { - fontFamily: name, - src: source, - ...parameters, - }) - } - - addRule( - selector?: string, - object?: Record, - ) { - return this.addText(Style.cssRule(selector, object)) - } -} - -export namespace Style { - const unCamelCase = (s: string) => { - return s.replace(/([A-Z])/g, (m, g) => `-${g.toLowerCase()}`) - } - - export function cssRule( - selector?: string, - rule?: Record, - ) { - if (!selector) { - return '' - } - - if (!rule) { - return selector - } - - let ret = `${selector} {` - - Object.keys(rule).forEach((key) => { - ret += `${unCamelCase(key)}: ${rule[key]};` - }) - - ret += '}' - - return ret - } -} diff --git a/packages/x6-vector/src/vector/style/types.ts b/packages/x6-vector/src/vector/style/types.ts deleted file mode 100644 index 4b4deafab9b..00000000000 --- a/packages/x6-vector/src/vector/style/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes as StyleAttributes, -} from '../types/attributes-core' - -export interface SVGStyleAttributes - extends SVGCoreAttributes, - StyleAttributes { - type?: string - media?: string - title?: string -} diff --git a/packages/x6-vector/src/vector/svg/exts.ts b/packages/x6-vector/src/vector/svg/exts.ts deleted file mode 100644 index 5c0cab2a981..00000000000 --- a/packages/x6-vector/src/vector/svg/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { SVG } from './svg' -import { SVGSVGAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - nested(attrs?: Attributes) { - return SVG.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/svg/svg.test.ts b/packages/x6-vector/src/vector/svg/svg.test.ts deleted file mode 100644 index 5fd11aebecb..00000000000 --- a/packages/x6-vector/src/vector/svg/svg.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { namespaces } from '../../util/dom' -import { Defs } from '../defs/defs' -import { SVG } from './svg' - -describe('Svg', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(SVG.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should add namespaces on creation', () => { - const svg = new SVG() - expect(svg.attr('xmlns')).toBe(namespaces.svg) - expect(svg.attr('version')).toBe(1.1) - expect(svg.attr('xmlns:xlink')).toBe(namespaces.xlink) - }) - }) - - describe('defs()', () => { - it('should return the defs if its the root svg', () => { - const svg = new SVG() - const defs = new Defs().addTo(svg) - expect(svg.defs()).toBe(defs) - }) - - it('should return the defs if its not the root svg', () => { - const svg = new SVG() - const defs = new Defs().addTo(svg) - const nested = new SVG().addTo(svg) - expect(nested.defs()).toBe(defs) - }) - - it('should create the defs if not found', () => { - const svg = new SVG() - expect(svg.findOne('defs')).toBe(null) - const defs = svg.defs() - expect(svg.findOne('defs')).toBe(defs) - }) - }) - - describe('namespace()', () => { - it('should create the namespace attributes on the svg', () => { - const svg = new SVG() - expect(svg.attr('xmlns')).toBe(namespaces.svg) - expect(svg.attr('version')).toBe(1.1) - expect(svg.attr('xmlns:xlink')).toBe(namespaces.xlink) - }) - - it('should create the namespace attributes on the root svg', () => { - const svg = new SVG() - const nested = svg.nested() - - nested.namespace() - - expect(svg.attr('xmlns')).toBe(namespaces.svg) - expect(svg.attr('version')).toBe(1.1) - expect(svg.attr('xmlns:xlink')).toBe(namespaces.xlink) - }) - }) - - describe('isRoot()', () => { - it('should return true if svg is the root svg', () => { - const svg = new SVG().addTo(document.body) - expect(svg.isRoot()).toBe(true) - svg.remove() - }) - - it('should return true if its detached from the dom', () => { - const svg = new SVG() - expect(svg.isRoot()).toBe(true) - }) - - it('should return false if its the child of a document-fragment', () => { - const fragment = document.createDocumentFragment() - const svg = new SVG().addTo(fragment) - expect(svg.isRoot()).toBe(false) - }) - - it('should return false if its a child of another svg element', () => { - const svg = new SVG() - const nested = new SVG().addTo(svg) - expect(nested.isRoot()).toBe(false) - }) - }) - - describe('removeNamespace()', () => { - it('should remove the namespace attributes from the svg element', () => { - const svg = new SVG() - - svg.removeNamespace() - - expect(svg.attr('xmlns')).toBeUndefined() - expect(svg.attr('version')).toBeUndefined() - expect(svg.attr('xmlns:xlink')).toBeUndefined() - }) - }) -}) diff --git a/packages/x6-vector/src/vector/svg/svg.ts b/packages/x6-vector/src/vector/svg/svg.ts deleted file mode 100644 index fb6e2f85a49..00000000000 --- a/packages/x6-vector/src/vector/svg/svg.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Global } from '../../global' -import { namespaces } from '../../util/dom' -import { Adopter } from '../../dom/common/adopter' -import { Container } from '../container/container' -import { Viewbox } from '../container/viewbox' -import { Defs } from '../defs/defs' -import { SVGSVGAttributes } from './types' - -@SVG.mixin(Viewbox) -@SVG.register('Svg') -export class SVG extends Container { - constructor() - constructor(attrs: SVGSVGAttributes | null) - constructor(node: SVGSVGElement | null, attrs?: SVGSVGAttributes | null) - constructor( - node?: SVGSVGElement | SVGSVGAttributes | null, - attrs?: SVGSVGAttributes | null, - ) - constructor( - node?: SVGSVGElement | SVGSVGAttributes | null, - attrs?: SVGSVGAttributes | null, - ) { - super(node, attrs) - this.namespace() - } - - isRoot() { - const parentNode = this.node.parentNode - return ( - !parentNode || - (!(parentNode instanceof Global.window.SVGElement) && - parentNode.nodeName !== '#document-fragment') - ) - } - - root() { - return this.isRoot() ? this : super.root() - } - - defs(): Defs { - if (!this.isRoot()) { - const root = this.root() - if (root) { - return root.defs() - } - } - - const defs = this.node.querySelector('defs') - if (defs) { - return Adopter.adopt(defs) - } - - return this.put(new Defs()) - } - - namespace() { - if (!this.isRoot()) { - const root = this.root() - if (root) { - root.namespace() - return this - } - } - - return this.attr({ xmlns: namespaces.svg, version: '1.1' }).attr( - 'xmlns:xlink', - namespaces.xlink, - ) - } - - removeNamespace() { - return this.attr({ xmlns: null, version: null }).attr('xmlns:xlink', null) - } -} - -export interface SVG extends Viewbox {} - -export namespace SVG { - export function create( - attrs?: Attributes | null, - ) { - const svg = new SVG() - if (attrs) { - svg.attr(attrs) - } - return svg - } -} diff --git a/packages/x6-vector/src/vector/svg/types.ts b/packages/x6-vector/src/vector/svg/types.ts deleted file mode 100644 index 393b1c023f8..00000000000 --- a/packages/x6-vector/src/vector/svg/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGSVGAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - x?: string | number - y?: string | number - width?: string | number - height?: string | number - preserveAspectRatio?: string - version?: number - viewBox?: string - baseProfile?: string - contentScriptType?: string - contentStyleType?: string -} diff --git a/packages/x6-vector/src/vector/switch/exts.ts b/packages/x6-vector/src/vector/switch/exts.ts deleted file mode 100644 index f5e1a2a3cb6..00000000000 --- a/packages/x6-vector/src/vector/switch/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { Switch } from './switch' -import { SVGSwitchAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - switch(attrs?: Attributes | null) { - return Switch.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/switch/switch.test.ts b/packages/x6-vector/src/vector/switch/switch.test.ts deleted file mode 100644 index f04fc3a1bca..00000000000 --- a/packages/x6-vector/src/vector/switch/switch.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { SVG } from '../svg/svg' -import { Switch } from './switch' - -describe('Switch', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(Switch.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from container', () => { - const svg = new SVG() - expect(svg.switch()).toBeInstanceOf(Switch) - }) - - it('should create an instance from container with given attributes', () => { - const svg = new SVG() - const elem = svg.switch({ id: 'foo' }) - expect(elem.id()).toEqual('foo') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/switch/switch.ts b/packages/x6-vector/src/vector/switch/switch.ts deleted file mode 100644 index 33c7b15ff47..00000000000 --- a/packages/x6-vector/src/vector/switch/switch.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Container } from '../container/container' -import { SVGSwitchAttributes } from './types' - -@Switch.register('Switch') -export class Switch extends Container {} - -export namespace Switch { - export function create( - attrs?: Attributes | null, - ) { - const elem = new Switch() - if (attrs) { - elem.attr(attrs) - } - return elem - } -} diff --git a/packages/x6-vector/src/vector/switch/types.ts b/packages/x6-vector/src/vector/switch/types.ts deleted file mode 100644 index c2e9b550914..00000000000 --- a/packages/x6-vector/src/vector/switch/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - SVGCoreAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGSwitchAttributes - extends SVGCoreAttributes, - SVGConditionalProcessingAttributes, - SVGStyleAttributes, - SVGPresentationAttributes {} diff --git a/packages/x6-vector/src/vector/symbol/exts.ts b/packages/x6-vector/src/vector/symbol/exts.ts deleted file mode 100644 index a60fab6ed34..00000000000 --- a/packages/x6-vector/src/vector/symbol/exts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from '../common/base' -import { Symbol } from './symbol' -import { SVGSymbolAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - symbol(attrs?: Attributes) { - return Symbol.create(attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/symbol/symbol.test.ts b/packages/x6-vector/src/vector/symbol/symbol.test.ts deleted file mode 100644 index b6cbb106755..00000000000 --- a/packages/x6-vector/src/vector/symbol/symbol.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { G } from '../g/g' -import { Symbol } from './symbol' - -describe('Symbol', () => { - describe('constructor()', () => { - it('should create a new object of type Symbol', () => { - expect(Symbol.create()).toBeInstanceOf(Symbol) - }) - - it('should create an instance with given attributes', () => { - expect(Symbol.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from container element', () => { - expect(new G().symbol()).toBeInstanceOf(Symbol) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/symbol/symbol.ts b/packages/x6-vector/src/vector/symbol/symbol.ts deleted file mode 100644 index ba3f8354be9..00000000000 --- a/packages/x6-vector/src/vector/symbol/symbol.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Vessel } from '../container/vessel' -import { Viewbox } from '../container/viewbox' -import { SVGSymbolAttributes } from './types' - -@Symbol.mixin(Viewbox) -@Symbol.register('Symbol') -export class Symbol extends Vessel {} - -export interface Symbol extends Viewbox {} - -export namespace Symbol { - export function create( - attrs?: Attributes | null, - ) { - const symbol = new Symbol() - if (attrs) { - symbol.attr(attrs) - } - return symbol - } -} diff --git a/packages/x6-vector/src/vector/symbol/types.ts b/packages/x6-vector/src/vector/symbol/types.ts deleted file mode 100644 index d8681d5c6bc..00000000000 --- a/packages/x6-vector/src/vector/symbol/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, -} from '../types/attributes-core' - -export interface SVGSymbolAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes { - x?: string | number - y?: string | number - width?: string | number - height?: string | number - viewBox?: string - refX?: string | number - refY?: string | number - preserveAspectRatio?: string -} diff --git a/packages/x6-vector/src/vector/text/base.test.ts b/packages/x6-vector/src/vector/text/base.test.ts deleted file mode 100644 index f8d2cdf42d0..00000000000 --- a/packages/x6-vector/src/vector/text/base.test.ts +++ /dev/null @@ -1,402 +0,0 @@ -import { Box } from '../../struct/box' -import { SVG } from '../svg/svg' -import { TSpan } from '../tspan/tspan' -import { Text } from './text' - -describe('TextBase', () => { - let svg: SVG - let text: Text - let tspan: TSpan - - beforeEach(() => { - svg = new SVG().appendTo(document.body) - text = svg.text('Hello World\nIn two lines') - tspan = text.get(0)! - }) - - afterEach(() => { - svg.remove() - }) - - describe('x()', () => { - it('should return the value of x on a text', () => { - expect(text.x(0).x()).toEqual(0) - }) - - it('should set the x value of the bbox on a text', () => { - text.x(123) - expect(text.bbox().x).toEqual(123) - }) - - it('should sets the value of all lines', () => { - text.x(200) - text.eachChild((child: TSpan) => { - expect(child.x()).toEqual(text.x()) - }) - }) - - it('should return the value of x on a tspan', () => { - expect(tspan.x(10).x()).toEqual(10) - }) - - it('should set the x value of the bbox on a tspan', () => { - tspan.x(123) - expect(tspan.bbox().x).toEqual(123) - }) - }) - - describe('y()', () => { - it('should return the value of y on a text', () => { - expect(text.y(0).y()).toEqual(0) - }) - - it('should set the y value of the bbox on a text', () => { - text.y(123) - expect(text.bbox().y).toEqual(123) - }) - - it('should set the y position of first line', () => { - text.y(200) - expect(text.firstChild()!.y()).toEqual(text.y()) - }) - - it('should return the value of y on a tspan', () => { - expect(tspan.y(10).y()).toEqual(10) - }) - - it('should set the y value of the bbox on a tspan', () => { - tspan.y(123) - expect(tspan.bbox().y).toEqual(123) - }) - }) - - describe('move()', () => { - it('should call x() and y() with parameters on text', () => { - const spyX = spyOn(text, 'x').and.callThrough() - const spyY = spyOn(text, 'y').and.callThrough() - const box = new Box() - text.move(1, 2, box) - expect(spyX).toHaveBeenCalledWith(1, box) - expect(spyY).toHaveBeenCalledWith(2, box) - }) - - it('should call x() and y() with parameters on tspan', () => { - const spyX = spyOn(tspan, 'x').and.callThrough() - const spyY = spyOn(tspan, 'y').and.callThrough() - const box = new Box() - tspan.move(1, 2, box) - expect(spyX).toHaveBeenCalledWith(1, box) - expect(spyY).toHaveBeenCalledWith(2, box) - }) - }) - - describe('ax()', () => { - it('should set the value of x with a percent value with Text', () => { - text.ax('40%') - expect(text.node.getAttribute('x')).toEqual('40%') - }) - - it('should return the value of x when x is a percentual value with Text', () => { - expect(text.ax('40%').ax()).toEqual('40%') - }) - - it('should set the value of x with a percent value with Tspan', () => { - tspan.ax('40%') - expect(tspan.node.getAttribute('x')).toEqual('40%') - }) - - it('should return the value of x when x is a percentual value with Tspan', () => { - tspan.ax('40%') - expect(tspan.ax()).toEqual('40%') - }) - }) - - describe('ay()', () => { - it('should set the value of y with a percent value with Text', () => { - text.ay('40%') - expect(text.node.getAttribute('y')).toEqual('40%') - }) - - it('should return the value of y when y is a percentual value with Tspan', () => { - expect(text.ay('45%').ay()).toEqual('45%') - }) - - it('should set the value of y with a percent value with Text', () => { - tspan.ay('40%') - expect(tspan.node.getAttribute('y')).toEqual('40%') - }) - - it('should return the value of y when y is a percentual value with Tspan', () => { - tspan.ay('40%') - expect(tspan.ay()).toEqual('40%') - }) - }) - - describe('amove()', () => { - it('should call ax() and ay() with parameters on text', () => { - const spyX = spyOn(text, 'ax').and.callThrough() - const spyY = spyOn(text, 'ay').and.callThrough() - text.amove(1, 2) - expect(spyX).toHaveBeenCalledWith(1) - expect(spyY).toHaveBeenCalledWith(2) - }) - - it('should call ax() and ay() with parameters on tspan', () => { - const spyX = spyOn(tspan, 'ax').and.callThrough() - const spyY = spyOn(tspan, 'ay').and.callThrough() - tspan.amove(1, 2) - expect(spyX).toHaveBeenCalledWith(1) - expect(spyY).toHaveBeenCalledWith(2) - }) - }) - - describe('cx()', () => { - it('should return the value of cx on Text', () => { - const box = text.bbox() - expect(text.cx()).toBeCloseTo(box.x + box.width / 2) - }) - - it('should set the value of cx on Text', () => { - text.cx(123) - const box = text.bbox() - expect(box.cx).toBeCloseTo(box.x + box.width / 2) - }) - - it('should return the value of cx on Tspan', () => { - const box = tspan.bbox() - expect(tspan.cx()).toBeCloseTo(box.x + box.width / 2) - }) - - it('should set the value of cx on Tspan', () => { - tspan.cx(123) - const box = tspan.bbox() - expect(box.cx).toBeCloseTo(box.x + box.width / 2) - }) - }) - - describe('cy()', () => { - it('should return the value of cy on Tspan', () => { - const box = tspan.bbox() - expect(tspan.cy()).toEqual(box.cy) - }) - - it('should set the value of cy on Tspan', () => { - tspan.cy(345) - const box = tspan.bbox() - expect(Math.round(box.cy * 10) / 10).toEqual(345) - }) - - it('should return the value of cy on Tspan', () => { - const box = tspan.bbox() - expect(tspan.cy()).toEqual(box.cy) - }) - - it('should set the value of cy on Tspan', () => { - tspan.cy(345) - const box = tspan.bbox() - expect(Math.round(box.cy * 10) / 10).toEqual(345) - }) - }) - - describe('center()', () => { - it('should call cx() and cy() with parameters on Text', () => { - const spyX = spyOn(text, 'cx').and.callThrough() - const spyY = spyOn(text, 'cy').and.callThrough() - const box = new Box() - text.center(1, 2, box) - expect(spyX).toHaveBeenCalledWith(1, box) - expect(spyY).toHaveBeenCalledWith(2, box) - }) - - it('should call cx() and cy() with parameters on Tspan', () => { - const spyX = spyOn(tspan, 'cx').and.callThrough() - const spyY = spyOn(tspan, 'cy').and.callThrough() - const box = new Box() - tspan.center(1, 2, box) - expect(spyX).toHaveBeenCalledWith(1, box) - expect(spyY).toHaveBeenCalledWith(2, box) - }) - }) - - describe('length()', () => { - it('should return the text length as number', () => { - expect(Number.isFinite(text.length())).toBeTrue() - }) - - it('should get the total length of text', () => { - text.text((t) => { - t.tspan('The first.') - t.tspan('The second.') - t.tspan('The third.') - }) - - expect(text.length()).toBeCloseTo( - text.get(0)!.length() + - text.get(1)!.length() + - text.get(2)!.length(), - 3, - ) - }) - - it('should get total length of tspan', () => { - tspan.text((add) => { - add.tspan('The first.') - add.tspan('The second.') - add.tspan('The third.') - }) - - expect(tspan.length()).toBeCloseTo( - tspan.get(0)!.length() + - tspan.get(1)!.length() + - tspan.get(2)!.length(), - 3, - ) - }) - }) - - describe('build()', () => { - it('should enable adding multiple plain text nodes when given true for Text', () => { - text.clear().build(true) - text.plain('A great piece!') - text.plain('Another great piece!') - expect((text.node.childNodes[0] as any).data).toBe('A great piece!') - expect((text.node.childNodes[1] as any).data).toBe('Another great piece!') - }) - - it('should enable adding multiple tspan nodes when given true for Text', () => { - text.clear().build(true) - text.tspan('A great piece!') - text.tspan('Another great piece!') - expect((text.node.childNodes[0].childNodes[0] as any).data).toBe( - 'A great piece!', - ) - expect((text.node.childNodes[1].childNodes[0] as any).data).toBe( - 'Another great piece!', - ) - }) - - it('should disable adding multiple plain text nodes when given false for Text', () => { - text.clear().build(true) - text.plain('A great piece!') - text.build(false).plain('Another great piece!') - expect((text.node.childNodes[0] as any).data).toBe('Another great piece!') - expect(text.node.childNodes[1]).toBeUndefined() - }) - - it('should disable adding multiple tspan nodes when given false for Text', () => { - text.clear().build(true) - text.tspan('A great piece!') - text.build(false).tspan('Another great piece!') - expect((text.node.childNodes[0].childNodes[0] as any).data).toBe( - 'Another great piece!', - ) - expect(text.node.childNodes[1]).toBeUndefined() - }) - - it('should enable adding multiple plain text nodes when given true for Tspan', () => { - tspan.clear().build(true) - tspan.plain('A great piece!') - tspan.plain('Another great piece!') - expect((tspan.node.childNodes[0] as any).data).toBe('A great piece!') - expect((tspan.node.childNodes[1] as any).data).toBe( - 'Another great piece!', - ) - }) - - it('should enable adding multiple text nodes when given true for Tspan', () => { - tspan.clear().build(true) - tspan.tspan('A great piece!') - tspan.tspan('Another great piece!') - expect((tspan.node.childNodes[0].childNodes[0] as any).data).toBe( - 'A great piece!', - ) - expect((tspan.node.childNodes[1].childNodes[0] as any).data).toBe( - 'Another great piece!', - ) - }) - - it('should disable adding multiple plain text nodes when given false for Tspan', () => { - tspan.clear().build(true) - tspan.plain('A great piece!') - tspan.build(false).plain('Another great piece!') - expect((tspan.node.childNodes[0] as any).data).toBe( - 'Another great piece!', - ) - expect(tspan.node.childNodes[1]).toBeUndefined() - }) - - it('should disable adding multiple tspan nodes when given false for Tspan', () => { - tspan.clear().build(true) - tspan.tspan('A great piece!') - tspan.build(false).tspan('Another great piece!') - expect((tspan.node.childNodes[0].childNodes[0] as any).data).toBe( - 'Another great piece!', - ) - expect(tspan.node.childNodes[1]).toBeUndefined() - }) - }) - - describe('plain()', () => { - it('should add content without a tspan with Text', () => { - text.plain('It is a bear!') - expect(text.node.childNodes[0].nodeType).toEqual(3) - expect((text.node.childNodes[0] as any).data).toEqual('It is a bear!') - }) - - it('should clear content before adding new content with Text', () => { - text.plain('It is not a bear!') - expect(text.node.childNodes.length).toEqual(1) - expect((text.node.childNodes[0] as any).data).toEqual('It is not a bear!') - }) - - it('should restore the content from the dom with Text', () => { - text.plain('Just plain text!') - expect(text.text()).toEqual('Just plain text!') - }) - - it('should add content without a tspan with Tspan', () => { - tspan.plain('It is a bear!') - expect(tspan.node.childNodes[0].nodeType).toEqual(3) - expect((tspan.node.childNodes[0] as any).data).toEqual('It is a bear!') - }) - - it('should clear content before adding new content with Tspan', () => { - tspan.plain('It is not a bear!') - expect(tspan.node.childNodes.length).toEqual(1) - expect((tspan.node.childNodes[0] as any).data).toEqual( - 'It is not a bear!', - ) - }) - - it('should restore the content from the dom with Tspan', () => { - const tspan = new TSpan().plain('Just plain text!') - expect(tspan.text()).toEqual('Just plain text!') - }) - - it('should create plain text with container', () => { - const text1 = svg.plain('Just plain text!', { id: 'foo' }) - const text2 = svg.plain({ id: 'bar' }) - const text3 = svg.plain() - - expect(text1.text()).toEqual('Just plain text!') - expect(text1.id()).toEqual('foo') - - expect(text2.id()).toEqual('bar') - - expect(text3).toBeInstanceOf(Text) - }) - - it('should create text with container', () => { - const text1 = svg.text('Just text!', { id: 'foo' }) - const text2 = svg.text({ id: 'bar' }) - const text3 = svg.text() - - expect(text1.text()).toEqual('Just text!') - expect(text1.id()).toEqual('foo') - - expect(text2.id()).toEqual('bar') - - expect(text3).toBeInstanceOf(Text) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/text/base.ts b/packages/x6-vector/src/vector/text/base.ts deleted file mode 100644 index fc73cad00a1..00000000000 --- a/packages/x6-vector/src/vector/text/base.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Global } from '../../global' -import { Box } from '../../struct/box' -import { UnitNumber } from '../../struct/unit-number' -import { Shape } from '../common/shape' - -export class TextBase< - TSVGTextElement extends SVGTextElement | SVGTSpanElement | SVGTextPathElement, -> extends Shape { - protected building = false - - build(building = false) { - this.building = building - return this - } - - plain(text: string) { - if (this.building === false) { - this.clear() - } - - this.node.append(Global.document.createTextNode(text)) - - return this - } - - length() { - return this.node.getComputedTextLength() - } - - x(): number - x(x: number | string, box?: Box): this - x(x?: number | string, box = this.bbox()) { - if (x == null) { - return box.x - } - - const old = +this.attr('x') || 0 - return this.attr('x', old + UnitNumber.toNumber(x) - box.x) - } - - y(): number - y(y: number | string, box?: Box): this - y(y?: number | string, box = this.bbox()) { - if (y == null) { - return box.y - } - - const old = +this.attr('y') || 0 - return this.attr('y', old + UnitNumber.toNumber(y) - box.y) - } - - move(x: number | string, y: number | string, box = this.bbox()) { - return this.x(x, box).y(y, box) - } - - cx(): number - cx(x: number | string, box?: Box): this - cx(x?: number | string, box = this.bbox()) { - if (x == null) { - return box.cx - } - - return this.attr('x', +this.attr('x') + UnitNumber.toNumber(x) - box.cx) - } - - cy(): number - cy(y: number | string, box?: Box): this - cy(y?: number | string, box = this.bbox()) { - if (y == null) { - return box.cy - } - - return this.attr('y', +this.attr('y') + UnitNumber.toNumber(y) - box.cy) - } - - center(x: number | string, y: number | string, box = this.bbox()) { - return this.cx(x, box).cy(y, box) - } - - ax(): number | string - ax(x: number | string): this - ax(x?: number | string) { - return this.attr('x', x) - } - - ay(): number | string - ay(y: number | string): this - ay(y?: number | string) { - return this.attr('y', y) - } - - amove(x: number | string, y: number | string) { - return this.ax(x).ay(y) - } -} diff --git a/packages/x6-vector/src/vector/text/exts.ts b/packages/x6-vector/src/vector/text/exts.ts deleted file mode 100644 index b6c2927a5be..00000000000 --- a/packages/x6-vector/src/vector/text/exts.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Base } from '../common/base' -import { Text } from './text' -import { SVGTextAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - text(attrs?: Attributes | null): Text - text( - text: string, - attrs?: Attributes | null, - ): Text - text( - text?: string | Attributes | null, - attrs?: Attributes | null, - ) { - const instance = new Text().appendTo(this) - if (text) { - if (typeof text === 'string') { - instance.text(text) - if (attrs) { - instance.attr(attrs) - } - } else { - instance.attr(text) - } - } - return instance - } - - plain(attrs?: Attributes | null): Text - plain( - text: string, - attrs?: Attributes | null, - ): Text - plain( - text?: string | Attributes | null, - attrs?: Attributes, - ) { - const instance = new Text().appendTo(this) - if (text) { - if (typeof text === 'string') { - instance.plain(text) - if (attrs) { - instance.attr(attrs) - } - } else { - instance.attr(text) - } - } - return instance - } -} diff --git a/packages/x6-vector/src/vector/text/mixins.ts b/packages/x6-vector/src/vector/text/mixins.ts deleted file mode 100644 index 90df28a2e0e..00000000000 --- a/packages/x6-vector/src/vector/text/mixins.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { applyMixins } from '../../util/mixin' -import { TextExtension as TextpathExtension } from '../textpath/exts' -import { TextExtension as TspanExtension } from '../tspan/exts' -import { Text } from './text' - -declare module './text' { - interface Text - extends TextpathExtension, - TspanExtension {} -} - -applyMixins(Text, TspanExtension, TextpathExtension) diff --git a/packages/x6-vector/src/vector/text/overrides.ts b/packages/x6-vector/src/vector/text/overrides.ts deleted file mode 100644 index 952265c41c3..00000000000 --- a/packages/x6-vector/src/vector/text/overrides.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { UnitNumber } from '../../struct/unit-number' -import { AttributesBase } from '../../dom/attributes' - -export class Overrides extends AttributesBase { - attr(attr: any, value: any) { - if (attr === 'leading') { - if (typeof value === 'undefined') { - return this.leading() - } - this.leading(value) - } - - const ret = super.attr(attr, value) - - if (typeof value !== 'undefined') { - if (attr === 'font-size' || attr === 'x') { - this.rebuild() - } - } - - return ret - } -} - -export interface Overrides extends Overrides.Depends {} - -export namespace Overrides { - export interface Depends { - leading(): number - leading(value: UnitNumber.Raw): this - rebuild(rebuilding?: boolean): this - } -} diff --git a/packages/x6-vector/src/vector/text/text.test.ts b/packages/x6-vector/src/vector/text/text.test.ts deleted file mode 100644 index cb1f6b6ad7c..00000000000 --- a/packages/x6-vector/src/vector/text/text.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Path } from '../path/path' -import { SVG } from '../svg/svg' -import { TextPath } from '../textpath/textpath' -import { TSpan } from '../tspan/tspan' -import { Text } from './text' - -describe('Text', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(new Text({ id: 'foo' }).id()).toBe('foo') - }) - }) - - describe('text()', () => { - it('should set the text content of the tspan and return itself', () => { - const text = new Text() - expect(text.text('Hello World')).toBe(text) - expect(text.node.textContent).toBe('Hello World') - }) - - it('should creates tspans for every line', () => { - const text = new Text().text('Hello World\nHow is it\ngoing') - expect(text.children().length).toBe(3) - expect(text.get(0)!.node.textContent).toBe('Hello World') - expect(text.get(1)!.node.textContent).toBe('How is it') - expect(text.get(2)!.node.textContent).toBe('going') - }) - - it('should increase dy after empty line', () => { - const svg = new SVG() - const text = svg.text('Hello World\n\nHow is it\ngoing') - expect(text.children().length).toBe(4) - expect(text.get(0)!.node.textContent).toBe('Hello World') - expect(text.get(1)!.node.textContent).toBe('') - expect(text.get(2)!.node.textContent).toBe('How is it') - expect(text.get(3)!.node.textContent).toBe('going') - expect(text.get(2)!.dy()).toBe(text.get(3)!.dy() * 2) - }) - - it('should return the correct text with newlines', () => { - const text = new Text().text('Hello World\nHow is it\ngoing') - expect(text.text()).toBe('Hello World\nHow is it\ngoing') - }) - - it('should return the correct text with newlines and skips textPaths', () => { - const path = new Path() - const text = new Text() - const textPath = text.text('Hello World\nHow is it\ngoing').path(path) - textPath.eachChild((child) => { - child.addTo(text) - }) - text.add(new TextPath(), 3) - expect(text.text()).toBe('Hello World\nHow is it\ngoing') - }) - - it('should execute passed update function', () => { - const text = new Text() - text.text((t) => { - t.tspan('Hello World').newLine() - t.tspan('How is it').newLine() - t.tspan('going').newLine() - expect(t).toBe(text) - }) - expect(text.text()).toBe('Hello World\nHow is it\ngoing') - }) - - it('should trigger rebuild', () => { - const text = new Text() - const spy = spyOn(text, 'rebuild') - text.text('foo') - expect(spy).toHaveBeenCalled() - }) - }) - - describe('leading()', () => { - it('should return the leading value of the text', () => { - const text = new Text() - expect(text.leading()).toEqual(1.3) - }) - - it('should set the leading value of the text', () => { - const text = new Text() - expect(text.leading(1.5).leading()).toBe(1.5) - }) - - it('should get the leading value by `attr`', () => { - const text = new Text() - expect(text.attr('leading')).toBe(1.3) - }) - - it('should set the leading value by `attr`', () => { - const text = new Text() - expect(text.attr('leading', 1.5).leading()).toBe(1.5) - }) - }) - - describe('rebuild()', () => { - it('should rebuild the text', () => { - const svg = new SVG().appendTo(document.body) - const text = new Text().addTo(svg) - text.text((t) => { - t.tspan('Hello World').newLine() - t.tspan('How is it').newLine() - t.tspan('going').newLine() - }) - - const dy = text.get(1)!.dy() - text.leading(1.7) - expect(dy).not.toBe(text.get(1)!.dy()) - - svg.remove() - }) - - it('should not rebuild the text', () => { - const svg = new SVG().appendTo(document.body) - const text = new Text().addTo(svg) - text.text((t) => { - t.tspan('Hello World').newLine() - t.tspan('How is it').newLine() - t.tspan('going').newLine() - }) - - const dy = text.get(1)!.dy() - text.rebuild(false) - text.leading(1.7) - expect(dy).toBe(text.get(1)!.dy()) - - svg.remove() - }) - }) -}) diff --git a/packages/x6-vector/src/vector/text/text.ts b/packages/x6-vector/src/vector/text/text.ts deleted file mode 100644 index ff459babcca..00000000000 --- a/packages/x6-vector/src/vector/text/text.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { UnitNumber } from '../../struct/unit-number' -import { Adopter } from '../../dom/common/adopter' -import { TSpan } from '../tspan/tspan' -import { TextBase } from './base' -import { Overrides } from './overrides' -import { getFontSize } from './util' - -@Text.mixin(Overrides) -@Text.register('Text') -export class Text< - TSVGTextElement extends - | SVGTextElement - | SVGTextPathElement = SVGTextElement, - > - extends TextBase - implements Overrides.Depends -{ - public affixes: Record & { - leading: number - } - - protected rebuilding = true - - restoreAffix() { - super.restoreAffix() - if (this.leading() == null) { - this.leading(1.3) - } - return this - } - - leading(): number - leading(value: UnitNumber.Raw): this - leading(value?: UnitNumber.Raw) { - if (value == null) { - return this.affix('leading') - } - - this.affix('leading', UnitNumber.create(value).valueOf()) - return this.rebuild() - } - - rebuild(rebuilding?: boolean) { - if (typeof rebuilding === 'boolean') { - this.rebuilding = rebuilding - } - - // define position of all lines - if (this.rebuilding) { - let blankLineOffset = 0 - const leading = this.leading() - this.eachChild((child, index) => { - const dy = leading * getFontSize(child.node) - if (child.affix('newLined')) { - child.attr('x', this.attr('x') as string) - - if (child.text() === '\n') { - blankLineOffset += dy - } else { - child.attr('dy', index ? dy + blankLineOffset : 0) - blankLineOffset = 0 - } - } - }) - - this.trigger('rebuild') - } - - return this - } - - text(): string - text(text: string | ((this: Text, t: Text) => void)): this - text(text?: string | ((this: Text, t: Text) => void)) { - // getter - if (text === undefined) { - const children = this.node.childNodes - let firstLine = 0 - let content = '' - - for (let index = 0, l = children.length; index < l; index += 1) { - // skip textPaths - they are no lines - const child = children[index] - if (child.nodeName === 'textPath') { - if (index === 0) { - firstLine = 1 - } - continue - } - - // add newline if its not the first child and newLined is set to true - if ( - index !== firstLine && - child.nodeType !== 3 && - Adopter.adopt(child).affix('newLined') === true - ) { - content += '\n' - } - - // add content of this node - content += children[index].textContent - } - - return content - } - - this.clear().build(true) - - if (typeof text === 'function') { - text.call(this, this) - } else { - const lines = `${text}`.split('\n') - lines.forEach((line) => this.newLine(line)) - } - - return this.build(false).rebuild() - } - - newLine(text = '') { - return this.tspan(text).newLine() - } -} diff --git a/packages/x6-vector/src/vector/text/types.ts b/packages/x6-vector/src/vector/text/types.ts deleted file mode 100644 index ce657f199ad..00000000000 --- a/packages/x6-vector/src/vector/text/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGTextAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - x?: string | number - y?: string | number - dx?: string | number - dy?: string | number - rotate?: string | number - textLength?: string | number - lengthAdjust?: 'spacing' | 'spacingAndGlyphs' -} diff --git a/packages/x6-vector/src/vector/text/util.ts b/packages/x6-vector/src/vector/text/util.ts deleted file mode 100644 index 21c33f3e9dc..00000000000 --- a/packages/x6-vector/src/vector/text/util.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Global } from '../../global' - -export function getFontSize(node: Element) { - const style = Global.window.getComputedStyle(node) - const fontSize = style.getPropertyValue('font-size') || '14px' - return parseFloat(fontSize) -} diff --git a/packages/x6-vector/src/vector/textpath/exts.ts b/packages/x6-vector/src/vector/textpath/exts.ts deleted file mode 100644 index ece1945dee0..00000000000 --- a/packages/x6-vector/src/vector/textpath/exts.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Base } from '../common/base' -import { Text } from '../text/text' -import { Path } from '../path/path' -import { TextPath } from './textpath' -import { SVGTextAttributes } from '../text/types' -import { SVGTextPathAttributes } from './types' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - textPath( - text: string | Text, - path: string | Path, - attrs?: Attributes, - ) { - // 1. create text instance - const instance = text instanceof Text ? text : new Text() - - // 2. append text to container - instance.appendTo(this) - - // 3. update text content - if (typeof text === 'string') { - instance.text(text) - } - - return instance.path(path, undefined, attrs) - } -} - -export class TextExtension< - TTSVGTextElement extends SVGTextElement | SVGTextPathElement, -> extends Base { - path( - track: string | Path, - importNodes = true, - attrs?: Attributes | null, - ) { - const textPath = new TextPath(attrs) - - let path: Path | null = null - if (track instanceof Path) { - path = track - } else { - const defs = this.defs() - if (defs) { - path = defs.path(track) - } - } - - if (path) { - textPath.attr('href', `#${path.id()}`) - - // Transplant all nodes from text to textPath - let node - if (importNodes) { - while ((node = this.node.firstChild)) { - textPath.node.append(node) - } - } - } - - textPath.appendTo(this) - - return textPath - } - - textPath() { - return this.findOne('textPath') - } -} - -export class PathExtension< - TSVGElement extends SVGElement, -> extends Base { - text( - text: string | Text, - attrs?: Attributes, - ) { - const instance = text instanceof Text ? text : new Text() - if (attrs) { - instance.attr(attrs) - } - - if (!instance.parent()) { - this.after(instance) - } - - // update text after the node was appended to the document - if (typeof text === 'string') { - instance.text(text) - } - - return instance.path(this as any) - } - - targets() { - const root = this.root() - return root - ? root.find('textPath').filter((node) => { - const href = node.attr('href') - return href && href.includes(this.id()) - }) - : [] - } -} diff --git a/packages/x6-vector/src/vector/textpath/textpath.test.ts b/packages/x6-vector/src/vector/textpath/textpath.test.ts deleted file mode 100644 index 50b3e8f38e6..00000000000 --- a/packages/x6-vector/src/vector/textpath/textpath.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { SVG } from '../svg/svg' -import { Text } from '../text/text' -import { Path } from '../path/path' -import { TextPath } from './textpath' -import { TSpan } from '../tspan/tspan' - -describe('TextPath', () => { - let svg: SVG - let text: Text - let path: Path - - const txt = 'We go up, then we go down, then up again' - const data = - 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' - - beforeEach(() => { - svg = new SVG().addTo(document.body) - text = svg.text(txt) - path = svg.path(data) - }) - - afterEach(() => { - svg.remove() - }) - - describe('constructor()', () => { - it('should create a new object of type TextPath', () => { - expect(new TextPath()).toBeInstanceOf(TextPath) - }) - - it('should set passed attributes on the element', () => { - expect(new TextPath({ id: 'foo' }).id()).toBe('foo') - }) - }) - - describe('track()', () => { - it('should return the referenced path instance', () => { - const textPath = text.path(path) - expect(textPath.track()).toBe(path) - }) - }) - - describe('toArray()', () => { - it('should return the path array of the underlying path', () => { - expect(text.path(path).toArray()).toEqual(path.toArray()) - }) - - it('should return null if there is no underlying path', () => { - const textPath = new TextPath() - expect(textPath.toArray()).toBe(null) - }) - }) - - describe('plot()', () => { - it('should change the underlying path', () => { - expect(text.path('').plot(path.toArray()).toArray()).toEqual( - path.toArray(), - ) - }) - - it('should return the path array of the underlying path when no arguments is passed', () => { - const textPath = text.path(path) - expect(textPath.plot()).not.toBeNull() - expect(textPath.plot()).toEqual(textPath.toPathArray()!.toArray()) - }) - - it('should do nothing if no path is attached as track', () => { - const textPath = new TextPath() - expect(textPath.plot('M0 0')).toBe(textPath) - }) - }) - - describe('Container', () => { - describe('textPath()', () => { - it('should create a textPath from string text and string path', () => { - const textPath = svg.textPath(txt, data) - expect(textPath).toBeInstanceOf(TextPath) - expect(textPath.parent()).toBeInstanceOf(Text) - expect(textPath.track()).toBeInstanceOf(Path as any) - expect(textPath.track()!.parent()).toBe(svg.defs()) - }) - - it('should create a textPath from Text and Path', () => { - const textPath = svg.textPath(text, path) - expect(textPath.parent()).toEqual(text) - expect(textPath.track()).toEqual(path) - }) - - it('should passes the text into textPath and not text', () => { - const tspan = text.firstChild() - const textPath = svg.textPath(text, path) - expect(textPath.firstChild()).toBe(tspan) - expect(text.firstChild()).toBe(textPath) - }) - }) - }) - - describe('Text', () => { - describe('path()', () => { - it('should create a textPath node in the text element', () => { - text.path(data) - expect(text.node.querySelector('textPath')).not.toBe(null) - }) - - it('should reference the passed path', () => { - const textPath = text.path(path) - expect(textPath.reference('href')).toBe(path) - }) - - it('should import all nodes from the text by default', () => { - const children = text.children() - const textPath = text.path(path) - expect(textPath.children()).toEqual(children) - }) - - it('should not import all nodes from the text when second parameter false', () => { - const textPath = text.path(path, false) - expect(textPath.children()).toEqual([]) - }) - }) - - describe('textPath()', () => { - it('should return the textPath element of this text', () => { - const textPath = text.path(path) - expect(text.textPath()).toBe(textPath) - }) - }) - }) - - describe('Path', () => { - describe('text()', () => { - it('should create a text with textPath node and inserts it after the path', () => { - const textPath = path.text(txt, { x: 10 }) - expect(textPath.parent()).toBeInstanceOf(Text) - expect(path.node.nextSibling).toBe(textPath.parent()!.node) - }) - - it('should transplant the node from text to textPath', () => { - const nodesInText = [].slice.call(text.node.childNodes) - const textPath = path.text(text) - const nodesInTextPath = [].slice.call(textPath.node.childNodes) - expect(nodesInText).toEqual(nodesInTextPath) - }) - }) - - describe('targets', () => { - it('should return all elements referencing this path with href', () => { - const textPath = text.path(path) - expect(path.targets()).toEqual([textPath]) - }) - - it('should return an empty array when there is no referencs', () => { - expect(path.targets()).toEqual([]) - }) - - it('should return an empty array when path is not in the document', () => { - const path = new Path() - expect(path.targets()).toEqual([]) - }) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/textpath/textpath.ts b/packages/x6-vector/src/vector/textpath/textpath.ts deleted file mode 100644 index d6d20c3386a..00000000000 --- a/packages/x6-vector/src/vector/textpath/textpath.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { PathArray } from '../../struct/path-array' -import { Text } from '../text/text' -import { Path } from '../path/path' - -@TextPath.register('TextPath') -export class TextPath extends Text { - plot(): PathArray - plot(d: string | Path.Segment[] | PathArray): this - plot(d?: string | Path.Segment[] | PathArray) { - const track = this.track() - if (d == null) { - return track ? track.plot() : null - } - if (track) { - track.plot(d) - } - - return this - } - - track() { - return this.reference('href') - } - - toArray() { - const track = this.track() - return track ? track.toArray() : null - } - - toPathArray() { - const track = this.track() - return track ? track.toPathArray() : null - } -} diff --git a/packages/x6-vector/src/vector/textpath/types.ts b/packages/x6-vector/src/vector/textpath/types.ts deleted file mode 100644 index f2f8bc866c3..00000000000 --- a/packages/x6-vector/src/vector/textpath/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGTextPathAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes, - SVGXLinkAttributes { - href?: string - lengthAdjust?: 'spacing' | 'spacingAndGlyphs' - method?: 'align' | 'stretch' - path?: string - side?: 'left' | 'right' - spacing?: 'auto' | 'exact' - startOffset?: string | number - textLength?: string | number -} diff --git a/packages/x6-vector/src/vector/title/exts.ts b/packages/x6-vector/src/vector/title/exts.ts deleted file mode 100644 index 4e6cfdbcbe3..00000000000 --- a/packages/x6-vector/src/vector/title/exts.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Base } from '../common/base' -import { Title } from './title' -import { SVGTitleAttributes } from './types' - -export class ElementExtension< - TSVGElement extends SVGElement, -> extends Base { - title(attrs?: Attributes | null): Title - title( - title: string, - attrs?: Attributes | null, - ): Title - title( - update: Title.Update, - attrs?: Attributes | null, - ): Title - title( - title?: string | Title.Update | Attributes | null, - attrs?: Attributes | null, - ) { - return Title.create(title, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/title/title.test.ts b/packages/x6-vector/src/vector/title/title.test.ts deleted file mode 100644 index 20b12920ebf..00000000000 --- a/packages/x6-vector/src/vector/title/title.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { G } from '../g/g' -import { Title } from './title' - -describe('Title', () => { - describe('constructor()', () => { - it('should create a new object of type Title', () => { - expect(new Title()).toBeInstanceOf(Title) - }) - - it('should set passed attributes on the element', () => { - expect(Title.create({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create an instance from element', () => { - const title = new G().title('foo', { id: 'foo' }) - expect(title.node.textContent).toEqual('foo') - expect(title.id()).toBe('foo') - }) - }) - - describe('update()', () => { - it('should update desc with text', () => { - const title = Title.create() - title.update('foo') - expect(title.node.textContent).toEqual('foo') - }) - - it('should remove desc content when pass null', () => { - const title = Title.create('foo') - expect(title.node.textContent).toEqual('foo') - title.update(null) - expect(title.node.textContent).toEqual('') - }) - - it('should update desc with callback', () => { - const title = Title.create() - title.update((instance) => (instance.node.textContent = 'foo')) - expect(title.node.textContent).toEqual('foo') - }) - }) -}) diff --git a/packages/x6-vector/src/vector/title/title.ts b/packages/x6-vector/src/vector/title/title.ts deleted file mode 100644 index fc5c1ccba1e..00000000000 --- a/packages/x6-vector/src/vector/title/title.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Base } from '../common/base' -import { SVGTitleAttributes } from './types' - -@Title.register('title') -export class Title extends Base { - update(desc?: string | null | Title.Update) { - if (typeof desc === 'string' || desc == null) { - this.node.textContent = desc || null - } else { - desc.call(this, this) - } - return this - } -} - -export namespace Title { - export type Update = (this: Title, pattern: Title) => void - - export function create( - attrs?: Attributes | null, - ): Title - export function create( - title: string, - attrs?: Attributes | null, - ): Title - export function create( - update: Update, - attrs?: Attributes | null, - ): Title - export function create( - title?: string | Update | Attributes | null, - attrs?: Attributes | null, - ): Title - export function create( - title?: string | Update | Attributes | null, - attrs?: Attributes | null, - ): Title { - const instance = new Title() - if (typeof title === 'string' || typeof title === 'function') { - instance.update(title) - if (attrs) { - instance.attr(attrs) - } - } else if (title) { - instance.attr(title) - } - return instance - } -} diff --git a/packages/x6-vector/src/vector/title/types.ts b/packages/x6-vector/src/vector/title/types.ts deleted file mode 100644 index d40fa76b265..00000000000 --- a/packages/x6-vector/src/vector/title/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { SVGCoreAttributes, SVGStyleAttributes } from '../types/attributes-core' - -export interface SVGTitleAttributes - extends SVGCoreAttributes, - SVGStyleAttributes {} diff --git a/packages/x6-vector/src/vector/tspan/exts.ts b/packages/x6-vector/src/vector/tspan/exts.ts deleted file mode 100644 index 87fb9ac7c3f..00000000000 --- a/packages/x6-vector/src/vector/tspan/exts.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { TextBase } from '../text/base' -import { TSpan } from './tspan' -import { SVGTSpanAttributes } from './types' - -export class TextExtension< - TSVGTextElement extends SVGTextElement | SVGTSpanElement | SVGTextPathElement, -> extends TextBase { - tspan(attrs?: Attributes | null): TSpan - tspan( - text: string, - attrs?: Attributes | null, - ): TSpan - tspan( - text: Attributes | string | null = '', - attrs?: Attributes | null, - ) { - const tspan = new TSpan() - - if (text != null) { - if (typeof text === 'string') { - tspan.text(text) - if (attrs) { - tspan.attr(attrs) - } - } else { - tspan.attr(text) - } - } - - if (!this.building) { - this.clear() - } - - return tspan.appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/tspan/mixins.ts b/packages/x6-vector/src/vector/tspan/mixins.ts deleted file mode 100644 index 0a25b0053f5..00000000000 --- a/packages/x6-vector/src/vector/tspan/mixins.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { applyMixins } from '../../util/mixin' -import { TextExtension as TspanExtension } from '../tspan/exts' -import { TSpan } from './tspan' - -declare module './tspan' { - interface TSpan extends TspanExtension {} -} - -applyMixins(TSpan, TspanExtension) diff --git a/packages/x6-vector/src/vector/tspan/tspan.test.ts b/packages/x6-vector/src/vector/tspan/tspan.test.ts deleted file mode 100644 index 871d1681597..00000000000 --- a/packages/x6-vector/src/vector/tspan/tspan.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { SVG } from '../svg/svg' -import { Text } from '../text/text' -import { TSpan } from './tspan' -import { getFontSize } from '../text/util' - -describe('TSpan', () => { - describe('constructor()', () => { - it('should create an instance with given attributes', () => { - expect(new TSpan({ id: 'foo' }).id()).toBe('foo') - }) - - it('should create a tspan in a text', () => { - const text = new Text() - const tspan = text.tspan('Hello World', { id: 'foo' }) - expect(tspan).toBeInstanceOf(TSpan) - expect(tspan.node.textContent).toBe('Hello World') - expect(tspan.id()).toEqual('foo') - expect(tspan.parent()).toBe(text) - }) - - it('should create a tspan in a tspan', () => { - const tspan1 = new TSpan() - const tspan2 = tspan1.tspan({ id: 'foo' }) - expect(tspan2).toBeInstanceOf(TSpan) - expect(tspan2.id()).toEqual('foo') - expect(tspan2.parent()).toBe(tspan1) - }) - - it('should create a tspan and calles newLine() on it', () => { - const text = new Text() - const tspan = text.newLine() - expect(tspan).toBeInstanceOf(TSpan) - expect(tspan.parent()).toBe(text) - expect(tspan.affix('newLined')).toBeTrue() - }) - }) - - describe('text()', () => { - it('should set the text content of the tspan and returns itself', () => { - const tspan = new TSpan() - expect(tspan.text('Hello World')).toBe(tspan) - expect(tspan.node.textContent).toBe('Hello World') - }) - - it('should return the textContent of the tspan', () => { - const tspan = new TSpan().text('Hello World') - expect(tspan.text()).toBe('Hello World') - }) - - it('should add a newline when this tspan is a newline', () => { - const tspan = new TSpan().text('Hello World').newLine() - expect(tspan.text()).toBe('Hello World\n') - }) - - it('should execute a function in the context of the tspan', () => { - const tspan = new TSpan() - tspan.text(function (t) { - expect(this).toBe(tspan) - expect(t).toBe(tspan) - }) - }) - }) - - describe('dx()', () => { - it('should set the dx attribute and returns itself', () => { - const tspan = new TSpan() - expect(tspan.dx(20)).toBe(tspan) - expect(tspan.attr('dx')).toBe(20) - }) - - it('should return the dx attribute', () => { - const tspan = new TSpan().dx(20) - expect(tspan.dx()).toBe(20) - }) - }) - - describe('dy()', () => { - it('should set the dy attribute and returns itself', () => { - const tspan = new TSpan() - expect(tspan.dy(20)).toBe(tspan) - expect(tspan.attr('dy')).toBe(20) - }) - - it('should return the dy attribute', () => { - const tspan = new TSpan().dy(20) - expect(tspan.dy()).toBe(20) - }) - }) - - describe('newLine()', () => { - it('should work without text parent', () => { - // should not fail - const tspan = new TSpan().newLine() - expect(tspan.affix('newLined')).toBeTrue() - }) - - it('should set dy to zero of first line', () => { - const text = new Text() - const first = text.tspan('First Line').newLine() - expect(first.dy()).toBe(0) - }) - - it('should set dy corresponding to line and leading', () => { - const svg = new SVG().appendTo(document.body) - const text = new Text().leading(2).build(true).addTo(svg) - text.tspan('First Line').newLine() - text.tspan('Second Line').newLine() - const third = text.tspan('Third Line').newLine() - - const dy = 2 * getFontSize(text.node) - expect(third.dy()).toBe(dy) - svg.remove() - }) - }) -}) diff --git a/packages/x6-vector/src/vector/tspan/tspan.ts b/packages/x6-vector/src/vector/tspan/tspan.ts deleted file mode 100644 index f5b61f54075..00000000000 --- a/packages/x6-vector/src/vector/tspan/tspan.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Text } from '../text/text' -import { TextBase } from '../text/base' -import { getFontSize } from '../text/util' - -@TSpan.register('Tspan') -export class TSpan extends TextBase { - dx(): number - dx(dx: number | string): this - dx(dx?: number | string) { - return this.attr('dx', dx) - } - - dy(): number - dy(dy: number | string): this - dy(dy?: number | string) { - return this.attr('dy', dy) - } - - newLine() { - // mark new line - this.affix('newLined', true) - - const text = this.parent() - if (text == null || !(text instanceof Text)) { - return this - } - - const index = text.indexOf(this) - const dy = index > 0 ? text.leading() * getFontSize(this.node) : 0 - this.dy(dy).attr('x', text.x()) - return this - } - - text(): string - text(text: string | ((this: TSpan, tspan: TSpan) => void)): this - text(text?: string | ((this: TSpan, tspan: TSpan) => void)) { - if (text == null) { - return ( - this.node.textContent + (this.affix('newLined') ? '\n' : '') - ) - } - - if (typeof text === 'function') { - this.clear().build(true) - text.call(this, this) - this.build(false) - } else { - this.plain(text) - } - - return this - } -} diff --git a/packages/x6-vector/src/vector/tspan/types.ts b/packages/x6-vector/src/vector/tspan/types.ts deleted file mode 100644 index d0b87c09eec..00000000000 --- a/packages/x6-vector/src/vector/tspan/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGTSpanAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes { - x?: string | number - y?: string | number - dx?: string | number - dy?: string | number - rotate?: string | number - textLength?: string | number - lengthAdjust?: 'spacing' | 'spacingAndGlyphs' -} diff --git a/packages/x6-vector/src/vector/types/attributes-core.ts b/packages/x6-vector/src/vector/types/attributes-core.ts deleted file mode 100644 index 9d0bdb68f26..00000000000 --- a/packages/x6-vector/src/vector/types/attributes-core.ts +++ /dev/null @@ -1,634 +0,0 @@ -import { - AriaAttributes, - CSSProperties, - CustomAttributes, -} from '../../dom/types/attributes-core' - -export * from '../../dom/types/attributes-core' - -export interface SVGCoreAttributes extends CustomAttributes { - /** - * The `id` attribute assigns a unique name to an element. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/id](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/id) - */ - id?: string - /** - * The `tabindex` attribute allows you to control whether an element is - * focusable and to define the relative order of the element for the purposes - * of sequential focus navigation. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/tabindex](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/tabindex) - */ - tabIndex?: number - /** - * The `lang` attribute specifies the primary language used in contents and - * attributes containing text content of particular elements. - * - * There is also an `xml:lang` attribute (with namespace). If both of them - * are defined, the one with namespace is used and the one without is ignored. - * - * In SVG 1.1 there was a lang attribute defined with a different meaning and - * only applying to `` elements. That attribute specified a list of - * languages in BCP 47 format. The glyph was meant to be used if the `xml:lang` - * attribute exactly matched one of the languages given in the value of this - * parameter, or if the `xml:lang` attribute exactly equaled a prefix of one - * of the languages given in the value of this parameter such that the first - * tag character following the prefix was "-". - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lang](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lang) - */ - lang?: string - xmlBase?: string - xmlLang?: string - xmlSpace?: string - role?: string -} - -export interface SVGStyleAttributes { - /** - * Assigns a class name or set of class names to an element. You may assign - * the same class name or names to any number of elements, however, multiple - * class names must be separated by whitespace characters. - * - * An element's class name serves two key roles: - * - As a style sheet selector, for when an author assigns style information - * to a set of elements. - * - For general use by the browser. - * - * You can use this class to style SVG content using CSS. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/class](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/class) - */ - class?: string - /** - * The `style` attribute allows to style an element using CSS declarations. - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/style](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/style) - */ - style?: CSSProperties -} - -export interface SVGConditionalProcessingAttributes { - externalResourcesRequired?: boolean - requiredExtensions?: string - requiredFeatures?: string - systemLanguage?: string -} - -export interface SVGCommonAttributes - extends AriaAttributes, - SVGCoreAttributes {} - -export interface SVGXLinkAttributes { - xlinkHref?: string - xlinkType?: 'simple' - xlinkRole?: string - xlinkArcrole?: string - xlinkTitle?: string - xlinkShow?: 'new' | 'replace' | 'embed' | 'other' | 'none' - xlinkActuate?: string -} - -export interface SVGPresentationClipAttributes { - clip?: string - /** - * The `clip-path` presentation attribute defines or associates a clipping - * path with the element it is related to. - * - * **Note**: As a presentation attribute clip-path can be used as a CSS - * property. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clip-path](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clip-path) - */ - clipPath?: string - /** - * The `clip-rule` attribute only applies to graphics elements that are - * contained within a `` element. The `clip-rule` attribute - * basically works as the `fill-rule` attribute, except that it applies to - * `` definitions. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clip-rule](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clip-rule) - */ - clipRule?: 'nonzero' | 'evenodd' -} - -export interface SVGPresentationColorAttributes { - /** - * The `color` attribute is used to provide a potential indirect value, - * `currentcolor`, for the `fill`, `stroke`, `stop-color`, `flood-color`, - * and `lighting-color` attributes. - * - * **Note**: As a presentation attribute, `color` can be used as a CSS - * property. See [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color) for further information. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color) - */ - color?: string - /** - * The `color-interpolation` attribute specifies the color space for gradient - * interpolations, color animations, and alpha compositing. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-interpolation](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-interpolation) - */ - colorInterpolation?: string - /** - * The `color-interpolation-filters` attribute specifies the color space for - * imaging operations performed via filter effects. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-interpolation-filters](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-interpolation-filters) - */ - colorInterpolationFilters?: string - /** - * - * The `color-profile` attribute is used to define which color profile a - * raster image included through the `` element should use. - * - * **Note**: As a presentation attribute, color-profile can be used as a - * CSS property. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-profile](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-profile) - - * @deprecated Since SVG 2 - * - * This feature is no longer recommended. Though some browsers might still - * support it, it may have already been removed from the relevant web - * standards, may be in the process of being dropped, or may only be kept - * for compatibility purposes. Avoid using it, and update existing code if - * possible; see the compatibility table at the bottom of this page to guide - * your decision. Be aware that this feature may cease to work at any time. - */ - colorProfile?: string - /** - * The `color-rendering` attribute provides a hint to the SVG user agent - * about how to optimize its color interpolation and compositing operations. - * - * @deprecated This feature is no longer recommended. Though some browsers - * might still support it, it may have already been removed from the relevant - * web standards, may be in the process of being dropped, or may only be - * kept for compatibility purposes. Avoid using it, and update existing code - * if possible; see the compatibility table at the bottom of this page to - * guide your decision. Be aware that this feature may cease to work at any - * time. - */ - colorRendering?: string - lightingColor?: string - solidColor?: string - solidOpacity?: number - stopColor?: string - stopOpacity?: number -} - -export interface SVGPresentationFillAttributes { - /** - * The `fill` attribute has two different meanings. For shapes and text it's a - * presentation attribute that defines the color (or any SVG paint servers - * like gradients or patterns) used to paint the element; for animation it - * defines the final state of the animation. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill) - */ - fill?: string - /** - * The `fill-opacity` attribute is a presentation attribute defining the - * opacity of the paint server (color, gradient, pattern, etc) applied to - * a shape. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-opacity](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-opacity) - */ - fillOpacity?: number - /** - * The `fill-rule` attribute is a presentation attribute defining the - * algorithm to use to determine the inside part of a shape. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule) - */ - fillRule?: 'nonzero' | 'evenodd' -} - -export interface SVGPresentationFloodAttributes { - /** - * The `flood-color` attribute indicates what color to use to flood the - * current filter primitive subregion. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/flood-color](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/flood-color) - */ - floodColor?: string - /** - * The `flood-opacity` attribute indicates the opacity value to use across - * the current filter primitive subregion. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/flood-opacity](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/flood-opacity) - */ - floodOpacity?: number -} - -export interface SVGPresentationFontAttributes { - /** - * The `font-family` attribute indicates which font family will be used to - * render the text, specified as a prioritized list of font family names - * and/or generic family names. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family) - */ - fontFamily?: string - /** - * The `font-size` attribute refers to the size of the font from baseline to - * baseline when multiple lines of text are set solid in a multiline layout - * environment. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size) - */ - fontSize?: number | string - /** - * The `font-size-adjust` attribute allows authors to specify an aspect value - * for an element that will preserve the x-height of the first choice font in - * a substitute font. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size-adjust](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size-adjust) - */ - fontSizeAdjust?: number - /** - * The `font-stretch` attribute indicates the desired amount of condensing or - * expansion in the glyphs used to render the text. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-stretch](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-stretch) - */ - fontStretch?: string - /** - * The `font-style` attribute specifies whether the text is to be rendered - * using a normal, italic, or oblique face. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style) - */ - fontStyle?: 'normal' | 'italic' | 'oblique' - /** - * The `font-variant` attribute indicates whether the text is to be rendered - * using variations of the font's glyphs. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-variant](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-variant) - */ - fontVariant?: string - /** - * The `font-weight` attribute refers to the boldness or lightness of the - * glyphs used to render the text, relative to other fonts in the same font - * family. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight) - */ - fontWeight?: number | 'normal' | 'bold' | 'bolder' | 'lighter' - /** - * The `letter-spacing` attribute controls spacing between text characters. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/letter-spacing](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/letter-spacing) - */ - letterSpacing?: number - /** - * The `text-anchor` attribute is used to align (start-, middle- or - * end-alignment) a string of pre-formatted text or auto-wrapped text where - * the wrapping area is determined from the inline-size property relative to - * a given point. It is not applicable to other types of auto-wrapped text. - * For those cases you should use text-align. For multi-line text, the - * alignment takes place for each line. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor) - */ - textAnchor?: 'start' | 'middle' | 'end' - /** - * The `text-decoration` attribute defines whether text is decorated with an - * underline, overline and/or strike-through. It is a shorthand for the - * text-decoration-line and text-decoration-style properties. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-decoration](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-decoration) - */ - textDecoration?: string - /** - * The `unicode-bidi` attribute specifies how the accumulation of the - * background image is managed. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/unicode-bidi](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/unicode-bidi) - */ - unicodeBidi?: - | 'normal' - | 'embed' - | 'isolate' - | 'bidi-override' - | 'isolate-override' - | 'plaintext' - /** - * The `word-spacing` attribute specifies spacing behavior between words. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/word-spacing](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/word-spacing) - */ - wordSpacing?: number - /** - * The `writing-mode` attribute specifies whether the initial - * inline-progression-direction for a `` element shall be left-to-right, - * right-to-left, or top-to-bottom. - */ - writingMode?: 'horizontal-tb' | 'vertical-rl' | 'vertical-lr' - /** - * The `alignment-baseline` attribute specifies how an object is aligned with - * respect to its parent. This property specifies which baseline of this - * element is to be aligned with the corresponding baseline of the parent. - * - * For example, this allows alphabetic baselines in Roman text to stay aligned - * across font size changes. It defaults to the baseline with the same name as - * the computed value of the `alignment-baseline` property. - */ - alignmentBaseline?: - | 'auto' - | 'baseline' - | 'before-edge' - | 'text-before-edge' - | 'middle' - | 'central' - | 'after-edge' - | 'text-after-edge' - | 'ideographic' - | 'alphabetic' - | 'hanging' - | 'mathematical' - | 'top' - | 'center' - | 'bottom' - /** - * The `baseline-shift` attribute allows repositioning of the - * dominant-baseline relative to the dominant-baseline of the parent text - * content element. The shifted object might be a sub- or superscript. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseline-shift](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseline-shift) - */ - baselineShift?: string | 'sub' | 'super' - /** - * The `dominant-baseline` attribute specifies the dominant baseline, which - * is the baseline used to align the box’s text and inline-level contents. - * It also indicates the default alignment baseline of any boxes participating - * in baseline alignment in the box’s alignment context. - * - * @see [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dominant-baseline](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dominant-baseline) - */ - dominantBaseline?: string - direction?: 'ltr' | 'rtl' - glyphOrientationHorizontal?: 'auto' | number - glyphOrientationVertical?: 'auto' | number -} - -export interface SVGPresentationMarkerAttributes { - markerEnd?: string - markerStart?: string - markerMid?: string -} - -export interface SVGPresentationStrokeAttributes { - stroke?: string - strokeDasharray?: string - strokeDashoffset?: string - strokeLinecap?: string - strokeLinejoin?: string - strokeMiterlimit?: string - strokeOpacity?: string - strokeWidth?: string -} - -export interface SVGPresentationTransformAttributes { - transform?: string - transformOrigin?: string -} - -export interface SVGPresentationAttributes - extends SVGPresentationClipAttributes, - SVGPresentationColorAttributes, - SVGPresentationFillAttributes, - SVGPresentationStrokeAttributes, - SVGPresentationTransformAttributes, - SVGPresentationFloodAttributes, - SVGPresentationFontAttributes, - SVGPresentationMarkerAttributes, - SVGPresentationStrokeAttributes { - cursor?: string - display?: string - filter?: string - mask?: string - opacity?: number - overflow?: 'visible' | 'hidden' | 'scroll' | 'auto' - pointerEvents?: - | 'bounding-box' - | 'visiblePainted' - | 'visibleFill' - | 'visibleStroke' - | 'visible' - | 'painted' - | 'fill' - | 'stroke' - | 'all' - | 'none' - shapeRendering?: - | 'auto' - | 'optimizeSpeed' - | 'crispEdges' - | 'geometricPrecision' - imageRendering?: 'auto' | 'optimizeSpeed' | 'optimizeQuality' - enableBackground?: string | number - kerning?: 'auto' | number - vectorEffect?: string - visibility?: string -} - -export interface SVGFilterPrimitiveAttributes { - height?: number | string - result?: string - width?: number | string - x?: number - y?: number -} - -export interface SVGTransferFunctionAttributes { - type?: 'translate' | 'scale' | 'rotate' | 'skewX' | 'skewY' - tableValues?: string - slope?: number - intercept?: number - amplitude?: number - exponent?: number - offset?: number -} - -export type AnimationAttributeTargetAttributeType = 'CSS' | 'XML' | 'auto' - -export interface AnimationAttributeTargetAttributes { - /** - * The `attributeName` attribute indicates the name of the CSS property or - * attribute of the target element that is going to be changed during an - * animation. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/attributeName - */ - attributeName?: string - /** - * The `attributeType` attribute specifies the namespace in which the target - * attribute and its associated values are defined. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/attributeType - * @deprecated This feature is no longer recommended. Though some browsers - * might still support it, it may have already been removed from the relevant - * web standards, may be in the process of being dropped, or may only be kept - * for compatibility purposes. Avoid using it, and update existing code if - * possible; see the compatibility table at the bottom of this page to guide - * your decision. Be aware that this feature may cease to work at any time. - */ - attributeType?: AnimationAttributeTargetAttributeType -} - -export type SVGAnimationTimingRestartMode = 'always' | 'whenNotActive' | 'never' -export type SVGAnimationTimingRepeatCount = number | 'indefinite' -export type SVGAnimationTimingRepeatDuration = string | 'indefinite' -export type SVGAnimationTimingFillMode = 'freeze' | 'remove' - -export interface SVGAnimationTimingAttributes { - /** - * The `begin` attribute defines when an animation should begin or when an - * element should be discarded. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/begin - */ - begin?: string - /** - * The `dur` attribute indicates the simple duration of an animation. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dur - */ - dur?: string - /** - * The `end` attribute defines an end value for the animation that can - * constrain the active duration. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/end - */ - end?: string - /** - * The `min` attribute specifies the minimum value of the active animation - * duration. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/min - */ - min?: string - /** - * The `max` attribute specifies the maximum value of the active animation - * duration. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/max - */ - max?: string - /** - * The `restart` attribute specifies whether or not an animation can restart. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/restart - */ - restart?: SVGAnimationTimingRestartMode - /** - * The `repeatCount` attribute indicates the number of times an animation will - * take place. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/repeatCount - */ - repeatCount?: SVGAnimationTimingRepeatCount - /** - * The `repeatDur` attribute specifies the total duration for repeating an - * animation. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/repeatDur - */ - repeatDur?: SVGAnimationTimingRepeatDuration - /** - * The `fill` attribute defines the final state of the animation - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill#animate - */ - fill?: SVGAnimationTimingFillMode -} - -export type SVGAnimationValueCalcMode = - | 'discrete' - | 'linear' - | 'paced' - | 'spline' - -export interface SVGAnimationValueAttributes { - /** - * The `calcMode` attribute specifies the interpolation mode for the animation. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/calcMode - */ - calcMode?: SVGAnimationValueCalcMode - /** - * The `values` attribute has different meanings, depending upon the context - * where it's used, either it defines a sequence of values used over the - * course of an animation, or it's a list of numbers for a color matrix, - * which is interpreted differently depending on the type of color change - * to be performed. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/values - */ - values?: string - /** - * The `keyTimes` attribute represents a list of time values used to control - * the pacing of the animation. Each time in the list corresponds to a value - * in the `values` attribute list, and defines when the value is used in the - * animation. Each time value in the `keyTimes` list is specified as a - * floating point value between `0` and `1` (inclusive), representing a - * proportional offset into the duration of the animation element. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keyTimes - */ - keyTimes?: string - /** - * The `keySplines` attribute defines a set of **Bézier curve** control - * points associated with the keyTimes list, defining a cubic Bézier function - * that controls interval pacing. - * - * This attribute is ignored unless the `calcMode` attribute is set to spline. - * - * If there are any errors in the `keySplines` specification (bad values, - * too many or too few values), the animation will not occur. - * - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keySplines - */ - keySplines?: string - /** - * The `from` attribute indicates the initial value of the attribute that - * will be modified during the animation. When used with the to attribute, - * the animation will change the modified attribute from the `from` value to - * the `to` value. When used with the `by` attribute, the animation will - * change the attribute relatively from the `from` value by the value - * specified in `by`. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/From - */ - from?: string | number - /** - * The `to` attribute indicates the final value of the attribute that will - * be modified during the animation. The value of the attribute will change - * between the `from` attribute value and this value. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/To - */ - to?: string | number - /** - * The `by` attribute specifies a relative offset value for an attribute - * that will be modified during an animation. - * - * The starting value for the attribute is either indicated by specifying - * it as value for the attribute given in the `attributeName` or the `from` - * attribute. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/by - */ - by?: string | number -} - -export type SVGAnimationAdditionAttributeAdditive = 'replace' | 'sum' -export type SVGAnimationAdditionAttributeAccumulate = 'none' | 'sum' - -export interface SVGAnimationAdditionAttributes { - /** - * The `additive` attribute controls whether or not an animation is additive. - * - * It is frequently useful to define animation as an offset or delta to an - * attribute's value, rather than as absolute values. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/additive - */ - additive?: SVGAnimationAdditionAttributeAdditive - /** - * The `accumulate` attribute controls whether or not an animation is - * cumulative. - * - * It is frequently useful for repeated animations to build upon the - * previous results, accumulating with each iteration. This attribute - * said to the animation if the value is added to the previous animated - * attribute's value on each iteration. - * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/accumulate - */ - accumulate?: SVGAnimationAdditionAttributeAccumulate -} diff --git a/packages/x6-vector/src/vector/types/attributes-map.ts b/packages/x6-vector/src/vector/types/attributes-map.ts deleted file mode 100644 index 3e025f33029..00000000000 --- a/packages/x6-vector/src/vector/types/attributes-map.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { SVGAAttributes } from '../a/types' -import { SVGAnimateAttributes } from '../animate/types' -import { SVGAnimateMotionAttributes } from '../animate-motion/types' -import { SVGAnimateTransformAttributes } from '../animate-transform/types' -import { SVGCircleAttributes } from '../circle/types' -import { SVGClipPathAttributes } from '../clippath/types' -import { SVGDefsAttributes } from '../defs/types' -import { SVGDescAttributes } from '../desc/types' -import { SVGEllipseAttributes } from '../ellipse/types' -import { SVGFEBlendAttributes } from '../fe-blend/types' -import { SVGFEColorMatrixAttributes } from '../fe-color-matrix/types' -import { SVGFEComponentTransferAttributes } from '../fe-component-transfer/types' -import { SVGFECompositeAttributes } from '../fe-composite/types' -import { SVGFEConvolveMatrixAttributes } from '../fe-convolve-matrix/types' -import { SVGFEDiffuseLightingAttributes } from '../fe-diffuse-lighting/types' -import { SVGFEDisplacementMapAttributes } from '../fe-displacement-map/types' -import { SVGFEDistantLightAttributes } from '../fe-distant-light/types' -import { SVGFEFloodAttributes } from '../fe-flood/types' -import { SVGFEFuncAAttributes } from '../fe-func-a/types' -import { SVGFEFuncBAttributes } from '../fe-func-b/types' -import { SVGFEFuncGAttributes } from '../fe-func-g/types' -import { SVGFEFuncRAttributes } from '../fe-func-r/types' -import { SVGFEGaussianBlurAttributes } from '../fe-gaussian-blur/types' -import { SVGFEImageAttributes } from '../fe-image/types' -import { SVGFEMergeNodeAttributes } from '../fe-merge-node/types' -import { SVGFEMergeAttributes } from '../fe-merge/types' -import { SVGFEMorphologyAttributes } from '../fe-morphology/types' -import { SVGFEOffsetAttributes } from '../fe-offset/types' -import { SVGFEPointLightAttributes } from '../fe-point-light/types' -import { SVGFESpecularLightingAttributes } from '../fe-specular-lighting/types' -import { SVGFESpotLightAttributes } from '../fe-spot-light/types' -import { SVGFETileAttributes } from '../fe-tile/types' -import { SVGFETurbulenceAttributes } from '../fe-turbulence/types' -import { SVGFilterAttributes } from '../filter/types' -import { SVGForeignObjectAttributes } from '../foreignobject/types' -import { SVGGAttributes } from '../g/types' -import { - SVGLinearGradientAttributes, - SVGRadialGradientAttributes, - SVGStopAttributes, -} from '../gradient/types' -import { SVGImageAttributes } from '../image/types' -import { SVGLineAttributes } from '../line/types' -import { SVGMarkerAttributes } from '../marker/types' -import { SVGMaskAttributes } from '../mask/types' -import { SVGMetadataAttributes } from '../metadata/types' -import { SVGPathAttributes } from '../path/types' -import { SVGPatternAttributes } from '../pattern/types' -import { SVGPolygonAttributes } from '../polygon/types' -import { SVGPolylineAttributes } from '../polyline/types' -import { SVGRectAttributes } from '../rect/types' -import { SVGScriptAttributes } from '../script/types' -import { SVGStyleAttributes } from '../style/types' -import { SVGSVGAttributes } from '../svg/types' -import { SVGSwitchAttributes } from '../switch/types' -import { SVGSymbolAttributes } from '../symbol/types' -import { SVGTextAttributes } from '../text/types' -import { SVGTextPathAttributes } from '../textpath/types' -import { SVGTitleAttributes } from '../title/types' -import { SVGTSpanAttributes } from '../tspan/types' -import { SVGUseAttributes } from '../use/types' -import { SVGViewAttributes } from '../view/types' -import { SVGCoreAttributes } from './attributes-core' - -export interface SVGAttributesTagNameMap { - a: SVGAAttributes - animate: SVGAnimateAttributes - animateMotion: SVGAnimateMotionAttributes - animateTransform: SVGAnimateTransformAttributes - circle: SVGCircleAttributes - clipPath: SVGClipPathAttributes - defs: SVGDefsAttributes - desc: SVGDescAttributes - ellipse: SVGEllipseAttributes - feBlend: SVGFEBlendAttributes - feColorMatrix: SVGFEColorMatrixAttributes - feComponentTransfer: SVGFEComponentTransferAttributes - feComposite: SVGFECompositeAttributes - feConvolveMatrix: SVGFEConvolveMatrixAttributes - feDiffuseLighting: SVGFEDiffuseLightingAttributes - feDisplacementMap: SVGFEDisplacementMapAttributes - feDistantLight: SVGFEDistantLightAttributes - feFlood: SVGFEFloodAttributes - feFuncA: SVGFEFuncAAttributes - feFuncB: SVGFEFuncBAttributes - feFuncG: SVGFEFuncGAttributes - feFuncR: SVGFEFuncRAttributes - feGaussianBlur: SVGFEGaussianBlurAttributes - feImage: SVGFEImageAttributes - feMerge: SVGFEMergeAttributes - feMergeNode: SVGFEMergeNodeAttributes - feMorphology: SVGFEMorphologyAttributes - feOffset: SVGFEOffsetAttributes - fePointLight: SVGFEPointLightAttributes - feSpecularLighting: SVGFESpecularLightingAttributes - feSpotLight: SVGFESpotLightAttributes - feTile: SVGFETileAttributes - feTurbulence: SVGFETurbulenceAttributes - filter: SVGFilterAttributes - foreignObject: SVGForeignObjectAttributes - g: SVGGAttributes - image: SVGImageAttributes - line: SVGLineAttributes - linearGradient: SVGLinearGradientAttributes - marker: SVGMarkerAttributes - mask: SVGMaskAttributes - metadata: SVGMetadataAttributes - path: SVGPathAttributes - pattern: SVGPatternAttributes - polygon: SVGPolygonAttributes - polyline: SVGPolylineAttributes - radialGradient: SVGRadialGradientAttributes - rect: SVGRectAttributes - script: SVGScriptAttributes - stop: SVGStopAttributes - style: SVGStyleAttributes - svg: SVGSVGAttributes - switch: SVGSwitchAttributes - symbol: SVGSymbolAttributes - text: SVGTextAttributes - textPath: SVGTextPathAttributes - title: SVGTitleAttributes - tspan: SVGTSpanAttributes - use: SVGUseAttributes - view: SVGViewAttributes -} - -// prettier-ignore -export type SVGAttributesMap = - T extends SVGAElement ? SVGAAttributes : - T extends SVGAnimateElement ? SVGAnimateAttributes : - T extends SVGAnimateMotionElement ? SVGAnimateMotionAttributes : - T extends SVGAnimateTransformElement ? SVGAnimateTransformAttributes : - T extends SVGCircleElement ? SVGCircleAttributes : - T extends SVGClipPathElement ? SVGClipPathAttributes : - T extends SVGDefsElement ? SVGDefsAttributes : - T extends SVGDescElement ? SVGDescAttributes : - T extends SVGEllipseElement ? SVGEllipseAttributes : - T extends SVGFEBlendElement ? SVGFEBlendAttributes : - T extends SVGFEColorMatrixElement ? SVGFEColorMatrixAttributes : - T extends SVGFEComponentTransferElement ? SVGFEComponentTransferAttributes : - T extends SVGFECompositeElement ? SVGFECompositeAttributes : - T extends SVGFEConvolveMatrixElement ? SVGFEConvolveMatrixAttributes : - T extends SVGFEDiffuseLightingElement ? SVGFEDiffuseLightingAttributes : - T extends SVGFEDisplacementMapElement ? SVGFEDisplacementMapAttributes : - T extends SVGFEDistantLightElement ? SVGFEDistantLightAttributes : - T extends SVGFEFloodElement ? SVGFEFloodAttributes : - T extends SVGFEFuncAElement ? SVGFEFuncAAttributes : - T extends SVGFEFuncBElement ? SVGFEFuncBAttributes : - T extends SVGFEFuncGElement ? SVGFEFuncGAttributes : - T extends SVGFEFuncRElement ? SVGFEFuncRAttributes : - T extends SVGFEGaussianBlurElement ? SVGFEGaussianBlurAttributes : - T extends SVGFEImageElement ? SVGFEImageAttributes : - T extends SVGFEMergeElement ? SVGFEMergeAttributes : - T extends SVGFEMergeNodeElement ? SVGFEMergeNodeAttributes : - T extends SVGFEMorphologyElement ? SVGFEMorphologyAttributes : - T extends SVGFEOffsetElement ? SVGFEOffsetAttributes : - T extends SVGFEPointLightElement ? SVGFEPointLightAttributes : - T extends SVGFETileElement ? SVGFETileAttributes : - T extends SVGFETurbulenceElement ? SVGFETurbulenceAttributes : - T extends SVGFilterElement ? SVGFilterAttributes : - T extends SVGForeignObjectElement ? SVGForeignObjectAttributes : - T extends SVGGElement ? SVGGAttributes : - T extends SVGImageElement ? SVGImageAttributes : - T extends SVGLineElement ? SVGLineAttributes : - T extends SVGLinearGradientElement ? SVGLinearGradientAttributes : - T extends SVGMarkerElement ? SVGMarkerAttributes : - T extends SVGMaskElement ? SVGMaskAttributes : - T extends SVGMetadataElement ? SVGMetadataAttributes : - T extends SVGPathElement ? SVGPathAttributes : - T extends SVGPatternElement ? SVGPatternAttributes : - T extends SVGPolygonElement ? SVGPolygonAttributes : - T extends SVGPolylineElement ? SVGPolylineAttributes : - T extends SVGRadialGradientElement ? SVGRadialGradientAttributes : - T extends SVGRectElement ? SVGRectAttributes : - T extends SVGScriptElement ? SVGScriptAttributes : - T extends SVGStopElement ? SVGStopAttributes : - T extends SVGStyleElement ? SVGStyleAttributes : - T extends SVGSVGElement ? SVGSVGAttributes : - T extends SVGSwitchElement ? SVGSwitchAttributes : - T extends SVGSymbolElement ? SVGSymbolAttributes : - T extends SVGTextElement ? SVGTextAttributes : - T extends SVGTextPathElement ? SVGTextPathAttributes : - T extends SVGTSpanElement ? SVGTSpanAttributes : - T extends SVGTitleElement ? SVGTitleAttributes : - T extends SVGUseElement ? SVGUseAttributes : - T extends SVGViewElement ? SVGViewAttributes - : SVGCoreAttributes diff --git a/packages/x6-vector/src/vector/types/index.ts b/packages/x6-vector/src/vector/types/index.ts deleted file mode 100644 index 59b3e5fa0cb..00000000000 --- a/packages/x6-vector/src/vector/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './attributes-core' -export * from './attributes-map' diff --git a/packages/x6-vector/src/vector/use/exts.ts b/packages/x6-vector/src/vector/use/exts.ts deleted file mode 100644 index 452f09a6ab3..00000000000 --- a/packages/x6-vector/src/vector/use/exts.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Base } from '../common/base' -import { Use } from './use' -import { SVGUseAttributes } from './types' -import { Vector } from '../vector/vector' - -export class ContainerExtension< - TSVGElement extends SVGElement, -> extends Base { - use(attrs?: Attributes | null): Use - use( - element: Vector, - attrs?: Attributes | null, - ): Use - use( - elementId: string, - attrs?: Attributes | null, - ): Use - use( - element: Vector, - file: string, - attrs?: Attributes | null, - ): Use - use( - elementId: string, - file: string, - attrs?: Attributes | null, - ): Use - use( - elementId?: string | Vector | Attributes | null, - file?: string | Attributes | null, - attrs?: Attributes | null, - ) { - return Use.create(elementId, file, attrs).appendTo(this) - } -} diff --git a/packages/x6-vector/src/vector/use/types.ts b/packages/x6-vector/src/vector/use/types.ts deleted file mode 100644 index 3d572099d4d..00000000000 --- a/packages/x6-vector/src/vector/use/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGXLinkAttributes, - SVGPresentationAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGUseAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, - SVGPresentationAttributes, - SVGXLinkAttributes { - x?: string | number - y?: string | number - width?: string | number - height?: string | number - href?: string -} diff --git a/packages/x6-vector/src/vector/use/use.test.ts b/packages/x6-vector/src/vector/use/use.test.ts deleted file mode 100644 index bf458813fe7..00000000000 --- a/packages/x6-vector/src/vector/use/use.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Rect } from '../rect/rect' -import { SVG } from '../svg/svg' -import { Use } from './use' - -describe('Use', () => { - describe('constructor()', () => { - it('should create a new object of type Use', () => { - expect(new Use()).toBeInstanceOf(Use) - }) - - it('should set passed attributes on the element', () => { - expect(Use.create({ id: 'foo' }).id()).toBe('foo') - }) - }) - - describe('use()', () => { - it('should link an element', () => { - const rect = new Rect() - const use = Use.create(rect, { foo: 'bar' } as any) - expect(use.attr('href')).toBe(`#${rect.id()}`) - expect(use.attr('foo')).toBe('bar') - }) - - it('should link an element with id', () => { - const use = Use.create('rect1', null, { foo: 'bar' } as any) - expect(use.attr('href')).toBe(`#rect1`) - expect(use.attr('foo')).toBe('bar') - }) - - it('should link an element from a different file', () => { - const use = Use.create('id', 'file', { foo: 'bar' } as any) - expect(use.attr('href')).toBe('file#id') - expect(use.attr('foo')).toBe('bar') - }) - - it('should create an use element linked to the given element', () => { - const svg = new SVG().appendTo(document.body) - const rect = svg.rect(100, 100) - const use = svg.use(rect) - - expect(use.attr('href')).toBe(`#${rect.id()}`) - expect(use.reference('href')).toBe(rect) - - svg.remove() - }) - }) -}) diff --git a/packages/x6-vector/src/vector/use/use.ts b/packages/x6-vector/src/vector/use/use.ts deleted file mode 100644 index 0d5053c5183..00000000000 --- a/packages/x6-vector/src/vector/use/use.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Shape } from '../common/shape' -import { Vector } from '../vector/vector' -import { SVGUseAttributes } from './types' - -@Use.register('Use') -export class Use extends Shape { - use(element: Vector, file?: string): this - use(elementId: string, file?: string): this - use(elementId: string | Vector, file?: string): this - use(elementId: string | Vector, file?: string) { - return this.attr('href', `${file || ''}#${elementId}`) - } -} - -export namespace Use { - export function create( - attrs?: Attributes | null, - ): Use - export function create( - element: Vector, - attrs?: Attributes | null, - ): Use - export function create( - elementId: string, - attrs?: Attributes | null, - ): Use - export function create( - element: Vector, - file: string, - attrs?: Attributes | null, - ): Use - export function create( - elementId: string, - file: string, - attrs?: Attributes | null, - ): Use - export function create( - elementId?: string | Vector | Attributes | null, - file?: string | Attributes | null, - attrs?: Attributes | null, - ): Use - export function create( - elementId?: string | Vector | Attributes | null, - file?: string | Attributes | null, - attrs?: Attributes | null, - ) { - const use = new Use() - if (elementId) { - if (typeof elementId === 'string' || elementId instanceof Vector) { - if (file) { - if (typeof file === 'string') { - use.use(elementId, file) - if (attrs) { - use.attr(attrs) - } - } else { - use.use(elementId).attr(file) - } - } else { - use.use(elementId) - if (attrs) { - use.attr(attrs) - } - } - } else { - use.attr(elementId) - } - } - return use - } -} diff --git a/packages/x6-vector/src/vector/vector/bbox.test.ts b/packages/x6-vector/src/vector/vector/bbox.test.ts deleted file mode 100644 index 45adcaec8c4..00000000000 --- a/packages/x6-vector/src/vector/vector/bbox.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Global } from '../../global' -import { Box } from '../../struct/box' -import { Matrix } from '../../struct/matrix' -import { Rect } from '../rect/rect' -import { SVG } from '../svg/svg' - -describe('Vector', () => { - describe('bbox()', () => { - it('should returns the bounding box of the element', () => { - const svg = new SVG().appendTo(document.body) - const rect = svg.rect().size(100, 200).move(20, 30) - - expect(rect.bbox()).toBeInstanceOf(Box) - expect(rect.bbox().toArray()).toEqual([20, 30, 100, 200]) - svg.remove() - }) - - it('should return the bounding box of the element even if the node is not in the dom', () => { - const rect = new Rect().size(100, 200).move(20, 30) - expect(rect.bbox().toArray()).toEqual([20, 30, 100, 200]) - }) - - it('should throw error when it is not possible to get a bbox', () => { - const spy = spyOn( - Global.window.SVGGraphicsElement.prototype, - 'getBBox', - ).and.callFake(() => { - throw new Error('No BBox for you') - }) - const rect = new Rect() - expect(() => rect.bbox()).toThrow() - expect(spy).toHaveBeenCalled() - }) - }) - - describe('rbox()', () => { - it('should return the BoundingClientRect of the element', () => { - document.body.style.margin = '0px' - document.body.style.padding = '0px' - - const svg = new SVG().appendTo(document.body) - const rect = new Rect() - .size(100, 200) - .move(20, 30) - .addTo(svg) - .attr( - 'transform', - new Matrix({ scale: 2, translate: [40, 50] }).toString(), - ) - - expect(rect.rbox()).toBeInstanceOf(Box) - expect(rect.rbox().toArray()).toEqual([80, 110, 200, 400]) - svg.remove() - - document.body.style.margin = '' - document.body.style.padding = '' - }) - - it('should return the rbox box of the element in the coordinate system of the passed element', () => { - const svg = new SVG().appendTo(document.body) - const group = svg.group().translate(1, 1) - const rect = new Rect() - .size(100, 200) - .move(20, 30) - .addTo(svg) - .attr( - 'transform', - new Matrix({ scale: 2, translate: [40, 50] }).toString(), - ) - - expect(rect.rbox(group)).toBeInstanceOf(Box) - expect(rect.rbox(group).toArray()).toEqual([79, 109, 200, 400]) - svg.remove() - }) - - it('should throw error when element is not in dom', () => { - expect(() => new Rect().rbox()).toThrow() - }) - }) - - describe('containsPoint()', () => { - it('checks if a point is in the elements borders', () => { - const svg = new SVG() - const rect = svg.rect(100, 100) - - expect(rect.containsPoint(50, 50)).toBe(true) - expect(rect.containsPoint({ x: 50, y: 50 })).toBe(true) - - expect(rect.containsPoint(101, 101)).toBe(false) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/vector/bbox.ts b/packages/x6-vector/src/vector/vector/bbox.ts deleted file mode 100644 index 1ae158ff27a..00000000000 --- a/packages/x6-vector/src/vector/vector/bbox.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { withSvgContext } from '../../util/context' -import { Box } from '../../struct/box' -import { Base } from '../common/base' -import { Transform } from './transform' -import { Global } from '../../global' -import { Point } from '../../struct/point' - -export class BBox< - TSVGElement extends SVGElement = SVGElement, -> extends Base { - bbox() { - const getBBox = (node: SVGGraphicsElement) => node.getBBox() - const retry = (node: SVGGraphicsElement) => - withSvgContext((svg) => { - try { - const cloned = this.clone().addTo(svg).show() - const elem = cloned.node as SVGGraphicsElement - const box = elem.getBBox() - cloned.remove() - return box - } catch (error) { - throw new Error( - `Getting bbox of element "${ - node.nodeName - }" is not possible: ${error.toString()}`, - ) - } - }) - - const box = BBox.getBox(this, getBBox, retry) - return new Box(box) - } - - rbox(elem?: T) { - const getRBox = (node: SVGGraphicsElement) => node.getBoundingClientRect() - const retry = (node: SVGGraphicsElement) => { - // There is no point in trying tricks here because if we insert the - // element into the dom ourselfes it obviously will be at the wrong position - throw new Error( - `Getting rbox of element "${node.nodeName}" is not possible`, - ) - } - - const box = BBox.getBox(this, getRBox, retry) - const rbox = new Box(box) - - // If an element was passed, return the bbox in the coordinate system of - // that element. - if (elem) { - return rbox.transform(elem.screenCTM().inverseO()) - } - - // Else we want it in absolute screen coordinates - // Therefore we need to add the scrollOffset - rbox.x += Global.window.pageXOffset - rbox.y += Global.window.pageYOffset - - return rbox - } - - containsPoint(p: Point.PointLike): boolean - containsPoint(x: number, y: number): boolean - containsPoint(arg1: number | Point.PointLike, arg2?: number) { - const box = this.bbox() - const x = typeof arg1 === 'number' ? arg1 : arg1.x - const y = typeof arg1 === 'number' ? (arg2 as number) : arg1.y - return ( - x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height - ) - } -} - -export namespace BBox { - export function getBox>( - elem: T, - getBBox: (node: SVGGraphicsElement) => DOMRect, - retry: (node: SVGGraphicsElement) => DOMRect, - ) { - const node = elem.node as SVGGraphicsElement - let box - try { - box = getBBox(node) - if (Box.isNull(box) && !elem.isInDocument()) { - throw new Error('Element not in the dom') - } - } catch { - box = retry(node) - } - - return box - } -} diff --git a/packages/x6-vector/src/vector/vector/fillstroke.test.ts b/packages/x6-vector/src/vector/vector/fillstroke.test.ts deleted file mode 100644 index f8594172281..00000000000 --- a/packages/x6-vector/src/vector/vector/fillstroke.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { Pattern } from '../pattern/pattern' -import { Rect } from '../rect/rect' -import { SVG } from '../svg/svg' - -describe('Vector', () => { - describe('fill()', () => { - describe('setter', () => { - it('should return itself', () => { - const rect = new Rect() - expect(rect.fill('black')).toBe(rect) - }) - - it('should set a fill color', () => { - const rect = new Rect() - expect(rect.fill('black').attr('fill')).toBe('black') - }) - - it('should remove fill when pass `null`', () => { - const rect = new Rect() - expect(rect.fill('black').attr('fill')).toBe('black') - expect(rect.fill(null).attr('fill')).toEqual('#000000') - }) - - it('should set fill with color object', () => { - const rect = new Rect() - expect(rect.fill({ r: 1, g: 1, b: 1 }).attr('fill')).toBe( - 'rgba(1,1,1,1)', - ) - }) - - it('should set a fill pattern when pattern given', () => { - const svg = new SVG() - const pattern = svg.pattern() - const rect = svg.rect(100, 100) - expect(rect.fill(pattern).attr('fill')).toBe(pattern.url()) - }) - - it('should set a fill pattern when image given', () => { - const svg = new SVG() - const image = svg.image('http://via.placeholder.com/120x80') - const rect = svg.rect(100, 100) - expect(rect.fill(image).attr('fill')).toBe( - image.parent()!.url(), - ) - }) - - it('should set a fill pattern when image url given', () => { - const svg = new SVG() - const rect = svg.rect() - rect.fill( - 'https://www.centerforempathy.org/wp-content/uploads/2019/11/placeholder.png', - ) - const defs = svg.defs() - const pattern = defs.firstChild()! - - expect(rect.fill()).toEqual(pattern.url()) - }) - - it('should set an object of fill properties', () => { - const rect = new Rect() - expect( - rect - .fill({ - color: 'black', - opacity: 0.5, - rule: 'evenodd', - }) - .attr(), - ).toEqual({ - fill: 'black', - fillOpacity: 0.5, - fillRule: 'evenodd', - } as any) - }) - }) - - describe('getter', () => { - it('should return fill color', () => { - const rect = new Rect().fill('black') - expect(rect.fill()).toBe('black') - }) - }) - }) - - describe('stroke()', () => { - describe('setter', () => { - it('should return itself', () => { - const rect = new Rect() - expect(rect.stroke('black')).toBe(rect) - }) - - it('should set a stroke color', () => { - const rect = new Rect() - expect(rect.stroke('black').attr('stroke')).toBe('black') - }) - - it('should sets a stroke pattern when pattern given', () => { - const svg = new SVG() - const pattern = svg.pattern() - const rect = svg.rect(100, 100) - expect(rect.stroke(pattern).attr('stroke')).toBe(pattern.url()) - }) - - it('should set a stroke pattern when image given', () => { - const svg = new SVG() - const image = svg.image('http://via.placeholder.com/120x80') - const rect = svg.rect(100, 100) - expect(rect.stroke(image).attr('stroke')).toBe( - image.parent()!.url(), - ) - }) - - it('should set an object of stroke properties', () => { - const rect = new Rect() - expect( - rect - .stroke({ - color: 'black', - width: 2, - opacity: 0.5, - linecap: 'butt', - linejoin: 'miter', - miterlimit: 10, - dasharray: '2 2', - dashoffset: 15, - }) - .attr(), - ).toEqual({ - stroke: 'black', - strokeWidth: 2, - strokeOpacity: 0.5, - strokeLinecap: 'butt', - strokeLinejoin: 'miter', - strokeMiterlimit: 10, - strokeDasharray: '2 2', - strokeDashoffset: 15, - } as any) - }) - - it('should set stroke dasharray with array passed', () => { - const rect = new Rect().stroke({ dasharray: [2, 2] }) - expect(rect.attr()).toEqual({ strokeDasharray: '2 2' } as any) - }) - }) - - describe('getter', () => { - it('should return stroke color', () => { - const rect = new Rect().stroke('black') - expect(rect.stroke()).toBe('black') - }) - }) - }) - - describe('opacity()', () => { - it('should get/set opacity', () => { - const rect = new Rect() - expect(rect.opacity()).toEqual(1) - rect.opacity(0.5) - expect(rect.opacity()).toEqual(0.5) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/vector/fillstroke.ts b/packages/x6-vector/src/vector/vector/fillstroke.ts deleted file mode 100644 index 70c8617f6d4..00000000000 --- a/packages/x6-vector/src/vector/vector/fillstroke.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Color } from '../../struct/color' -import { Base } from '../common/base' -import { Image } from '../image/image' -import { Vector } from './vector' - -export class FillStroke< - TSVGElement extends SVGElement = SVGElement, -> extends Base { - fill(): string - fill(color: string | Color | Color.RGBALike | Vector | null): this - fill(attrs: { - color?: string - opacity?: number - rule?: 'nonzero' | 'evenodd' - }): this - fill( - value?: - | null - | string - | Color - | Color.RGBALike - | Vector - | { - color?: string - opacity?: number - rule?: 'nonzero' | 'evenodd' - }, - ) { - if (typeof value === 'undefined') { - return this.attr('fill') - } - - FillStroke.fillOrStroke(this, 'fill', value) - return this - } - - stroke(): string - stroke(color: string | Color | Color.RGBALike | Vector | null): this - stroke(attrs: { - color?: string - width?: number - opacity?: number - linecap?: 'butt' | 'round' | 'square' - linejoin?: 'arcs' | 'bevel' | 'miter' | 'miter-clip' | 'round' - miterlimit?: number - dasharray?: string | number[] - dashoffset?: string | number - }): this - stroke( - value?: - | null - | string - | Color - | Color.RGBALike - | Vector - | { - color?: string - width?: number - opacity?: number - linecap?: 'butt' | 'round' | 'square' - linejoin?: 'arcs' | 'bevel' | 'miter' | 'miter-clip' | 'round' - miterlimit?: number - dasharray?: string | number[] - dashoffset?: string | number - }, - ) { - if (typeof value === 'undefined') { - return this.attr('stroke') - } - - FillStroke.fillOrStroke(this, 'stroke', value) - return this - } - - opacity(): number - opacity(value: number | null): this - opacity(value?: number | null) { - return this.attr('opacity', value) - } -} - -export namespace FillStroke { - const map = { - fill: ['color', 'opacity', 'rule'], - stroke: [ - 'color', - 'width', - 'opacity', - 'linecap', - 'linejoin', - 'miterlimit', - 'dasharray', - 'dashoffset', - ], - } - - const prefix = (t: string, a: string) => (a === 'color' ? t : `${t}-${a}`) - - export function fillOrStroke( - elem: Base, - type: 'fill' | 'stroke', - value: - | string - | Color - | Color.RGBALike - | Vector - | Record - | null, - ) { - if (value === null) { - elem.attr(type, null) - } else if (typeof value === 'string' || value instanceof Vector) { - elem.attr( - type, - value instanceof Image ? (value as any) : value.toString(), - ) - } else if (value instanceof Color || Color.isRgbLike(value)) { - const color = new Color(value) - elem.attr(type, color.toString()) - } else { - const names = map[type] - for (let i = names.length - 1; i >= 0; i -= 1) { - const k = names[i] - const v = value[k] - if (v != null) { - elem.attr(prefix(type, k), Array.isArray(v) ? v.join(' ') : v) - } - } - } - } -} diff --git a/packages/x6-vector/src/vector/vector/font.test.ts b/packages/x6-vector/src/vector/vector/font.test.ts deleted file mode 100644 index 7c49bcac4fd..00000000000 --- a/packages/x6-vector/src/vector/vector/font.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import sinon from 'sinon' -import { SVG } from '../svg/svg' -import { Text } from '../text/text' - -describe('Vector', () => { - describe('font()', () => { - let svg: SVG - let txt: Text - - beforeEach(() => { - svg = new SVG().appendTo(document.body) - txt = svg.text('Some text') - }) - - afterEach(() => { - svg.remove() - }) - - it('should set leading when given', function () { - const spy = spyOn(txt, 'leading') - txt.font({ leading: 3 }) - expect(spy).toHaveBeenCalledWith(3) - }) - - it('should sets text-anchor when anchor given', function () { - const spy = sinon.spy(txt, 'attr') - txt.font({ anchor: 'start' }) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual(['text-anchor', 'start']) - }) - - it('should set all font properties via attr()', function () { - const spy = spyOn(txt, 'attr') - txt.font({ - size: 20, - family: 'Verdana', - weight: 'bold', - stretch: 'wider', - variant: 'small-caps', - style: 'italic', - }) - expect(spy).toHaveBeenCalledWith('font-size', 20) - expect(spy).toHaveBeenCalledWith('font-family', 'Verdana') - expect(spy).toHaveBeenCalledWith('font-weight', 'bold') - expect(spy).toHaveBeenCalledWith('font-stretch', 'wider') - expect(spy).toHaveBeenCalledWith('font-variant', 'small-caps') - expect(spy).toHaveBeenCalledWith('font-style', 'italic') - }) - - it('should redirect all other stuff directly to attr()', function () { - const spy = spyOn(txt, 'attr') - txt.font({ - foo: 'bar', - bar: 'baz', - } as any) - expect(spy).toHaveBeenCalledWith('foo', 'bar') - expect(spy).toHaveBeenCalledWith('bar', 'baz') - }) - - it('should set key value pair', function () { - const spy = spyOn(txt, 'attr') - txt.font('size', 20) - expect(spy).toHaveBeenCalledWith('font-size', 20) - }) - - it('should get value if called with one parameter', function () { - const spy = spyOn(txt, 'attr') - txt.font('size') - expect(spy).toHaveBeenCalledWith('font-size', undefined) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/vector/font.ts b/packages/x6-vector/src/vector/vector/font.ts deleted file mode 100644 index 51e345eeffc..00000000000 --- a/packages/x6-vector/src/vector/vector/font.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Base } from '../common/base' -import { Text } from '../text/text' - -export class FontStyle< - TSVGElement extends SVGElement = SVGElement, -> extends Base { - font(key: string): string | number - font(attrs: FontStyle.Attributes): this - font(key: string, value: string | number | null | undefined): this - font(a: FontStyle.Attributes | string, v?: string | number) { - if (typeof a === 'object') { - Object.keys(a).forEach((key: keyof FontStyle.Attributes) => - this.font(key, a[key]), - ) - return this - } - - if (a === 'leading') { - const text = this as any as Text // eslint-disable-line - if (text.leading) { - return text.leading(v) - } - } - - return a === 'anchor' - ? this.attr('text-anchor', v) - : a === 'size' || - a === 'family' || - a === 'weight' || - a === 'stretch' || - a === 'variant' || - a === 'style' - ? this.attr(`font-${a}`, v) - : this.attr(a, v) - } -} - -export namespace FontStyle { - export interface Attributes { - leading?: number - anchor?: string | number | null - size?: string | number | null - family?: string | null - weight?: string | number | null - stretch?: string | null - variant?: string | null - style?: string | null - } -} diff --git a/packages/x6-vector/src/vector/vector/mixins.ts b/packages/x6-vector/src/vector/vector/mixins.ts deleted file mode 100644 index f9a6da38132..00000000000 --- a/packages/x6-vector/src/vector/vector/mixins.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { applyMixins } from '../../util/mixin' -import { ElementExtension as AExtension } from '../a/exts' -import { ElementExtension as MaskExtension } from '../mask/exts' -import { ElementExtension as DescExtension } from '../desc/exts' -import { ElementExtension as FilterExtension } from '../filter/exts' -import { ElementExtension as TitleExtension } from '../title/exts' -import { ElementExtension as StyleExtension } from '../style/exts' -import { ElementExtension as ClipPathExtension } from '../clippath/exts' -import { Base } from '../common/base' -import { Vector } from './vector' -import { Overrides } from './overrides' -import { BBox } from './bbox' -import { FontStyle } from './font' -import { Transform } from './transform' -import { FillStroke } from './fillstroke' - -declare module './vector' { - interface Vector - extends Base, - FillStroke, - BBox, - AExtension, - DescExtension, - TitleExtension, - FilterExtension, - ClipPathExtension, - FontStyle, - Transform, - MaskExtension, - StyleExtension {} -} - -applyMixins( - Vector, - Base, - Overrides, - BBox, - AExtension, - DescExtension, - TitleExtension, - FilterExtension, - ClipPathExtension, - FontStyle, - Transform, - FillStroke, - MaskExtension, - StyleExtension, -) diff --git a/packages/x6-vector/src/vector/vector/overrides.ts b/packages/x6-vector/src/vector/vector/overrides.ts deleted file mode 100644 index 911a14f7791..00000000000 --- a/packages/x6-vector/src/vector/vector/overrides.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Util } from '../../dom/attributes/util' -import { AttributesBase } from '../../dom/attributes/base' - -export const defaultAttributes = { - // fill and stroke - fillOpacity: 1, - strokeOpacity: 1, - strokeWidth: 0, - strokeLinejoin: 'miter', - strokeLinecap: 'butt', - fill: '#000000', - stroke: '#000000', - opacity: 1, - - // position - x: 0, - y: 0, - cx: 0, - cy: 0, - - // size - width: 0, - height: 0, - - // radius - r: 0, - rx: 0, - ry: 0, - - // gradient - offset: 0, - stopOpacity: 1, - stopColor: '#000000', - - // text - textAnchor: 'start', -} - -export class Overrides extends AttributesBase { - attr(attributeName: any, attributeValue: any) { - if ( - typeof attributeName === 'string' && - typeof attributeValue === 'undefined' - ) { - const val = super.attr(attributeName) - if (typeof val === 'undefined') { - return defaultAttributes[ - Util.camelCase(attributeName) as keyof typeof defaultAttributes - ] - } - return val - } - return super.attr(attributeName, attributeValue) - } -} diff --git a/packages/x6-vector/src/vector/vector/transform.test.ts b/packages/x6-vector/src/vector/vector/transform.test.ts deleted file mode 100644 index 1279897729d..00000000000 --- a/packages/x6-vector/src/vector/vector/transform.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import sinon from 'sinon' -import { Global } from '../../global' -import { Matrix } from '../../struct/matrix' -import { Point } from '../../struct/point' -import { G } from '../g/g' -import { Rect } from '../rect/rect' -import { SVG } from '../svg/svg' - -describe('Vector', () => { - describe('toParent()', () => { - it('should return itself', () => { - const svg = new SVG() - const g = svg.group() - const rect = g.rect(100, 100) - expect(rect.toParent(svg)).toBe(rect) - }) - - it('should do nothing if the parent is the the current element', () => { - const svg = new SVG().appendTo(document.body) - const g = svg.group() - const parent = g.parent() - const index = g.index() - g.toParent(g) - expect(g.parent()).toBe(parent) - expect(g.index()).toBe(index) - svg.remove() - }) - - it('should move the element to a different container without changing its visual representation [1]', () => { - const svg = new SVG().appendTo(document.body) - const g = svg.group().matrix(1, 0, 1, 1, 0, 1) - const rect = g.rect(100, 100) - rect.toParent(svg) - expect(rect.matrix()).toEqual(new Matrix(1, 0, 1, 1, 0, 1)) - expect(rect.parent()).toBe(svg) - svg.remove() - }) - - it('should move the element to a different container without changing its visual representation [2]', () => { - const svg = new SVG().appendTo(document.body) - const g = svg.group().translate(10, 20) - const rect = g.rect(100, 100) - const g2 = svg.group().rotate(10) - rect.toParent(g2) - const actual = rect.matrix() - const expected = new Matrix().translate(10, 20).rotate(-10) - - const factors = 'abcdef'.split('') - // funny enough the dom seems to shorten the floats and precision gets lost - factors.forEach((prop: 'a') => - expect(actual[prop]).toBeCloseTo(expected[prop], 5), - ) - - svg.remove() - }) - - it('should insert the element at the specified position', () => { - const svg = new SVG() - const g = svg.group() - const rect = g.rect(100, 100) - svg.rect(100, 100) - svg.rect(100, 100) - expect(rect.toParent(svg, 2).index()).toBe(2) - }) - }) - - describe('toRoot()', () => { - it('should call `toParent()` with root node', () => { - const svg = new SVG() - const g = svg.group().matrix(1, 0, 1, 1, 0, 1) - const rect = g.rect(100, 100) - const spy = sinon.spy(rect, 'toParent') - rect.toRoot(3) - expect(spy.callCount).toEqual(1) - expect(spy.args[0]).toEqual([svg, 3]) - }) - - it('should do nothing when the element do not have a root', () => { - const g = new G() - const rect = g.rect() - rect.toRoot() - expect(rect.parent()).toBe(g) - }) - }) - - describe('toLocalPoint()', () => { - it('should transform a screen point into the coordinate system of the element', () => { - const rect = new Rect() - spyOn(rect, 'screenCTM').and.callFake( - () => new Matrix(1, 0, 0, 1, 20, 20), - ) - expect(rect.toLocalPoint({ x: 10, y: 10 })).toEqual(new Point(-10, -10)) - expect(rect.toLocalPoint(10, 10)).toEqual(new Point(-10, -10)) - }) - }) - - describe('ctm()', () => { - it('should return the native ctm wrapped into a matrix', () => { - const rect = new Rect() - const spy = sinon.spy(rect.node, 'getCTM') - rect.ctm() - expect(spy.callCount).toEqual(1) - }) - }) - - describe('screenCTM()', () => { - it('should return the native screenCTM wrapped into a matrix for a normal element', () => { - const rect = new Rect() - const spy = sinon.spy(rect.node, 'getScreenCTM') - rect.screenCTM() - expect(spy.callCount).toEqual(1) - }) - - it('should do extra work for nested svgs because firefox needs it', () => { - const spy = sinon.spy( - Global.window.SVGGraphicsElement.prototype, - 'getScreenCTM', - ) - const svg = new SVG().nested() - svg.screenCTM() - expect(spy.callCount).toEqual(1) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/vector/transform.ts b/packages/x6-vector/src/vector/vector/transform.ts deleted file mode 100644 index 53c889add09..00000000000 --- a/packages/x6-vector/src/vector/vector/transform.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Matrix } from '../../struct/matrix' -import { Point } from '../../struct/point' -import { Base } from '../common/base' -import type { SVG } from '../svg/svg' - -export class Transform< - TSVGElement extends SVGElement = SVGElement, -> extends Base { - /** - * Moves an element to a different parent (similar to addTo), but without - * changing its visual representation. All transformations are merged and - * applied to the element. - */ - toParent(parent: Transform, index?: number): this { - if (this !== parent) { - const ctm = this.screenCTM() - const pCtm = parent.screenCTM().inverse() - - this.addTo(parent, index).untransform().transform(pCtm.multiply(ctm)) - } - - return this - } - - /** - * Moves an element to the root svg (similar to addTo), but without - * changing its visual representation. All transformations are merged and - * applied to the element. - */ - toRoot(index?: number): this { - const root = this.root() - if (root) { - return this.toParent(root, index) - } - return this - } - - /** - * Transforms a point from screen coordinates to the elements coordinate system. - */ - toLocalPoint(p: Point.PointLike): Point - toLocalPoint(x: number, y: number): Point - toLocalPoint(x: number | Point.PointLike, y?: number) { - const p = typeof x === 'number' ? new Point(x, y) : new Point(x) - return p.transform(this.screenCTM().inverse()) - } - - ctm() { - const node = this.node as any as SVGGraphicsElement - return new Matrix(node.getCTM()) - } - - screenCTM() { - /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537 - This is needed because FF does not return the transformation matrix - for the inner coordinate system when getScreenCTM() is called on nested svgs. - However all other Browsers do that */ - const svg = this as any as SVG - if (typeof svg.isRoot === 'function' && !svg.isRoot()) { - const rect = svg.rect(1, 1) - const m = rect.node.getScreenCTM() - rect.remove() - return new Matrix(m) - } - - const node = this.node as any as SVGGraphicsElement - return new Matrix(node.getScreenCTM()) - } -} diff --git a/packages/x6-vector/src/vector/vector/vector.test.ts b/packages/x6-vector/src/vector/vector/vector.test.ts deleted file mode 100644 index e5cc6e952b5..00000000000 --- a/packages/x6-vector/src/vector/vector/vector.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { Rect } from '../rect/rect' - -describe('Vector', () => { - let rect: Rect - - beforeEach(() => { - rect = new Rect() - }) - - describe('x()', () => { - it('should call attr with x', () => { - const spy = spyOn(rect, 'attr') - rect.x(123) - expect(spy).toHaveBeenCalledWith('x', 123) - }) - }) - - describe('y()', () => { - it('should call attr with y', () => { - const spy = spyOn(rect, 'attr') - rect.y(123) - expect(spy).toHaveBeenCalledWith('y', 123) - }) - }) - - describe('move()', () => { - it('should call `x()` and `y()` with passed parameters and return itself', () => { - const spyx = spyOn(rect, 'x').and.callThrough() - const spyy = spyOn(rect, 'y').and.callThrough() - expect(rect.move(1, 2)).toBe(rect) - expect(spyx).toHaveBeenCalledWith(1) - expect(spyy).toHaveBeenCalledWith(2) - }) - }) - - describe('cx()', () => { - it('should return the elements center along the x axis', () => { - rect.attr({ x: 10, width: 100 }) - expect(rect.cx()).toBe(60) - }) - - it('should center the element along the x axis and return itself', () => { - rect.attr({ x: 10, width: 100 }) - expect(rect.cx(100)).toBe(rect) - expect(rect.attr('x')).toBe(50) - }) - }) - - describe('cy()', () => { - it('should return the elements center along the y axis', () => { - rect.attr({ y: 10, height: 100 }) - expect(rect.cy()).toBe(60) - }) - - it('should center the element along the y axis and return itself', () => { - rect.attr({ y: 10, height: 100 }) - expect(rect.cy(100)).toBe(rect) - expect(rect.attr('y')).toBe(50) - }) - }) - - describe('center()', () => { - it('should call `cx()` and `cy()` with passed parameters and return itself', () => { - const spyCx = spyOn(rect, 'cx').and.callThrough() - const spyCy = spyOn(rect, 'cy').and.callThrough() - expect(rect.center(1, 2)).toBe(rect) - expect(spyCx).toHaveBeenCalledWith(1) - expect(spyCy).toHaveBeenCalledWith(2) - }) - }) - - describe('dx()', () => { - it('should move the element along the x axis relatively and return itself', () => { - rect.attr({ x: 10, width: 100 }) - expect(rect.dx(100)).toBe(rect) - expect(rect.attr('x')).toBe(110) - }) - }) - - describe('dy()', () => { - it('should move the element along the x axis relatively and return itself', () => { - rect.attr({ y: 10, height: 100 }) - expect(rect.dy(100)).toBe(rect) - expect(rect.attr('y')).toBe(110) - }) - }) - - describe('dmove()', () => { - it('should call `dx()` and `dy()` with passed parameters and return itself', () => { - const spyDx = spyOn(rect, 'dx').and.callThrough() - const spyDy = spyOn(rect, 'dy').and.callThrough() - expect(rect.dmove(1, 2)).toBe(rect) - expect(spyDx).toHaveBeenCalledWith(1) - expect(spyDy).toHaveBeenCalledWith(2) - }) - }) - - describe('width()', () => { - it('should call attr with width', () => { - const spy = spyOn(rect, 'attr') - rect.width(123) - expect(spy).toHaveBeenCalledWith('width', 123) - }) - }) - - describe('height()', () => { - it('should call attr with height', () => { - const spy = spyOn(rect, 'attr') - rect.height(123) - expect(spy).toHaveBeenCalledWith('height', 123) - }) - }) - - describe('size()', () => { - it('should call `width()` and `height()` with passed parameters and return itself', () => { - const spyWidth = spyOn(rect, 'width').and.callThrough() - const spyHeight = spyOn(rect, 'height').and.callThrough() - expect(rect.size(1, 2)).toBe(rect) - expect(spyWidth).toHaveBeenCalledWith(1) - expect(spyHeight).toHaveBeenCalledWith(2) - }) - - it('should change height proportionally if null', () => { - const rect = Rect.create(100, 100) - const spyWidth = spyOn(rect, 'width').and.callThrough() - const spyHeight = spyOn(rect, 'height').and.callThrough() - expect(rect.size(200, null)).toBe(rect) - expect(spyWidth).toHaveBeenCalledWith(200) - expect(spyHeight).toHaveBeenCalledWith(200) - }) - - it('should change width proportionally if null', () => { - const rect = Rect.create(100, 100) - const spyWidth = spyOn(rect, 'width').and.callThrough() - const spyHeight = spyOn(rect, 'height').and.callThrough() - expect(rect.size(null, 200)).toBe(rect) - expect(spyWidth).toHaveBeenCalledWith(200) - expect(spyHeight).toHaveBeenCalledWith(200) - }) - }) -}) diff --git a/packages/x6-vector/src/vector/vector/vector.ts b/packages/x6-vector/src/vector/vector/vector.ts deleted file mode 100644 index aedff26b650..00000000000 --- a/packages/x6-vector/src/vector/vector/vector.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { UnitNumber } from '../../struct/unit-number' -import { Size } from '../common/size' -import { Base } from '../common/base' - -@Vector.register('Vector') -export class Vector< - TSVGElement extends SVGElement = SVGElement, -> extends Base { - width(): number - width(width: string | number | null): this - width(width?: string | number | null) { - return this.attr('width', width) - } - - height(): number - height(height: string | number | null): this - height(height?: string | number | null) { - return this.attr('height', height) - } - - size(width: string | number, height: string | number): this - size(width: string | number, height: string | number | null | undefined): this - size(width: string | number | null | undefined, height: string | number): this - size(width?: string | number | null, height?: string | number | null) { - const size = Size.normalize(this, width, height) - return this.width(size.width).height(size.height) - } - - x(): number - x(x: string | number | null): this - x(x?: string | number | null) { - return this.attr('x', x) - } - - y(): number - y(y: string | number | null): this - y(y?: string | number | null) { - return this.attr('y', y) - } - - move(x: string | number = 0, y: string | number = 0) { - return this.x(x).y(y) - } - - cx(): number - cx(x: null): number - cx(x: string | number): this - cx(x?: string | number | null) { - return x == null - ? this.x() + this.width() / 2 - : this.x(UnitNumber.minus(x, this.width() / 2)) - } - - cy(): number - cy(y: null): number - cy(y: string | number | null): this - cy(y?: string | number | null) { - return y == null - ? this.y() + this.height() / 2 - : this.y(UnitNumber.minus(y, this.height() / 2)) - } - - center(x: string | number, y: string | number) { - return this.cx(x).cy(y) - } - - dx(x: string | number) { - return this.x(UnitNumber.plus(x, this.x())) - } - - dy(y: string | number) { - return this.y(UnitNumber.plus(y, this.y())) - } - - dmove(x: string | number = 0, y: string | number = 0) { - return this.dx(x).dy(y) - } -} diff --git a/packages/x6-vector/src/vector/view/types.ts b/packages/x6-vector/src/vector/view/types.ts deleted file mode 100644 index f96d770f6f4..00000000000 --- a/packages/x6-vector/src/vector/view/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes, -} from '../types/attributes-core' - -export interface SVGViewAttributes - extends SVGCommonAttributes, - SVGStyleAttributes, - SVGConditionalProcessingAttributes { - viewBox?: string - preserveAspectRatio?: string - zoomAndPan?: 'disable' | 'magnify' - viewTarget?: string -} diff --git a/packages/x6-vector/tsconfig.json b/packages/x6-vector/tsconfig.json deleted file mode 100644 index 4082f16a5d9..00000000000 --- a/packages/x6-vector/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../tsconfig.json" -} diff --git a/packages/x6-vue-shape/package.json b/packages/x6-vue-shape/package.json index 0cf04de920a..17e681b41aa 100644 --- a/packages/x6-vue-shape/package.json +++ b/packages/x6-vue-shape/package.json @@ -49,7 +49,7 @@ }, "peerDependencies": { "@antv/x6-common": ">=2.0.6-beta.0", - "@antv/x6-next": ">=2.0.6-beta.0", + "@antv/x6": ">=2.0.6-beta.0", "@vue/composition-api": "^1.0.0-rc.6", "vue": "^2.6.12 || ^3.0.0" }, diff --git a/packages/x6-vue-shape/rollup.config.js b/packages/x6-vue-shape/rollup.config.js index fcc8acb2d93..b5ad6b76c7f 100644 --- a/packages/x6-vue-shape/rollup.config.js +++ b/packages/x6-vue-shape/rollup.config.js @@ -10,10 +10,10 @@ export default config({ globals: { vue: 'Vue', '@vue/composition-api': 'VueCompositionAPI', - '@antv/x6-next': 'X6', + '@antv/x6': 'X6', '@antv/x6-common': 'X6Common', }, }, ], - external: ['@antv/x6-next', '@antv/x6-common', '@vue/composition-api', 'vue'], + external: ['@antv/x6', '@antv/x6-common', '@vue/composition-api', 'vue'], }) diff --git a/packages/x6-vue-shape/src/node.ts b/packages/x6-vue-shape/src/node.ts index ac40ed4a9a2..22472acc207 100644 --- a/packages/x6-vue-shape/src/node.ts +++ b/packages/x6-vue-shape/src/node.ts @@ -1,4 +1,4 @@ -import { Markup, Node } from '@antv/x6-next' +import { Markup, Node } from '@antv/x6' import { ObjectExt } from '@antv/x6-common' export class VueShape< diff --git a/packages/x6-vue-shape/src/registry.ts b/packages/x6-vue-shape/src/registry.ts index 4f7a2d32103..cb2ca8402cc 100644 --- a/packages/x6-vue-shape/src/registry.ts +++ b/packages/x6-vue-shape/src/registry.ts @@ -1,4 +1,4 @@ -import { Graph, Node } from '@antv/x6-next' +import { Graph, Node } from '@antv/x6' export type VueShapeConfig = Node.Properties & { shape: string diff --git a/packages/x6-vue-shape/src/view.ts b/packages/x6-vue-shape/src/view.ts index f0a355b6aca..9177ae72185 100644 --- a/packages/x6-vue-shape/src/view.ts +++ b/packages/x6-vue-shape/src/view.ts @@ -1,4 +1,4 @@ -import { NodeView } from '@antv/x6-next' +import { NodeView } from '@antv/x6' import { isVue2, isVue3, createApp, h, Vue2 } from 'vue-demi' import { VueShape } from './node' import { shapeMaps } from './registry' diff --git a/packages/x6/CHANGELOG.md b/packages/x6/CHANGELOG.md deleted file mode 100644 index e153c136b29..00000000000 --- a/packages/x6/CHANGELOG.md +++ /dev/null @@ -1,1584 +0,0 @@ -## @antv/x6 [1.30.2](https://github.com/antvis/x6/compare/@antv/x6@1.30.1...@antv/x6@1.30.2) (2022-03-10) - - -### Bug Fixes - -* 🐛 optimize setText for edge-editor ([#1877](https://github.com/antvis/x6/issues/1877)) ([928b678](https://github.com/antvis/x6/commit/928b678f35ddf495ce73a405d0e0d1f568b6d660)) - -## @antv/x6 [1.30.1](https://github.com/antvis/x6/compare/@antv/x6@1.30.0...@antv/x6@1.30.1) (2022-02-21) - - -### Bug Fixes - -* 🐛 fix source graph and target graph not updating synchronously when transform in panning mode ([#1832](https://github.com/antvis/x6/issues/1832)) ([0de1f22](https://github.com/antvis/x6/commit/0de1f2227158fc8c6c6f762de64b8a50118292a9)) - -# @antv/x6 [1.30.0](https://github.com/antvis/x6/compare/@antv/x6@1.29.6...@antv/x6@1.30.0) (2022-01-22) - - -### Bug Fixes - -* 🐛 not create label when text is empty ([#1783](https://github.com/antvis/x6/issues/1783)) ([ed1fcd1](https://github.com/antvis/x6/commit/ed1fcd1f26601150d1b7913b8eaaf329a958af53)) - - -### Features - -* ✨ add insertPort api ([#1763](https://github.com/antvis/x6/issues/1763)) ([6809dba](https://github.com/antvis/x6/commit/6809dba2d86308d0b315d0c6164f91d80e8a40ff)) - -## @antv/x6 [1.29.6](https://github.com/antvis/x6/compare/@antv/x6@1.29.5...@antv/x6@1.29.6) (2022-01-13) - - -### Bug Fixes - -* node input cannot get focus when keyboard options.global is enabled ([#1754](https://github.com/antvis/x6/issues/1754)) ([61b26b7](https://github.com/antvis/x6/commit/61b26b70688164de1a794029cd877f6ecdc98fb2)), closes [Close#1753](https://github.com/Close/issues/1753) - -## @antv/x6 [1.29.5](https://github.com/antvis/x6/compare/@antv/x6@1.29.4...@antv/x6@1.29.5) (2022-01-10) - - -### Bug Fixes - -* 🐛 change startBatch to stopBatch ([#1744](https://github.com/antvis/x6/issues/1744)) ([7d9a2de](https://github.com/antvis/x6/commit/7d9a2deb4cd0d5bccc14a75af45daa9c4215b076)) - -## @antv/x6 [1.29.4](https://github.com/antvis/x6/compare/@antv/x6@1.29.3...@antv/x6@1.29.4) (2022-01-08) - - -### Bug Fixes - -* 🐛 stop panning when mouseleave body ([#1741](https://github.com/antvis/x6/issues/1741)) ([d23ea46](https://github.com/antvis/x6/commit/d23ea460109e3f487dbc249db9545e8d440f0789)) - -## @antv/x6 [1.29.3](https://github.com/antvis/x6/compare/@antv/x6@1.29.2...@antv/x6@1.29.3) (2022-01-08) - - -### Bug Fixes - -* 🐛 add updateCellId api ([#1739](https://github.com/antvis/x6/issues/1739)) ([78cdb3b](https://github.com/antvis/x6/commit/78cdb3bd56e7655ffcb2e5046d00f5d4f932cd3c)) - -## @antv/x6 [1.29.2](https://github.com/antvis/x6/compare/@antv/x6@1.29.1...@antv/x6@1.29.2) (2022-01-07) - - -### Bug Fixes - -* 🐛 add zindex for x6-widget-dne ([#1722](https://github.com/antvis/x6/issues/1722)) ([bf5b932](https://github.com/antvis/x6/commit/bf5b9329bd0d929ecba66c82af7f1ad934663669)) -* fix typo in model ([#1720](https://github.com/antvis/x6/issues/1720)) ([4119a1e](https://github.com/antvis/x6/commit/4119a1e62420e765dedac55311ca80d9c5c4441b)) - -## @antv/x6 [1.29.1](https://github.com/antvis/x6/compare/@antv/x6@1.29.0...@antv/x6@1.29.1) (2021-12-24) - - -### Bug Fixes - -* 🐛 add exception check for isLine and isCurve ([#1686](https://github.com/antvis/x6/issues/1686)) ([25c17b7](https://github.com/antvis/x6/commit/25c17b7679589b065c77625526ac0884fef05035)) -* 🐛 unexpected feedback in "vertices" edge tool ([#1680](https://github.com/antvis/x6/issues/1680)) ([307584f](https://github.com/antvis/x6/commit/307584f017cedcaf9bc4f4e5b4eefdcabcb35926)), closes [antvis/X6#1679](https://github.com/antvis/X6/issues/1679) - -# @antv/x6 [1.29.0](https://github.com/antvis/x6/compare/@antv/x6@1.28.1...@antv/x6@1.29.0) (2021-12-17) - - -### Bug Fixes - -* 🐛 fix lint error ([#1649](https://github.com/antvis/x6/issues/1649)) ([f793682](https://github.com/antvis/x6/commit/f7936826f026169ccf094cee28f54bb13d15ef9b)) -* 🐛 get children or parent delay ([#1641](https://github.com/antvis/x6/issues/1641)) ([8508b91](https://github.com/antvis/x6/commit/8508b91da297fef7819d6404854f4592466dec62)) -* 🐛 listen mousedown event on scroller container ([#1642](https://github.com/antvis/x6/issues/1642)) ([7687cb2](https://github.com/antvis/x6/commit/7687cb201a7b2be38b67524f99d4226b9e3e30d1)) -* 🐛 release x6 v1.28.2 ([#1654](https://github.com/antvis/x6/issues/1654)) ([745b90a](https://github.com/antvis/x6/commit/745b90ac94dbbd9443ecf1456e6a5aa9eb646594)) -* 🐛 update x6 version ([#1655](https://github.com/antvis/x6/issues/1655)) ([07037be](https://github.com/antvis/x6/commit/07037beb59537d0feaa47ac1ab629d8c9b8c3a8b)) -* 🐛 update x6-vector version ([#1656](https://github.com/antvis/x6/issues/1656)) ([d4d2125](https://github.com/antvis/x6/commit/d4d21251cc42d263327ea72edb8a038d7ef71c89)) - - -### Features - -* ✨ update x6 and x6-vector version ([#1659](https://github.com/antvis/x6/issues/1659)) ([a199b59](https://github.com/antvis/x6/commit/a199b590d5f108b51162e276b432fbb3737d2c14)) - -## @antv/x6 [1.28.1](https://github.com/antvis/x6/compare/@antv/x6@1.28.0...@antv/x6@1.28.1) (2021-10-08) - - -### Bug Fixes - -* 🐛 fiter nodes when rubberband ([#1408](https://github.com/antvis/x6/issues/1408)) ([35ad628](https://github.com/antvis/x6/commit/35ad628a1b51630aedbed038eb0ff05c6575ca09)) - -# @antv/x6 [1.28.0](https://github.com/antvis/x6/compare/@antv/x6@1.27.2...@antv/x6@1.28.0) (2021-10-02) - - -### Bug Fixes - -* 🐛 modify the font size even if the text does not change ([#1397](https://github.com/antvis/x6/issues/1397)) ([f93c290](https://github.com/antvis/x6/commit/f93c290abd8e546e76feb914c4f0457aa6f1bbbf)) -* 🐛 stop propagation when enable following ([#1398](https://github.com/antvis/x6/issues/1398)) ([59cbe44](https://github.com/antvis/x6/commit/59cbe44cf308e7b452ac0fba37dd19c86324e475)) - - -### Features - -* ✨ add batch for selection ([#1399](https://github.com/antvis/x6/issues/1399)) ([276abac](https://github.com/antvis/x6/commit/276abac2949bd6c2160e749e0d4e41d9201ae08e)) -* ✨ add excludeHiddenNodes for manhattan router ([#1400](https://github.com/antvis/x6/issues/1400)) ([c57908f](https://github.com/antvis/x6/commit/c57908fc7e6183319f27a8acc0e3567e76d11e6e)) - -## @antv/x6 [1.27.2](https://github.com/antvis/x6/compare/@antv/x6@1.27.1...@antv/x6@1.27.2) (2021-10-01) - - -### Bug Fixes - -* 🐛 can not select flat edge ([#1394](https://github.com/antvis/x6/issues/1394)) ([89ae2cc](https://github.com/antvis/x6/commit/89ae2ccdc02ae2dfb8d4f9a1b10dd81a77a59b10)) - - -### Performance Improvements - -* ⚡️ improve box selection performance ([#1393](https://github.com/antvis/x6/issues/1393)) ([175aa58](https://github.com/antvis/x6/commit/175aa587202fa687555815a448c9bf80926b166e)) - -## @antv/x6 [1.27.1](https://github.com/antvis/x6/compare/@antv/x6@1.27.0...@antv/x6@1.27.1) (2021-10-01) - - -### Bug Fixes - -* 🐛 draw background multiple times to show the last image ([#1389](https://github.com/antvis/x6/issues/1389)) ([e44098d](https://github.com/antvis/x6/commit/e44098d04b886d9b54b752bee2dcaa57db6b5a3f)) - -# @antv/x6 [1.27.0](https://github.com/antvis/x6/compare/@antv/x6@1.26.3...@antv/x6@1.27.0) (2021-09-30) - - -### Bug Fixes - -* 🐛 normalize event before get clientX ([#1387](https://github.com/antvis/x6/issues/1387)) ([ba4aedc](https://github.com/antvis/x6/commit/ba4aedc49793fa681cd73a4790ab4dc34e3cf936)) - - -### Features - -* ✨ add resizeGroup for stencil ([#1388](https://github.com/antvis/x6/issues/1388)) ([4baba33](https://github.com/antvis/x6/commit/4baba3326bc9ecbbd9c272fcfa4ee3d3c0389db3)) - -## @antv/x6 [1.26.3](https://github.com/antvis/x6/compare/@antv/x6@1.26.2...@antv/x6@1.26.3) (2021-09-22) - - -### Bug Fixes - -* 🐛 fix option merge order in paste ([#1350](https://github.com/antvis/x6/issues/1350)) ([016303c](https://github.com/antvis/x6/commit/016303c936ccbc23e7bbb80c8461e4ba9fe8d14f)) -* segments & vertices tool add onChanged callback ([#1348](https://github.com/antvis/x6/issues/1348)) ([502856c](https://github.com/antvis/x6/commit/502856c3b89a4613859c7152ec212253ec7919d8)) - -## @antv/x6 [1.26.2](https://github.com/antvis/x6/compare/@antv/x6@1.26.1...@antv/x6@1.26.2) (2021-09-13) - -## @antv/x6 [1.26.1](https://github.com/antvis/x6/compare/@antv/x6@1.26.0...@antv/x6@1.26.1) (2021-08-31) - - -### Bug Fixes - -* 🐛 not stopPropagation when mousedown ([#1305](https://github.com/antvis/x6/issues/1305)) ([6a3f96d](https://github.com/antvis/x6/commit/6a3f96d3b5754def401084e023dad466c013cc49)) - -# @antv/x6 [1.26.0](https://github.com/antvis/x6/compare/@antv/x6@1.25.5...@antv/x6@1.26.0) (2021-08-24) - - -### Features - -* 🎸 为定义业务数据的 data 属性提供类型定义 ([#1278](https://github.com/antvis/x6/issues/1278)) ([412d035](https://github.com/antvis/x6/commit/412d035687c6caf235f2afb1fa23d830d2a5f002)) - -## @antv/x6 [1.25.5](https://github.com/antvis/x6/compare/@antv/x6@1.25.4...@antv/x6@1.25.5) (2021-08-18) - - -### Bug Fixes - -* 🐛 optimize get child method ([#1262](https://github.com/antvis/x6/issues/1262)) ([c7212bb](https://github.com/antvis/x6/commit/c7212bb1ec059f2d2379f0b6c8ffe0e6edd52aad)) - -## @antv/x6 [1.25.4](https://github.com/antvis/x6/compare/@antv/x6@1.25.3...@antv/x6@1.25.4) (2021-08-17) - - -### Bug Fixes - -* 🐛 add keepId for node clone ([#1254](https://github.com/antvis/x6/issues/1254)) ([7c51152](https://github.com/antvis/x6/commit/7c51152f03998c26b740eec640961193c29ebd34)) - -## @antv/x6 [1.25.3](https://github.com/antvis/x6/compare/@antv/x6@1.25.2...@antv/x6@1.25.3) (2021-08-06) - - -### Bug Fixes - -* 🐛 translateSelected nodes exclude self ([#1238](https://github.com/antvis/x6/issues/1238)) ([31a3442](https://github.com/antvis/x6/commit/31a3442c92265e12830b28de2988b23008ca4666)) - -## @antv/x6 [1.25.2](https://github.com/antvis/x6/compare/@antv/x6@1.25.1...@antv/x6@1.25.2) (2021-08-06) - - -### Bug Fixes - -* 🐛 fix scroller content size when not autoResize ([#1237](https://github.com/antvis/x6/issues/1237)) ([1010f39](https://github.com/antvis/x6/commit/1010f394e6a6390dae74ba30dfbb4493e5193ddc)) - -## @antv/x6 [1.25.1](https://github.com/antvis/x6/compare/@antv/x6@1.25.0...@antv/x6@1.25.1) (2021-08-03) - - -### Bug Fixes - -* 🐛 remove prop not remove attr ([#1225](https://github.com/antvis/x6/issues/1225)) ([ba2350d](https://github.com/antvis/x6/commit/ba2350dcc930a9a4ab350862db67a5415fe41cfd)) -* 🐛 Stencil searches are case sensitive ([#1211](https://github.com/antvis/x6/issues/1211)) ([c28289c](https://github.com/antvis/x6/commit/c28289c3c9f0552b14c81999542374649271e495)) - -# @antv/x6 [1.25.0](https://github.com/antvis/x6/compare/@antv/x6@1.24.8...@antv/x6@1.25.0) (2021-07-27) - - -### Bug Fixes - -* 🐛 change type of padding in ScaleContentToFitOptions ([#1203](https://github.com/antvis/x6/issues/1203)) ([692ea04](https://github.com/antvis/x6/commit/692ea043854095535273e7a9ff2e4f8809faee81)) - - -### Features - -* ✨ add cell-editor tool ([#1202](https://github.com/antvis/x6/issues/1202)) ([2286097](https://github.com/antvis/x6/commit/228609783e1b3a3ff579ce9d38cf2e70dacd4931)) - -## @antv/x6 [1.24.8](https://github.com/antvis/x6/compare/@antv/x6@1.24.7...@antv/x6@1.24.8) (2021-07-22) - - -### Bug Fixes - -* 🐛 auto scroller when selection over graph ([#1197](https://github.com/antvis/x6/issues/1197)) ([ffc801b](https://github.com/antvis/x6/commit/ffc801b9550fd55a03a2642051a84ef586d88ee7)) - -## @antv/x6 [1.24.7](https://github.com/antvis/x6/compare/@antv/x6@1.24.6...@antv/x6@1.24.7) (2021-07-21) - - -### Bug Fixes - -* 🐛 revert optimze find snap elem ([#1192](https://github.com/antvis/x6/issues/1192)) ([c7bbc27](https://github.com/antvis/x6/commit/c7bbc2747c2851401aabc94417f8bab6fee1e8ea)) - -## @antv/x6 [1.24.6](https://github.com/antvis/x6/compare/@antv/x6@1.24.5...@antv/x6@1.24.6) (2021-07-21) - -## @antv/x6 [1.24.5](https://github.com/antvis/x6/compare/@antv/x6@1.24.4...@antv/x6@1.24.5) (2021-07-09) - - -### Bug Fixes - -* 🐛 unified panning api ([#1151](https://github.com/antvis/x6/issues/1151)) ([fc78817](https://github.com/antvis/x6/commit/fc7881731bfc5c7cde698ca8948dfa0c40f891fc)) - -## @antv/x6 [1.24.4](https://github.com/antvis/x6/compare/@antv/x6@1.24.3...@antv/x6@1.24.4) (2021-06-23) - - -### Bug Fixes - -* 🐛 add toolsAddable config for interacting ([#1124](https://github.com/antvis/x6/issues/1124)) ([5dde09c](https://github.com/antvis/x6/commit/5dde09c4fc99e395947d8839adb67ab6c20abbe7)) - -## @antv/x6 [1.24.3](https://github.com/antvis/x6/compare/@antv/x6@1.24.2...@antv/x6@1.24.3) (2021-06-22) - - -### Bug Fixes - -* 🐛 fix alerts of lgtm ([3e75128](https://github.com/antvis/x6/commit/3e75128390a0e649cc2c9e4ee86eb4f534b99f23)) -* 🐛 fix type definitions ([a6922f8](https://github.com/antvis/x6/commit/a6922f81c80566877e2ca41af5261e6bb12aa4f5)) - -## @antv/x6 [1.24.2](https://github.com/antvis/x6/compare/@antv/x6@1.24.1...@antv/x6@1.24.2) (2021-06-21) - - -### Bug Fixes - -* 🐛 auto scroll graph when drag magnet ([#1121](https://github.com/antvis/x6/issues/1121)) ([fde89fc](https://github.com/antvis/x6/commit/fde89fcf14e4dec9d9bf15489290cc145c29e400)) - -## @antv/x6 [1.24.1](https://github.com/antvis/x6/compare/@antv/x6@1.24.0...@antv/x6@1.24.1) (2021-06-21) - - -### Bug Fixes - -* 🐛 revert fix scroller content size ([#1120](https://github.com/antvis/x6/issues/1120)) ([7546ea2](https://github.com/antvis/x6/commit/7546ea2b878d0037eec0362184464664cd797b69)) - -# @antv/x6 [1.24.0](https://github.com/antvis/x6/compare/@antv/x6@1.23.13...@antv/x6@1.24.0) (2021-06-19) - - -### Features - -* ✨ optimize selection event handle ([#1115](https://github.com/antvis/x6/issues/1115)) ([41fe23b](https://github.com/antvis/x6/commit/41fe23b6b959782487c0db03215fcfcf5c821b87)) - -## @antv/x6 [1.23.13](https://github.com/antvis/x6/compare/@antv/x6@1.23.12...@antv/x6@1.23.13) (2021-06-19) - - -### Bug Fixes - -* 🐛 preventScroll when focus graph ([#1116](https://github.com/antvis/x6/issues/1116)) ([9b66853](https://github.com/antvis/x6/commit/9b66853e143825de669fa5c97e1540971e835bed)) - -## @antv/x6 [1.23.12](https://github.com/antvis/x6/compare/@antv/x6@1.23.11...@antv/x6@1.23.12) (2021-06-17) - - -### Bug Fixes - -* 🐛 fix demo error in x6 sites ([#1108](https://github.com/antvis/x6/issues/1108)) ([575aba0](https://github.com/antvis/x6/commit/575aba08c8901cc7753adcf7568d44b5845bc6e4)) - -## @antv/x6 [1.23.11](https://github.com/antvis/x6/compare/@antv/x6@1.23.10...@antv/x6@1.23.11) (2021-06-17) - - -### Bug Fixes - -* 🐛 alerts on lgtm.com ([#1104](https://github.com/antvis/x6/issues/1104)) ([6eb34b1](https://github.com/antvis/x6/commit/6eb34b1d9a25462593ba5e4a69995cca5211bc0c)) - -## @antv/x6 [1.23.10](https://github.com/antvis/x6/compare/@antv/x6@1.23.9...@antv/x6@1.23.10) (2021-06-16) - - -### Bug Fixes - -* 🐛 fix node:move event triggered source ([cca97ff](https://github.com/antvis/x6/commit/cca97ff1ed44a45ca6ef0acf9b2c2d38070cb4cb)) -* 🐛 fix the embedded triggered source ([a922aa4](https://github.com/antvis/x6/commit/a922aa42b6a726fd7daf45d283d5b4a9c792e4a8)) -* 🐛 optimize cell remove method ([c6fd5da](https://github.com/antvis/x6/commit/c6fd5da9e5b8b89b3eff13f1026da0298ac397e9)) -* 🐛 specify return type ([cea7ae1](https://github.com/antvis/x6/commit/cea7ae11364d4bc521d41cc254d6c35484a34b8a)) -* update dependencies and fix type errors ([#1103](https://github.com/antvis/x6/issues/1103)) ([056b862](https://github.com/antvis/x6/commit/056b862b4efe7dbdc559cac7194c2453996acc07)) - -## @antv/x6 [1.23.9](https://github.com/antvis/x6/compare/@antv/x6@1.23.8...@antv/x6@1.23.9) (2021-06-15) - - -### Bug Fixes - -* 🐛 translateSelectionNodes is only triggered manually ([6d445da](https://github.com/antvis/x6/commit/6d445dac3a83f5e39d0ed8ce2b05e6055a6decaa)) - -## @antv/x6 [1.23.8](https://github.com/antvis/x6/compare/@antv/x6@1.23.7...@antv/x6@1.23.8) (2021-06-13) - - -### Bug Fixes - -* 🐛 not clean selection when mousedown ([d6195a7](https://github.com/antvis/x6/commit/d6195a74f3765d65fcdf42dd0de7739fb8867f75)) - -## @antv/x6 [1.23.7](https://github.com/antvis/x6/compare/@antv/x6@1.23.6...@antv/x6@1.23.7) (2021-06-11) - - -### Bug Fixes - -* 🐛 check target file before read it ([8e8be17](https://github.com/antvis/x6/commit/8e8be17818421042ac6c1bd3d0176d1f9b79bb20)) - -## @antv/x6 [1.23.6](https://github.com/antvis/x6/compare/@antv/x6@1.23.5...@antv/x6@1.23.6) (2021-06-09) - -## @antv/x6 [1.23.5](https://github.com/antvis/x6/compare/@antv/x6@1.23.4...@antv/x6@1.23.5) (2021-06-09) - - -### Bug Fixes - -* 🐛 fix background size when not defined, fixed [#1070](https://github.com/antvis/x6/issues/1070) ([#1072](https://github.com/antvis/x6/issues/1072)) ([ada59be](https://github.com/antvis/x6/commit/ada59be847cb139040c8ee8b68603756194201cd)) - -## @antv/x6 [1.23.4](https://github.com/antvis/x6/compare/@antv/x6@1.23.3...@antv/x6@1.23.4) (2021-06-09) - -## @antv/x6 [1.23.3](https://github.com/antvis/x6/compare/@antv/x6@1.23.2...@antv/x6@1.23.3) (2021-06-07) - - -### Bug Fixes - -* 🐛 fix scroller content size when not autoResize ([1a77b44](https://github.com/antvis/x6/commit/1a77b4464c6397c407d848adb17f8aa90131d63d)) - -## @antv/x6 [1.23.2](https://github.com/antvis/x6/compare/@antv/x6@1.23.1...@antv/x6@1.23.2) (2021-06-07) - - -### Bug Fixes - -* 🐛 optimize addTools params ([9fb3a51](https://github.com/antvis/x6/commit/9fb3a514c2d34dee61d1ee5acd68416ae5bd5cfa)) - -## @antv/x6 [1.23.1](https://github.com/antvis/x6/compare/@antv/x6@1.23.0...@antv/x6@1.23.1) (2021-06-02) - -# @antv/x6 [1.23.0](https://github.com/antvis/x6/compare/@antv/x6@1.22.1...@antv/x6@1.23.0) (2021-06-02) - - -### Features - -* ✨ support minimap in normal graph ([3ce87d9](https://github.com/antvis/x6/commit/3ce87d91a952e9b8bf61988cba1cc8cedd39b942)) - -## @antv/x6 [1.22.1](https://github.com/antvis/x6/compare/@antv/x6@1.22.0...@antv/x6@1.22.1) (2021-06-01) - - -### Bug Fixes - -* 🐛 fix bbox calc error in firefox ([328eb7e](https://github.com/antvis/x6/commit/328eb7e980fae6091ca94ce20f867f3694788446)) - -# @antv/x6 [1.22.0](https://github.com/antvis/x6/compare/@antv/x6@1.21.7...@antv/x6@1.22.0) (2021-05-27) - - -### Features - -* ✨ support panning scroller graph byrightmousedown ([2ceca37](https://github.com/antvis/x6/commit/2ceca3724e792dc0247398bf8790909597c50f31)) - -## @antv/x6 [1.21.7](https://github.com/antvis/x6/compare/@antv/x6@1.21.6...@antv/x6@1.21.7) (2021-05-18) - - -### Bug Fixes - -* 🐛 fix handledTranslation option passing to child node ([0244e0d](https://github.com/antvis/x6/commit/0244e0d919e9a7ebdd97dede4be456290560da8a)) - -## @antv/x6 [1.21.6](https://github.com/antvis/x6/compare/@antv/x6@1.21.5...@antv/x6@1.21.6) (2021-05-18) - - -### Bug Fixes - -* 🐛 fix node translation with selected children ([770d1d1](https://github.com/antvis/x6/commit/770d1d1e75575aaa7dce9f6c503af982393a076d)) - -## @antv/x6 [1.21.5](https://github.com/antvis/x6/compare/@antv/x6@1.21.4...@antv/x6@1.21.5) (2021-05-14) - -## @antv/x6 [1.21.4](https://github.com/antvis/x6/compare/@antv/x6@1.21.3...@antv/x6@1.21.4) (2021-05-12) - - -### Bug Fixes - -* 🐛 revert getBoundingRect change ([703eead](https://github.com/antvis/x6/commit/703eeade93332890ec8d7e4879043db2e8257a75)) - -## @antv/x6 [1.21.3](https://github.com/antvis/x6/compare/@antv/x6@1.21.2...@antv/x6@1.21.3) (2021-05-08) - - -### Bug Fixes - -* 🐛 only delay svg attr ([5d804a2](https://github.com/antvis/x6/commit/5d804a253cb56b71dbf551f95a6160a079a7f6d2)) - -## @antv/x6 [1.21.2](https://github.com/antvis/x6/compare/@antv/x6@1.21.1...@antv/x6@1.21.2) (2021-05-07) - - -### Bug Fixes - -* 🐛 fix getBBoxByElementAttr ([0a540da](https://github.com/antvis/x6/commit/0a540da92bfde24750ea5fd58df247d3dc2f77ce)) - -## @antv/x6 [1.21.1](https://github.com/antvis/x6/compare/@antv/x6@1.21.0...@antv/x6@1.21.1) (2021-05-07) - - -### Bug Fixes - -* 🐛 support css var in font-size ([c4dde7e](https://github.com/antvis/x6/commit/c4dde7e498c5b892a181e86d9908519bc0e2c200)) - -# @antv/x6 [1.21.0](https://github.com/antvis/x6/compare/@antv/x6@1.20.0...@antv/x6@1.21.0) (2021-05-06) - - -### Bug Fixes - -* 🐛 add onWheelGuard to prevent unwanted preventDefault ([#945](https://github.com/antvis/x6/issues/945)) ([3d567b1](https://github.com/antvis/x6/commit/3d567b14c3ba1da4522f94a3c8a325b5cf58012e)) -* 🐛 fix can not scroller by mousewheel ([dd511f8](https://github.com/antvis/x6/commit/dd511f81a9f96da969381c064188401dd009e42e)) - - -### Features - -* ✨ add rubberNode and rubberEdge config ([#949](https://github.com/antvis/x6/issues/949)) ([b715463](https://github.com/antvis/x6/commit/b71546332a91f7a894f453c691efee006fdf4ad0)) - -# @antv/x6 [1.20.0](https://github.com/antvis/x6/compare/@antv/x6@1.19.6...@antv/x6@1.20.0) (2021-05-04) - - -### Bug Fixes - -* 🐛 optimize usage of mousewheel ([a2851cd](https://github.com/antvis/x6/commit/a2851cd862c9224bb66a8f9bda3e0035259fd940)) - - -### Features - -* ✨ panning support rightMouseDown and mousehwheel ([3694cc0](https://github.com/antvis/x6/commit/3694cc080ca5d2a291ba6b2df8c25933bb39b80a)) - -## @antv/x6 [1.19.6](https://github.com/antvis/x6/compare/@antv/x6@1.19.5...@antv/x6@1.19.6) (2021-05-01) - - -### Bug Fixes - -* 🐛 update delay attrs async when node is not caller ([6e253fc](https://github.com/antvis/x6/commit/6e253fc140b289811442a9a89e4484cb1a7c285c)) -* 🐛 validateEdge is sync ([57a6f2b](https://github.com/antvis/x6/commit/57a6f2be0685c91dced5208103051ad30742e87f)) - -## @antv/x6 [1.19.5](https://github.com/antvis/x6/compare/@antv/x6@1.19.4...@antv/x6@1.19.5) (2021-05-01) - - -### Bug Fixes - -* 🐛 revert delay text attr tmp ([bd41f0c](https://github.com/antvis/x6/commit/bd41f0cba09df2aa029c645a2bc1a8515538dcb5)) - -## @antv/x6 [1.19.4](https://github.com/antvis/x6/compare/@antv/x6@1.19.3...@antv/x6@1.19.4) (2021-04-30) - - -### Bug Fixes - -* 🐛 del getBBoxOfNeatElement ([d031982](https://github.com/antvis/x6/commit/d031982c85ad1d52de1003958daeb26c4fb468c4)) -* 🐛 revert View.find ([88a31d1](https://github.com/antvis/x6/commit/88a31d167de8a2d1dbe3a7acf4fd8cb71cd3a80a)) - - -### Performance Improvements - -* ⚡️ add getMatrixByElementAttr and getBBoxByElementAttr to reduce reflow ([a5a83d3](https://github.com/antvis/x6/commit/a5a83d341646fdc83b2e9a300010e5e60bc75831)) -* ⚡️ optimize marker render ([6e90b6b](https://github.com/antvis/x6/commit/6e90b6b308a854dad97acf7c0577900731c08d9d)) -* ⚡️ optimize node render performance ([433d25f](https://github.com/antvis/x6/commit/433d25ff4a8cb6bab662f1a2317f59c1d41aa7bd)) -* ⚡️ reduce dom op in sortViews ([79800b8](https://github.com/antvis/x6/commit/79800b8c4c007513bfd57abee2c7066fdfdc678e)) - -## @antv/x6 [1.19.3](https://github.com/antvis/x6/compare/@antv/x6@1.19.2...@antv/x6@1.19.3) (2021-04-28) - - -### Bug Fixes - -* 🐛 add default route for createLines in jumpover ([cb48d31](https://github.com/antvis/x6/commit/cb48d31e58041764f9bbb5f00e285849afd9058e)) -* 🐛 fix validateEdge trigger timming ([8bc943d](https://github.com/antvis/x6/commit/8bc943de5ec800d3cff4f68d95bb4d0b1713449c)) - -## @antv/x6 [1.19.2](https://github.com/antvis/x6/compare/@antv/x6@1.19.1...@antv/x6@1.19.2) (2021-04-28) - - -### Bug Fixes - -* 🐛 check correct magnet in validateConnection ([866c795](https://github.com/antvis/x6/commit/866c79547408e3661a10421590ee5c34f93a53e3)) - -## @antv/x6 [1.19.1](https://github.com/antvis/x6/compare/@antv/x6@1.19.0...@antv/x6@1.19.1) (2021-04-28) - -# @antv/x6 [1.19.0](https://github.com/antvis/x6/compare/@antv/x6@1.18.5...@antv/x6@1.19.0) (2021-04-26) - - -### Bug Fixes - -* 🐛 add comment, want to release new version ([bfeae0c](https://github.com/antvis/x6/commit/bfeae0c4958f6440a5a245a094db237c074d255b)) - - -### Features - -* ✨ enhance scroller performance by avoid scoller reflow caused by changing classList ([#909](https://github.com/antvis/x6/issues/909)) ([1346223](https://github.com/antvis/x6/commit/134622397cab7312cb8e9cfc3f78901008a7b49f)) -* ✨ postpone keyboard target focusing to improve dragging performance ([f3c04ca](https://github.com/antvis/x6/commit/f3c04ca02945d14e7912a963a11fe89908e1a4c8)) - -## @antv/x6 [1.18.5](https://github.com/antvis/x6/compare/@antv/x6@1.18.4...@antv/x6@1.18.5) (2021-04-20) - - -### Bug Fixes - -* 🐛 don't change cell id when setProp ([44be23e](https://github.com/antvis/x6/commit/44be23ed5500a3b219ee4774990e8caee58269c3)) - -## @antv/x6 [1.18.4](https://github.com/antvis/x6/compare/@antv/x6@1.18.3...@antv/x6@1.18.4) (2021-04-17) - - -### Bug Fixes - -* 🐛 change port cursor style when is not magnet ([2a1aa21](https://github.com/antvis/x6/commit/2a1aa2167e73f0bf2992bf2ef264c0401025a3f3)) -* 🐛 src dir included in publication ([d2901a8](https://github.com/antvis/x6/commit/d2901a86a065dd5b6537fdf6d7a1249ed4e10a1e)) - -## @antv/x6 [1.18.3](https://github.com/antvis/x6/compare/@antv/x6@1.18.2...@antv/x6@1.18.3) (2021-04-13) - - -### Bug Fixes - -* 🐛 fix factor calculation in mousewheel ([#855](https://github.com/antvis/x6/issues/855)) ([8a3ecce](https://github.com/antvis/x6/commit/8a3eccec6856a8ff7bfb4464dfed43a0c27097a4)) - -## @antv/x6 [1.18.2](https://github.com/antvis/x6/compare/@antv/x6@1.18.1...@antv/x6@1.18.2) (2021-03-30) - -## @antv/x6 [1.18.1](https://github.com/antvis/x6/compare/@antv/x6@1.18.0...@antv/x6@1.18.1) (2021-03-30) - -# @antv/x6 [1.18.0](https://github.com/antvis/x6/compare/@antv/x6@1.17.7...@antv/x6@1.18.0) (2021-03-23) - - -### Features - -* ✨ revert a lint fix commit ([319f30f](https://github.com/antvis/x6/commit/319f30f5e68587623d85a2759142feaf37ac46fc)) - -## @antv/x6 [1.17.7](https://github.com/antvis/x6/compare/@antv/x6@1.17.6...@antv/x6@1.17.7) (2021-03-23) - - -### Bug Fixes - -* 🐛 fix eslint errors ([06ba121](https://github.com/antvis/x6/commit/06ba121e3b937c5aeebbbe2b24e6db67fc141cb9)) -* 🐛 fix karma can not process lodash-es ([f7ae6b1](https://github.com/antvis/x6/commit/f7ae6b1f6b961a01c58d8827a9aaa2d5a984a6e0)) - -## @antv/x6 [1.17.6](https://github.com/antvis/x6/compare/@antv/x6@1.17.5...@antv/x6@1.17.6) (2021-03-20) - - -### Bug Fixes - -* 🐛 fix model event trigger twice ([#789](https://github.com/antvis/x6/issues/789)) ([5520bc3](https://github.com/antvis/x6/commit/5520bc38ab4106287e6591c73aff8c6f96f675da)) - -## @antv/x6 [1.17.5](https://github.com/antvis/x6/compare/@antv/x6@1.17.4...@antv/x6@1.17.5) (2021-03-19) - - -### Bug Fixes - -* 🐛 restrict unembed condition ([2f797fd](https://github.com/antvis/x6/commit/2f797fd13754e3068f321800d1973a0ad3612d4d)) - -## @antv/x6 [1.17.4](https://github.com/antvis/x6/compare/@antv/x6@1.17.3...@antv/x6@1.17.4) (2021-03-19) - - -### Bug Fixes - -* 🐛 set the async of minimapGraph to be the same as sourceGraph ([474d93c](https://github.com/antvis/x6/commit/474d93c9bc54cb469d75b72e6cd956c9671afcd2)) - -## @antv/x6 [1.17.3](https://github.com/antvis/x6/compare/@antv/x6@1.17.2...@antv/x6@1.17.3) (2021-03-12) - - -### Bug Fixes - -* 🐛 fix add tools not work ([191eab3](https://github.com/antvis/x6/commit/191eab3d02bdea32755009d865a2929a131cb9e2)) - -## @antv/x6 [1.17.2](https://github.com/antvis/x6/compare/@antv/x6@1.17.1...@antv/x6@1.17.2) (2021-03-12) - - -### Bug Fixes - -* 🐛 fix not refresh after removeTool ([9d15243](https://github.com/antvis/x6/commit/9d152433ee4050bf2311c140d74dd805cae519ba)) - -## @antv/x6 [1.17.1](https://github.com/antvis/x6/compare/@antv/x6@1.17.0...@antv/x6@1.17.1) (2021-03-11) - - -### Bug Fixes - -* 🐛 add ToJSONData type ([f5ffbe2](https://github.com/antvis/x6/commit/f5ffbe2ffa50cb9585ee241aa1f37d5c069e97e5)) - -# @antv/x6 [1.17.0](https://github.com/antvis/x6/compare/@antv/x6@1.16.0...@antv/x6@1.17.0) (2021-03-11) - - -### Bug Fixes - -* 🐛 make sure css resource is disposed after all graph instance is disposed ([81fa537](https://github.com/antvis/x6/commit/81fa537282ce5cd65c2fb585b8a7649087490313)) - - -### Features - -* ✨ allow disable auto resize in some high performance scenario ([dd6681b](https://github.com/antvis/x6/commit/dd6681b53739bf48fa0a97166e16ada4a2c16896)) - -# @antv/x6 [1.16.0](https://github.com/antvis/x6/compare/@antv/x6@1.15.0...@antv/x6@1.16.0) (2021-03-11) - - -### Features - -* ✨ add direction option for midside node-anchor ([b235c11](https://github.com/antvis/x6/commit/b235c1106b4041f257d4f0db5f30198e5c41c90d)) - -# @antv/x6 [1.15.0](https://github.com/antvis/x6/compare/@antv/x6@1.14.0...@antv/x6@1.15.0) (2021-03-10) - - -### Features - -* ✨ add allowReverse option for resizing ([a597a75](https://github.com/antvis/x6/commit/a597a759c0a0b83f53e99e530d1970f46faf3dd2)) - -# @antv/x6 [1.14.0](https://github.com/antvis/x6/compare/@antv/x6@1.13.4...@antv/x6@1.14.0) (2021-03-10) - - -### Features - -* ✨ add containerParent config for dnd ([58fb4fd](https://github.com/antvis/x6/commit/58fb4fdd6a51c672e5a473773874081fe548fe0a)) - -## @antv/x6 [1.13.4](https://github.com/antvis/x6/compare/@antv/x6@1.13.3...@antv/x6@1.13.4) (2021-03-03) - - -### Bug Fixes - -* 🐛 get container size use offsetxxx ([faf3e4f](https://github.com/antvis/x6/commit/faf3e4f0b015650b04f33d7ad914c116962f317e)) - -## @antv/x6 [1.13.3](https://github.com/antvis/x6/compare/@antv/x6@1.13.2...@antv/x6@1.13.3) (2021-03-02) - - -### Bug Fixes - -* 🐛 fix start pos error in mousewheel ([1077a31](https://github.com/antvis/x6/commit/1077a3196219ecadbf1ecffac31d843d542f91fb)) - -## @antv/x6 [1.13.2](https://github.com/antvis/x6/compare/@antv/x6@1.13.1...@antv/x6@1.13.2) (2021-03-02) - - -### Bug Fixes - -* add judgment in sepcial event ([2732b12](https://github.com/antvis/x6/commit/2732b12b4fe9cf7e381239333497c10ca2b8e7d0)) - - -### Performance Improvements - -* ⚡️ avoid multiple times reflow when resize ([83483e9](https://github.com/antvis/x6/commit/83483e9d2af65ce2397770d35e8a7b799af8972a)) - -## @antv/x6 [1.13.1](https://github.com/antvis/x6/compare/@antv/x6@1.13.0...@antv/x6@1.13.1) (2021-02-23) - -# @antv/x6 [1.13.0](https://github.com/antvis/x6/compare/@antv/x6@1.12.32...@antv/x6@1.13.0) (2021-02-23) - - -### Features - -* add following config for selection ([#687](https://github.com/antvis/x6/issues/687)) ([5b52433](https://github.com/antvis/x6/commit/5b52433709089280320cc6b13e6442f31c1dcf30)) - -## @antv/x6 [1.12.32](https://github.com/antvis/x6/compare/@antv/x6@1.12.31...@antv/x6@1.12.32) (2021-02-20) - - -### Bug Fixes - -* fix keyboard event not trigger ([4ea5f31](https://github.com/antvis/x6/commit/4ea5f3194e80125a68e25e71920526f5b6c86150)) - -## @antv/x6 [1.12.31](https://github.com/antvis/x6/compare/@antv/x6@1.12.30...@antv/x6@1.12.31) (2021-02-09) - - -### Bug Fixes - -* fix spelling error ([2f33b99](https://github.com/antvis/x6/commit/2f33b99cae8559d3d98204fe427c1ad18ce94ac0)) - -## @antv/x6 [1.12.30](https://github.com/antvis/x6/compare/@antv/x6@1.12.29...@antv/x6@1.12.30) (2021-02-07) - - -### Bug Fixes - -* change css selector foreignobject to foreignObject ([#664](https://github.com/antvis/x6/issues/664)) ([2fa99f0](https://github.com/antvis/x6/commit/2fa99f05a8692bef1e9a4c241db399cee258fbb6)) - -## @antv/x6 [1.12.29](https://github.com/antvis/x6/compare/@antv/x6@1.12.28...@antv/x6@1.12.29) (2021-02-05) - -## @antv/x6 [1.12.28](https://github.com/antvis/x6/compare/@antv/x6@1.12.27...@antv/x6@1.12.28) (2021-02-04) - - -### Bug Fixes - -* 🐛 do not update pagesize automatically when set graph size ([fcb5f11](https://github.com/antvis/x6/commit/fcb5f11195c9e0418091ad55bb684294b189979e)), closes [#644](https://github.com/antvis/x6/issues/644) [#564](https://github.com/antvis/x6/issues/564) - -## @antv/x6 [1.12.27](https://github.com/antvis/x6/compare/@antv/x6@1.12.26...@antv/x6@1.12.27) (2021-02-03) - - -### Bug Fixes - -* 🐛 do not generate new commands on redoing/undoing ([1696f51](https://github.com/antvis/x6/commit/1696f519056a2cd57189b19532a758b24af3fe2a)), closes [#627](https://github.com/antvis/x6/issues/627) -* 🐛 should update inner reference when set "parent" and "children" by `prop()` ([f258522](https://github.com/antvis/x6/commit/f2585224689f4deb980d14f0e1f1a0c247bdcedc)) - -## @antv/x6 [1.12.26](https://github.com/antvis/x6/compare/@antv/x6@1.12.25...@antv/x6@1.12.26) (2021-02-02) - - -### Bug Fixes - -* 🐛 edge vertices can be point data array `[number, number][]` ([c7b0f0d](https://github.com/antvis/x6/commit/c7b0f0d811bbceb30365b683f69124ddf9d96008)) -* 🐛 linear gradient along edge path ([39619d3](https://github.com/antvis/x6/commit/39619d3baaab1af79cb0d2ecc08bb8859ef44065)), closes [#635](https://github.com/antvis/x6/issues/635) -* 🐛 liner gradient not available on horizontal and vertical edges ([1817298](https://github.com/antvis/x6/commit/1817298f7788d0b5475cf53c516a0a9b177bfdd0)), closes [#635](https://github.com/antvis/x6/issues/635) -* 🐛 should render vertices tool with lowest z-index ([da9ddf5](https://github.com/antvis/x6/commit/da9ddf5f068f84fbe1b359aad459252d49feab34)), closes [#638](https://github.com/antvis/x6/issues/638) -* fix move event trigger error ([#643](https://github.com/antvis/x6/issues/643)) ([586fc1f](https://github.com/antvis/x6/commit/586fc1f08fff85dcc1c6c8637aef529e8bccb169)) - -## @antv/x6 [1.12.25](https://github.com/antvis/x6/compare/@antv/x6@1.12.24...@antv/x6@1.12.25) (2021-02-02) - - -### Bug Fixes - -* delete console.log, add no-console config ([#642](https://github.com/antvis/x6/issues/642)) ([024c01f](https://github.com/antvis/x6/commit/024c01f000545c0c0b50259510fc886b3aa9815b)) - -## @antv/x6 [1.12.24](https://github.com/antvis/x6/compare/@antv/x6@1.12.23...@antv/x6@1.12.24) (2021-02-02) - - -### Bug Fixes - -* 🐛 auto extend scroller's graph with `async` mode ([8cd3aa6](https://github.com/antvis/x6/commit/8cd3aa63e4bb6a556d4c259c3116f590f87d021c)), closes [#636](https://github.com/antvis/x6/issues/636) - -## @antv/x6 [1.12.23](https://github.com/antvis/x6/compare/@antv/x6@1.12.22...@antv/x6@1.12.23) (2021-02-01) - - -### Bug Fixes - -* 🐛 break text with chinese characters(double byte character) ([7f37319](https://github.com/antvis/x6/commit/7f373194d9a3722aab403319aa2a843a00a18825)), closes [#596](https://github.com/antvis/x6/issues/596) - -## @antv/x6 [1.12.22](https://github.com/antvis/x6/compare/@antv/x6@1.12.21...@antv/x6@1.12.22) (2021-02-01) - - -### Bug Fixes - -* 🐛 auto resize graph with flexbox ([73c1e1d](https://github.com/antvis/x6/commit/73c1e1d869b098bd341ad1c6e969222980067053)) - -## @antv/x6 [1.12.21](https://github.com/antvis/x6/compare/@antv/x6@1.12.20...@antv/x6@1.12.21) (2021-01-31) - - -### Bug Fixes - -* fix trigger multiple moved event when close movable config ([f651181](https://github.com/antvis/x6/commit/f65118150178df82ee795f4fc292f5ce91c78b6b)) - -## @antv/x6 [1.12.20](https://github.com/antvis/x6/compare/@antv/x6@1.12.19...@antv/x6@1.12.20) (2021-01-29) - - -### Bug Fixes - -* 🐛 should clean cells on destroy graph ([e211c1a](https://github.com/antvis/x6/commit/e211c1a0a1f79588b084a4de326fdd493e839def)), closes [#600](https://github.com/antvis/x6/issues/600) -* change x6-svg-to-shape router ([3c5d11f](https://github.com/antvis/x6/commit/3c5d11f4b46d4843150ac3f294dc4ed0cf611c43)) - -## @antv/x6 [1.12.19](https://github.com/antvis/x6/compare/@antv/x6@1.12.18...@antv/x6@1.12.19) (2021-01-28) - - -### Bug Fixes - -* 🐛 fix type defines ([6b446f8](https://github.com/antvis/x6/commit/6b446f80ba96dbbfbff7257012fc6f6f8aca49fd)) - -## @antv/x6 [1.12.18](https://github.com/antvis/x6/compare/@antv/x6@1.12.17...@antv/x6@1.12.18) (2021-01-27) - - -### Bug Fixes - -* 🐛 exclude edge on node:move event ([#593](https://github.com/antvis/x6/issues/593)) ([dac555e](https://github.com/antvis/x6/commit/dac555e5cf15fba6a5450ecdb335a2cd9145d339)) - -## @antv/x6 [1.12.17](https://github.com/antvis/x6/compare/@antv/x6@1.12.16...@antv/x6@1.12.17) (2021-01-26) - - -### Bug Fixes - -* 🐛 should apply prop hooks when update props by `prop()` method ([0dfc09f](https://github.com/antvis/x6/commit/0dfc09f97ff4281aacb465ad74f1958930d30c8c)) - -## @antv/x6 [1.12.16](https://github.com/antvis/x6/compare/@antv/x6@1.12.15...@antv/x6@1.12.16) (2021-01-25) - - -### Bug Fixes - -* 🐛 support register html render object ([e0c9e97](https://github.com/antvis/x6/commit/e0c9e970723b8c7bd6a63127edf46be79a72d7c3)) - -## @antv/x6 [1.12.15](https://github.com/antvis/x6/compare/@antv/x6@1.12.14...@antv/x6@1.12.15) (2021-01-25) - - -### Bug Fixes - -* x6 support ie 11 ([#585](https://github.com/antvis/x6/issues/585)) ([8cb2f48](https://github.com/antvis/x6/commit/8cb2f489d2f913dd9fa80dab5c50e1fffe7f6939)) - -## @antv/x6 [1.12.14](https://github.com/antvis/x6/compare/@antv/x6@1.12.13...@antv/x6@1.12.14) (2021-01-24) - -## @antv/x6 [1.12.13](https://github.com/antvis/x6/compare/@antv/x6@1.12.12...@antv/x6@1.12.13) (2021-01-23) - - -### Bug Fixes - -* get registry first in html component ([c810821](https://github.com/antvis/x6/commit/c81082169763f4ca5432b44c94996674cd3599b1)) - -## @antv/x6 [1.12.12](https://github.com/antvis/x6/compare/@antv/x6@1.12.11...@antv/x6@1.12.12) (2021-01-23) - -## @antv/x6 [1.12.11](https://github.com/antvis/x6/compare/@antv/x6@1.12.10...@antv/x6@1.12.11) (2021-01-22) - - -### Bug Fixes - -* 🐛 prevent handle 'delete' and 'backsapce' key triggered from input ([429ef9a](https://github.com/antvis/x6/commit/429ef9ad45a11a49072d169a0c89146640f7e21a)) - -## @antv/x6 [1.12.10](https://github.com/antvis/x6/compare/@antv/x6@1.12.9...@antv/x6@1.12.10) (2021-01-22) - - -### Bug Fixes - -* add `placeholder` and `notFoundText` for stencil component ([#574](https://github.com/antvis/x6/issues/574)) ([c9100ab](https://github.com/antvis/x6/commit/c9100abb8576eaf55c5a9b0c5496f63c1796af5a)), closes [#555](https://github.com/antvis/x6/issues/555) - -## @antv/x6 [1.12.9](https://github.com/antvis/x6/compare/@antv/x6@1.12.8...@antv/x6@1.12.9) (2021-01-22) - - -### Bug Fixes - -* fix canot get graph ([#573](https://github.com/antvis/x6/issues/573)) ([5aadc87](https://github.com/antvis/x6/commit/5aadc87467e61dbd33d385e94a94bee72e744f84)) - -## @antv/x6 [1.12.8](https://github.com/antvis/x6/compare/@antv/x6@1.12.7...@antv/x6@1.12.8) (2021-01-21) - - -### Bug Fixes - -* 🐛 update page size as needed when graph size changed ([#571](https://github.com/antvis/x6/issues/571)) ([0fbab4b](https://github.com/antvis/x6/commit/0fbab4b2e932017ec60111c87a27d11cb85b2934)), closes [#564](https://github.com/antvis/x6/issues/564) - -## @antv/x6 [1.12.7](https://github.com/antvis/x6/compare/@antv/x6@1.12.6...@antv/x6@1.12.7) (2021-01-21) - -## @antv/x6 [1.12.6](https://github.com/antvis/x6/compare/@antv/x6@1.12.5...@antv/x6@1.12.6) (2021-01-21) - -## @antv/x6 [1.12.5](https://github.com/antvis/x6/compare/@antv/x6@1.12.4...@antv/x6@1.12.5) (2021-01-21) - -## @antv/x6 [1.12.4](https://github.com/antvis/x6/compare/@antv/x6@1.12.3...@antv/x6@1.12.4) (2021-01-20) - - -### Bug Fixes - -* 🐛 auto fix node's css className on mouseenter ([#566](https://github.com/antvis/x6/issues/566)) ([6a33149](https://github.com/antvis/x6/commit/6a3314959206c1299eb916c1dc10130d49ee7de8)), closes [#558](https://github.com/antvis/x6/issues/558) - -## @antv/x6 [1.12.3](https://github.com/antvis/x6/compare/@antv/x6@1.12.2...@antv/x6@1.12.3) (2021-01-20) - - -### Bug Fixes - -* 🐛 remove single tool by name or index ([#565](https://github.com/antvis/x6/issues/565)) ([f87dc43](https://github.com/antvis/x6/commit/f87dc43e439bfd13b7afe193db096bacd456bdcd)), closes [#552](https://github.com/antvis/x6/issues/552) - -## @antv/x6 [1.12.2](https://github.com/antvis/x6/compare/@antv/x6@1.12.1...@antv/x6@1.12.2) (2021-01-20) - - -### Bug Fixes - -* change $ to JQuery ([3c4d229](https://github.com/antvis/x6/commit/3c4d22952b33d592791cdead017d860833f51608)) - -## @antv/x6 [1.12.1](https://github.com/antvis/x6/compare/@antv/x6@1.12.0...@antv/x6@1.12.1) (2021-01-14) - - -### Bug Fixes - -* comment the copy style code first ([bdb01a4](https://github.com/antvis/x6/commit/bdb01a4ebac1896b4788c5c56d5fcff55bbbb663)) - -# @antv/x6 [1.12.0](https://github.com/antvis/x6/compare/@antv/x6@1.11.6...@antv/x6@1.12.0) (2021-01-13) - - -### Bug Fixes - -* 🐛 should copy inline style to scroller container ([ac47a27](https://github.com/antvis/x6/commit/ac47a27f4853d84627f4394f766c34f7fa57b27c)) - - -### Features - -* ✨ add util to detect element size change ([116571c](https://github.com/antvis/x6/commit/116571c6304a66e84a85aac9c6d8cf3ac91224e3)) -* ✨ auto resize graph when container resized ([ff6e2b6](https://github.com/antvis/x6/commit/ff6e2b63bce78992cdb1892c84d7bf2ce6c2bbc3)), closes [#531](https://github.com/antvis/x6/issues/531) - -## @antv/x6 [1.11.6](https://github.com/antvis/x6/compare/@antv/x6@1.11.5...@antv/x6@1.11.6) (2021-01-13) - -## @antv/x6 [1.8.3](https://github.com/antvis/x6/compare/@antv/x6@1.8.2...@antv/x6@1.8.3) (2021-01-13) - -## @antv/x6 [1.8.2](https://github.com/antvis/x6/compare/@antv/x6@1.8.1...@antv/x6@1.8.2) (2021-01-12) - -## @antv/x6 [1.8.1](https://github.com/antvis/x6/compare/@antv/x6@1.8.0...@antv/x6@1.8.1) (2021-01-12) - - -### Bug Fixes - -* 🐛 fix node immovable cursor style ([a91c71f](https://github.com/antvis/x6/commit/a91c71f6bc073e509530d78db904300436d80f00)) - -# @antv/x6 [1.8.0](https://github.com/antvis/x6/compare/@antv/x6@1.7.0...@antv/x6@1.8.0) (2021-01-11) - - -### Bug Fixes - -* 🐛 auto calc er router's direction ([9b9a727](https://github.com/antvis/x6/commit/9b9a727c9b168af80623be448d5ae389a21a72b0)) -* 🐛 box-sizing style was overwrited ([95c1329](https://github.com/antvis/x6/commit/95c132900b8881e12b73b9c7d5ab742c0154d472)) -* 🐛 can overwirte shape when define new shape with `Node.define(...)` ([f47fe4c](https://github.com/antvis/x6/commit/f47fe4cdef2da7ac6bb188c3ae131dffc2192cdb)) -* 🐛 change component -> render ([b90c519](https://github.com/antvis/x6/commit/b90c519c98a5adf81f111fb3ea1d8781ce7996bc)) -* 🐛 change rerender -> shouldComponentUpdate ([79672e9](https://github.com/antvis/x6/commit/79672e9e097b99d4339ddb5f6fba0dafa2c648f3)) -* 🐛 fix draw background ([521f99a](https://github.com/antvis/x6/commit/521f99a2942ec42284fefaf63fba3ddf77a7da3a)), closes [#466](https://github.com/antvis/x6/issues/466) -* 🐛 fix html rerender ([3412c0c](https://github.com/antvis/x6/commit/3412c0ce454b1481bdc037579819ab866bf01c14)) -* 🐛 fix marker of shadow edge ([7acd9f2](https://github.com/antvis/x6/commit/7acd9f2897747a45dd442975bc326e71740eb09e)) -* 🐛 fix the judgment error of dom object ([afb4f0b](https://github.com/antvis/x6/commit/afb4f0b12bc28e353e5f2e4c41822cb0b77c6f8d)) -* 🐛 fix type definition of node and edge registry ([d2742a4](https://github.com/antvis/x6/commit/d2742a4a8a473e60bc47fe099fd49c27e0c2d9ae)), closes [#478](https://github.com/antvis/x6/issues/478) -* 🐛 get completed picture when execue toPNG ([6dc50e9](https://github.com/antvis/x6/commit/6dc50e91d94fae0da2bc35a056e6410cb94d07be)) -* 🐛 ignore null keys on toJSON ([4462a56](https://github.com/antvis/x6/commit/4462a5698f1ccea33dad4f65e7eef3a88c7fff7c)) -* 🐛 konb 和 transform 的控制旋钮,在交互时只显示正在交互的旋钮 ([73bb1e1](https://github.com/antvis/x6/commit/73bb1e16e329853ae5a47c0a3725000a65efd6a3)) -* 🐛 recover the lost minimap style ([6de2ac8](https://github.com/antvis/x6/commit/6de2ac895475eda529f72a8ae774ce42a1226655)) -* 🐛 remove code tracing ([bdb0db2](https://github.com/antvis/x6/commit/bdb0db2da8708d626ebd09b46da7d431102b79bf)) -* 🐛 should clear knob on forcused node changed ([bf83cd8](https://github.com/antvis/x6/commit/bf83cd8760e89358846e216bc2a41c305f8a17fb)) -* 🐛 should render html tool in the graph container ([ebb43a9](https://github.com/antvis/x6/commit/ebb43a9501be68196266db2ffab2cbde54b7bdb4)) -* 🐛 should support multi knobs ([9fe76b9](https://github.com/antvis/x6/commit/9fe76b9c82c7e6040a4dcca00c417183a6fcb130)) -* 🐛 should trigger batch event on cell ([429f4e8](https://github.com/antvis/x6/commit/429f4e8b6a394dd10412fce4775af22af583cadc)) -* 🐛 support function on interacting items ([2222ab6](https://github.com/antvis/x6/commit/2222ab683abea60e7208832e8ef856ce132c8cf0)) -* 🐛 update selection boxs on after cell view updated ([7e5f759](https://github.com/antvis/x6/commit/7e5f75925d98aabcc4a96baef4670beb76d6996a)) -* 🐛 事件队列在事件回调用被修改,应该先缓存起来 ([d29ea43](https://github.com/antvis/x6/commit/d29ea43ea6e2b24a0caa2e861849bc01f6b4ce79)) -* change node:resize and node:rotate event trigger times ([#505](https://github.com/antvis/x6/issues/505)) ([4156e57](https://github.com/antvis/x6/commit/4156e5712ec1940041e7b22863361a6e6ee820aa)) -* fix cursor error ([b7d61b7](https://github.com/antvis/x6/commit/b7d61b75fbc24b36cfc384fdd9c6ed3baf2cf12a)) -* fix html node not update when setData ([0020c78](https://github.com/antvis/x6/commit/0020c781c3bb4b4747220fe327ade7e926d52014)) -* fix mousemove event fires on first mousedown ([2cb270e](https://github.com/antvis/x6/commit/2cb270e189361670b2479b4a9c9694b953bdb8ab)) -* update selection box when cell:changed ([#517](https://github.com/antvis/x6/issues/517)) ([c8234d5](https://github.com/antvis/x6/commit/c8234d5df1c7cb7910a93d5d7314c01c7b4023b0)) - - -### Features - -* ✨ add `arcTo`, `quadTo`, `drawPoints` methods for path ([00e8fd0](https://github.com/antvis/x6/commit/00e8fd0ec06e442833dd3f6c7ce7c05aabc5b556)) -* ✨ add `position` hook for knobs ([3e2f315](https://github.com/antvis/x6/commit/3e2f3154a7635f1b94176d05e6780d4b79761037)) -* ✨ add `rotate` method for geoemtrys ([90a5603](https://github.com/antvis/x6/commit/90a56037b16adff6fc3fbf50660eb95d3bd6bd2d)) -* ✨ convert client/rectangle point to graph point/rectangle ([1d55c62](https://github.com/antvis/x6/commit/1d55c62507d112d4a1f52e3ea6c4768017956fa0)) -* ✨ support html tool ([97624f4](https://github.com/antvis/x6/commit/97624f4a9dfaacc551acd89c5557a2b301fe2d5e)) -* ✨ 支持调节手柄,如圆柱,通过调节手柄修改圆柱椭圆面的大小 ([6ae70b8](https://github.com/antvis/x6/commit/6ae70b809e85db4d537e9104830eef1328c16f7a)) -* add direction for smooth connector ([deec3bc](https://github.com/antvis/x6/commit/deec3bc805c5af1d2d0fc81a25c0819a6072f99e)) - -## @antv/x6 [1.11.2](https://github.com/antvis/x6/compare/@antv/x6@1.11.1...@antv/x6@1.11.2) (2021-01-11) - - -### Bug Fixes - -* 🐛 ignore null keys on toJSON ([4462a56](https://github.com/antvis/x6/commit/4462a5698f1ccea33dad4f65e7eef3a88c7fff7c)) - -## @antv/x6 [1.11.1](https://github.com/antvis/x6/compare/@antv/x6@1.11.0...@antv/x6@1.11.1) (2021-01-11) - - -### Bug Fixes - -* fix cursor error ([b7d61b7](https://github.com/antvis/x6/commit/b7d61b75fbc24b36cfc384fdd9c6ed3baf2cf12a)) - -# @antv/x6 [1.11.0](https://github.com/antvis/x6/compare/@antv/x6@1.10.2...@antv/x6@1.11.0) (2021-01-08) - - -### Bug Fixes - -* fix mousemove event fires on first mousedown ([2cb270e](https://github.com/antvis/x6/commit/2cb270e189361670b2479b4a9c9694b953bdb8ab)) - - -### Features - -* add direction for smooth connector ([deec3bc](https://github.com/antvis/x6/commit/deec3bc805c5af1d2d0fc81a25c0819a6072f99e)) - -## @antv/x6 [1.10.2](https://github.com/antvis/x6/compare/@antv/x6@1.10.1...@antv/x6@1.10.2) (2021-01-08) - - -### Bug Fixes - -* 🐛 should trigger batch event on cell ([429f4e8](https://github.com/antvis/x6/commit/429f4e8b6a394dd10412fce4775af22af583cadc)) -* 🐛 update selection boxs on after cell view updated ([7e5f759](https://github.com/antvis/x6/commit/7e5f75925d98aabcc4a96baef4670beb76d6996a)) - -## @antv/x6 [1.10.1](https://github.com/antvis/x6/compare/@antv/x6@1.10.0...@antv/x6@1.10.1) (2021-01-08) - - -### Bug Fixes - -* update selection box when cell:changed ([#517](https://github.com/antvis/x6/issues/517)) ([c8234d5](https://github.com/antvis/x6/commit/c8234d5df1c7cb7910a93d5d7314c01c7b4023b0)) - -# @antv/x6 [1.10.0](https://github.com/antvis/x6/compare/@antv/x6@1.9.3...@antv/x6@1.10.0) (2021-01-08) - - -### Bug Fixes - -* 🐛 can overwirte shape when define new shape with `Node.define(...)` ([f47fe4c](https://github.com/antvis/x6/commit/f47fe4cdef2da7ac6bb188c3ae131dffc2192cdb)) -* 🐛 should support multi knobs ([9fe76b9](https://github.com/antvis/x6/commit/9fe76b9c82c7e6040a4dcca00c417183a6fcb130)) - - -### Features - -* ✨ add `arcTo`, `quadTo`, `drawPoints` methods for path ([00e8fd0](https://github.com/antvis/x6/commit/00e8fd0ec06e442833dd3f6c7ce7c05aabc5b556)) -* ✨ add `position` hook for knobs ([3e2f315](https://github.com/antvis/x6/commit/3e2f3154a7635f1b94176d05e6780d4b79761037)) -* ✨ add `rotate` method for geoemtrys ([90a5603](https://github.com/antvis/x6/commit/90a56037b16adff6fc3fbf50660eb95d3bd6bd2d)) - -## @antv/x6 [1.9.3](https://github.com/antvis/x6/compare/@antv/x6@1.9.2...@antv/x6@1.9.3) (2021-01-05) - - -### Bug Fixes - -* 🐛 konb 和 transform 的控制旋钮,在交互时只显示正在交互的旋钮 ([73bb1e1](https://github.com/antvis/x6/commit/73bb1e16e329853ae5a47c0a3725000a65efd6a3)) -* 🐛 should clear knob on forcused node changed ([bf83cd8](https://github.com/antvis/x6/commit/bf83cd8760e89358846e216bc2a41c305f8a17fb)) - -## @antv/x6 [1.9.2](https://github.com/antvis/x6/compare/@antv/x6@1.9.1...@antv/x6@1.9.2) (2021-01-05) - - -### Bug Fixes - -* change node:resize and node:rotate event trigger times ([#505](https://github.com/antvis/x6/issues/505)) ([4156e57](https://github.com/antvis/x6/commit/4156e5712ec1940041e7b22863361a6e6ee820aa)) - -## @antv/x6 [1.9.1](https://github.com/antvis/x6/compare/@antv/x6@1.9.0...@antv/x6@1.9.1) (2021-01-05) - - -### Bug Fixes - -* 🐛 事件队列在事件回调用被修改,应该先缓存起来 ([d29ea43](https://github.com/antvis/x6/commit/d29ea43ea6e2b24a0caa2e861849bc01f6b4ce79)) - -# @antv/x6 [1.9.0](https://github.com/antvis/x6/compare/@antv/x6@1.8.0...@antv/x6@1.9.0) (2021-01-04) - - -### Bug Fixes - -* 🐛 should render html tool in the graph container ([ebb43a9](https://github.com/antvis/x6/commit/ebb43a9501be68196266db2ffab2cbde54b7bdb4)) - - -### Features - -* ✨ convert client/rectangle point to graph point/rectangle ([1d55c62](https://github.com/antvis/x6/commit/1d55c62507d112d4a1f52e3ea6c4768017956fa0)) -* ✨ 支持调节手柄,如圆柱,通过调节手柄修改圆柱椭圆面的大小 ([6ae70b8](https://github.com/antvis/x6/commit/6ae70b809e85db4d537e9104830eef1328c16f7a)) - -# @antv/x6 [1.8.0](https://github.com/antvis/x6/compare/@antv/x6@1.7.12...@antv/x6@1.8.0) (2021-01-04) - - -### Features - -* ✨ support html tool ([97624f4](https://github.com/antvis/x6/commit/97624f4a9dfaacc551acd89c5557a2b301fe2d5e)) - -## @antv/x6 [1.7.12](https://github.com/antvis/x6/compare/@antv/x6@1.7.11...@antv/x6@1.7.12) (2020-12-31) - - -### Bug Fixes - -* 🐛 get completed picture when execue toPNG ([6dc50e9](https://github.com/antvis/x6/commit/6dc50e91d94fae0da2bc35a056e6410cb94d07be)) - -## @antv/x6 [1.7.11](https://github.com/antvis/x6/compare/@antv/x6@1.7.10...@antv/x6@1.7.11) (2020-12-30) - - -### Bug Fixes - -* 🐛 auto calc er router's direction ([9b9a727](https://github.com/antvis/x6/commit/9b9a727c9b168af80623be448d5ae389a21a72b0)) - -## @antv/x6 [1.7.10](https://github.com/antvis/x6/compare/@antv/x6@1.7.9...@antv/x6@1.7.10) (2020-12-29) - -## @antv/x6 [1.7.9](https://github.com/antvis/x6/compare/@antv/x6@1.7.8...@antv/x6@1.7.9) (2020-12-29) - - -### Bug Fixes - -* 🐛 fix type definition of node and edge registry ([d2742a4](https://github.com/antvis/x6/commit/d2742a4a8a473e60bc47fe099fd49c27e0c2d9ae)), closes [#478](https://github.com/antvis/x6/issues/478) - -## @antv/x6 [1.7.8](https://github.com/antvis/x6/compare/@antv/x6@1.7.7...@antv/x6@1.7.8) (2020-12-28) - - -### Bug Fixes - -* 🐛 remove code tracing ([bdb0db2](https://github.com/antvis/x6/commit/bdb0db2da8708d626ebd09b46da7d431102b79bf)) - -## @antv/x6 [1.7.7](https://github.com/antvis/x6/compare/@antv/x6@1.7.6...@antv/x6@1.7.7) (2020-12-26) - - -### Bug Fixes - -* 🐛 fix the judgment error of dom object ([afb4f0b](https://github.com/antvis/x6/commit/afb4f0b12bc28e353e5f2e4c41822cb0b77c6f8d)) -* 🐛 support function on interacting items ([2222ab6](https://github.com/antvis/x6/commit/2222ab683abea60e7208832e8ef856ce132c8cf0)) - -## @antv/x6 [1.7.6](https://github.com/antvis/x6/compare/@antv/x6@1.7.5...@antv/x6@1.7.6) (2020-12-25) - - -### Bug Fixes - -* 🐛 fix draw background ([521f99a](https://github.com/antvis/x6/commit/521f99a2942ec42284fefaf63fba3ddf77a7da3a)), closes [#466](https://github.com/antvis/x6/issues/466) -* 🐛 recover the lost minimap style ([6de2ac8](https://github.com/antvis/x6/commit/6de2ac895475eda529f72a8ae774ce42a1226655)) - -## @antv/x6 [1.7.5](https://github.com/antvis/x6/compare/@antv/x6@1.7.4...@antv/x6@1.7.5) (2020-12-25) - -## @antv/x6 [1.7.4](https://github.com/antvis/x6/compare/@antv/x6@1.7.3...@antv/x6@1.7.4) (2020-12-24) - - -### Bug Fixes - -* 🐛 change component -> render ([b90c519](https://github.com/antvis/x6/commit/b90c519c98a5adf81f111fb3ea1d8781ce7996bc)) -* 🐛 change rerender -> shouldComponentUpdate ([79672e9](https://github.com/antvis/x6/commit/79672e9e097b99d4339ddb5f6fba0dafa2c648f3)) -* 🐛 fix html rerender ([3412c0c](https://github.com/antvis/x6/commit/3412c0ce454b1481bdc037579819ab866bf01c14)) -* fix html node not update when setData ([0020c78](https://github.com/antvis/x6/commit/0020c781c3bb4b4747220fe327ade7e926d52014)) - -## @antv/x6 [1.7.3](https://github.com/antvis/x6/compare/@antv/x6@1.7.2...@antv/x6@1.7.3) (2020-12-24) - -## @antv/x6 [1.7.2](https://github.com/antvis/x6/compare/@antv/x6@1.7.1...@antv/x6@1.7.2) (2020-12-24) - -## @antv/x6 [1.7.1](https://github.com/antvis/x6/compare/@antv/x6@1.7.0...@antv/x6@1.7.1) (2020-12-24) - - -### Bug Fixes - -* 🐛 box-sizing style was overwrited ([95c1329](https://github.com/antvis/x6/commit/95c132900b8881e12b73b9c7d5ab742c0154d472)) -* 🐛 fix marker of shadow edge ([7acd9f2](https://github.com/antvis/x6/commit/7acd9f2897747a45dd442975bc326e71740eb09e)) - -# @antv/x6 [1.7.0](https://github.com/antvis/x6/compare/@antv/x6@1.6.4...@antv/x6@1.7.0) (2020-12-24) - - -### Bug Fixes - -* 🐛 process special attributes of text ([e1f9abf](https://github.com/antvis/x6/commit/e1f9abfffcdd723815311ebc58ef17761ad2a063)) - - -### Features - -* ✨ parse markup from xml string ([f16e7eb](https://github.com/antvis/x6/commit/f16e7eb38ca1f0dec71f51cd41b74341fc1a0f3d)) - -## @antv/x6 [1.6.4](https://github.com/antvis/x6/compare/@antv/x6@1.6.3...@antv/x6@1.6.4) (2020-12-23) - - -### Performance Improvements - -* ⚡️ add bounds proprtyies for rectangle ([c4480af](https://github.com/antvis/x6/commit/c4480af4e45b9a90746f3aefa14a4d7332b08d6a)) - -## @antv/x6 [1.6.3](https://github.com/antvis/x6/compare/@antv/x6@1.6.2...@antv/x6@1.6.3) (2020-12-22) - - -### Bug Fixes - -* 🐛 default transparent background for ForeignObject ([a386f94](https://github.com/antvis/x6/commit/a386f940eb18e718998b150d432242d8cfea5f8b)) -* 🐛 only append SVG tool to ToolsView, HTML tools should handle manually ([5c7b7d6](https://github.com/antvis/x6/commit/5c7b7d646c90e20a28f273d268d83a16246bb9f2)) - -## @antv/x6 [1.6.2](https://github.com/antvis/x6/compare/@antv/x6@1.6.1...@antv/x6@1.6.2) (2020-12-22) - - -### Bug Fixes - -* 🐛 fix sourceMarker and targetMaker position ([d637cf6](https://github.com/antvis/x6/commit/d637cf649e0b149acdf9dee12e6561e3b4f76b17)) -* 🐛 fix tools ware removed when update cell ([fac7e7a](https://github.com/antvis/x6/commit/fac7e7a4c853d75ea0ae37fcd7089bf20e56654b)) -* 🐛 update arrowhead on dragging ([c9e7b5f](https://github.com/antvis/x6/commit/c9e7b5ffeb52e2fd609283d5f72b0d43ad368561)) - -## @antv/x6 [1.6.1](https://github.com/antvis/x6/compare/@antv/x6@1.6.0...@antv/x6@1.6.1) (2020-12-21) - -# @antv/x6 [1.6.0](https://github.com/antvis/x6/compare/@antv/x6@1.5.2...@antv/x6@1.6.0) (2020-12-21) - - -### Bug Fixes - -* 🐛 fix alerts on lgtm.com ([e9f23f0](https://github.com/antvis/x6/commit/e9f23f025944d2c1660827c495794b6afa6f9412)) - - -### Features - -* ✨ add 'loop' and 'loose' option for connecting ([bbc41d4](https://github.com/antvis/x6/commit/bbc41d48294398053e77da161f2d0e7f0602f905)), closes [#390](https://github.com/antvis/x6/issues/390) -* ✨ add some connecting option ([68f7965](https://github.com/antvis/x6/commit/68f7965699b36d6a46f25e6aba5d144fb086c9a0)) - -## @antv/x6 [1.5.2](https://github.com/antvis/x6/compare/@antv/x6@1.5.1...@antv/x6@1.5.2) (2020-12-18) - - -### Bug Fixes - -* 🐛 take the stroke-width into account when calc connection point ([b21cac6](https://github.com/antvis/x6/commit/b21cac6968a548cad17c185a4219f24d135eaa8a)) - -## @antv/x6 [1.5.1](https://github.com/antvis/x6/compare/@antv/x6@1.5.0...@antv/x6@1.5.1) (2020-12-17) - - -### Bug Fixes - -* 🐛 should stop dragging when validate node async ([d418e07](https://github.com/antvis/x6/commit/d418e07ef404881400faf03943c8c9ff067e4598)), closes [#429](https://github.com/antvis/x6/issues/429) - -# @antv/x6 [1.5.0](https://github.com/antvis/x6/compare/@antv/x6@1.4.2...@antv/x6@1.5.0) (2020-12-17) - - -### Bug Fixes - -* 🐛 should return `stop` method when calling `sendToken` ([21276b2](https://github.com/antvis/x6/commit/21276b2a0f396b8e8343f133fed9383142468f5d)) - - -### Features - -* ✨ add `animate` and `animateTransform` ([b2ebf69](https://github.com/antvis/x6/commit/b2ebf69f2c311b1b8056179005d8fafd0a7eb8e9)) - - -### Performance Improvements - -* ⚡️ add transition callbacks and events for animation lifecycle ([462abd0](https://github.com/antvis/x6/commit/462abd0aa06e28bbbabf96ffd0493af4a9af6e1a)), closes [#419](https://github.com/antvis/x6/issues/419) [#420](https://github.com/antvis/x6/issues/420) - -## @antv/x6 [1.4.2](https://github.com/antvis/x6/compare/@antv/x6@1.4.1...@antv/x6@1.4.2) (2020-12-16) - -## @antv/x6 [1.4.1](https://github.com/antvis/x6/compare/@antv/x6@1.4.0...@antv/x6@1.4.1) (2020-12-16) - - -### Bug Fixes - -* optimize the ModifierKey isMatch method ([10c191d](https://github.com/antvis/x6/commit/10c191de7099c3305268aa36baaf5216d1684acb)) - -# @antv/x6 [1.4.0](https://github.com/antvis/x6/compare/@antv/x6@1.3.20...@antv/x6@1.4.0) (2020-12-16) - - -### Bug Fixes - -* 🐛 offset connection point ([3abfb53](https://github.com/antvis/x6/commit/3abfb53a040db0cfc4964d71ea64ce5f008a63b2)) -* 🐛 round path segment points ([4244439](https://github.com/antvis/x6/commit/4244439057fb20976edab3c334840fb1446ae218)) -* 🐛 should auto normalize path data when parse path from string ([7441c38](https://github.com/antvis/x6/commit/7441c383336ecb148311f318075517806619941e)) - - -### Features - -* ✨ add loop line ([bfa3c67](https://github.com/antvis/x6/commit/bfa3c6743b42c22d64edfbf79f82913129a5a285)), closes [#392](https://github.com/antvis/x6/issues/392) - -## @antv/x6 [1.3.20](https://github.com/antvis/x6/compare/@antv/x6@1.3.19...@antv/x6@1.3.20) (2020-12-12) - - -### Bug Fixes - -* fix size invalid on image node ([#397](https://github.com/antvis/x6/issues/397)) ([15fd567](https://github.com/antvis/x6/commit/15fd5673e13825a94bd05ffb4f892645ee20e887)) - -## @antv/x6 [1.3.19](https://github.com/antvis/x6/compare/@antv/x6@1.3.18...@antv/x6@1.3.19) (2020-12-11) - -## @antv/x6 [1.3.18](https://github.com/antvis/x6/compare/@antv/x6@1.3.17...@antv/x6@1.3.18) (2020-12-11) - - -### Bug Fixes - -* 🐛 offset connection point ([3563838](https://github.com/antvis/x6/commit/3563838e2547976365afea1d495a2288d93512c8)) -* 🐛 rotate anchor ([f314602](https://github.com/antvis/x6/commit/f31460212f03091be6273aa6103246186150b7b1)) - - -### Performance Improvements - -* ⚡️ align anchor ([b0a6b34](https://github.com/antvis/x6/commit/b0a6b340a20cdf989aa7d406eb938fd318c3d10a)) -* ⚡️ parallel line ([b8d5431](https://github.com/antvis/x6/commit/b8d543117f06e723eadca1ecd89de915815fcbaf)) - -## @antv/x6 [1.3.17](https://github.com/antvis/x6/compare/@antv/x6@1.3.16...@antv/x6@1.3.17) (2020-12-10) - - -### Bug Fixes - -* 🐛 do not reset anchor when `resetAnchor` option is `false` ([223cccc](https://github.com/antvis/x6/commit/223cccc66889abcb49284792d471bd22cf0ad61e)) -* 🐛 path error when rewrite prop ([ff61eb4](https://github.com/antvis/x6/commit/ff61eb4e16752a9186ab00f96429e5dabc4bb557)) -* 🐛 should deep merge attrs ([f869725](https://github.com/antvis/x6/commit/f8697256b2810ce94204a5b376891bcf5a3f4171)) -* 🐛 tool changes should be sync render ([47de4cb](https://github.com/antvis/x6/commit/47de4cb5de7838b8dd9b005a974476998c9974ef)) -* 🐛 tool changes should be sync render ([c3080b4](https://github.com/antvis/x6/commit/c3080b45a08050e937e872123c80b375b77ac3d6)) - -## @antv/x6 [1.3.16](https://github.com/antvis/x6/compare/@antv/x6@1.3.15...@antv/x6@1.3.16) (2020-12-10) - -## @antv/x6 [1.3.15](https://github.com/antvis/x6/compare/@antv/x6@1.3.14...@antv/x6@1.3.15) (2020-12-09) - -## @antv/x6 [1.3.14](https://github.com/antvis/x6/compare/@antv/x6@1.3.13...@antv/x6@1.3.14) (2020-12-09) - - -### Bug Fixes - -* 🐛 modifier keys of panning and selecting ([dc97368](https://github.com/antvis/x6/commit/dc97368b52b8810f095e2bf1f771736841e8feed)) -* 🐛 remove space ([a7258cd](https://github.com/antvis/x6/commit/a7258cd2db48ab63b6925101b8f98b38caa04929)) - -## @antv/x6 [1.3.13](https://github.com/antvis/x6/compare/@antv/x6@1.3.12...@antv/x6@1.3.13) (2020-12-09) - - -### Bug Fixes - -* 🐛 typos ([74d03b4](https://github.com/antvis/x6/commit/74d03b401e015018661b2bb4a8689e49d829f7d8)) - -## @antv/x6 [1.3.12](https://github.com/antvis/x6/compare/@antv/x6@1.3.11...@antv/x6@1.3.12) (2020-12-09) - - -### Bug Fixes - -* 🐛 inherit from 'edge' by default ([7797340](https://github.com/antvis/x6/commit/7797340406605b7e7ddfc97e0b47b1a726550660)) -* 🐛 modifer key of panning and selecting ([2b7b871](https://github.com/antvis/x6/commit/2b7b87196693f6eb50851a4327f3d9bdc944beff)) - - -### Performance Improvements - -* ⚡️ support space modifier key ([25e93cb](https://github.com/antvis/x6/commit/25e93cbf5d116cef25454434832362019b81d76b)) - -## @antv/x6 [1.3.11](https://github.com/antvis/x6/compare/@antv/x6@1.3.10...@antv/x6@1.3.11) (2020-12-09) - - -### Bug Fixes - -* 🐛 animateAlongPath: do not rotate by default ([ac77d51](https://github.com/antvis/x6/commit/ac77d51460f3d44254b7a0cba3b465f87879ea25)) -* 🐛 segments tool not work ([86767ae](https://github.com/antvis/x6/commit/86767aebae8c653247ca47cbb54f9061d79d34d4)) -* 🐛 selection box donot disappeared when select nothing ([b1c7314](https://github.com/antvis/x6/commit/b1c7314242530d962d23058a61e526b74df9cfb1)) - -## @antv/x6 [1.3.10](https://github.com/antvis/x6/compare/@antv/x6@1.3.9...@antv/x6@1.3.10) (2020-12-08) - - -### Bug Fixes - -* add trigger point for update in scroller ([03b4e6a](https://github.com/antvis/x6/commit/03b4e6a76b8c4a5bb251a8707fe7b7907237fae9)) - -## @antv/x6 [1.3.9](https://github.com/antvis/x6/compare/@antv/x6@1.3.8...@antv/x6@1.3.9) (2020-12-08) - - -### Bug Fixes - -* 🐛 should auto remove tools on cell was removed ([5f455f0](https://github.com/antvis/x6/commit/5f455f0cc1ff51b555ab00066ac694221537ed40)), closes [#383](https://github.com/antvis/x6/issues/383) - -## @antv/x6 [1.3.8](https://github.com/antvis/x6/compare/@antv/x6@1.3.7...@antv/x6@1.3.8) (2020-12-08) - - -### Bug Fixes - -* 🐛 fix return types ([66c6a7d](https://github.com/antvis/x6/commit/66c6a7d8dd89eedcecbf4079ac5b6e5e8f1bc2b5)) -* 🐛 should auto remove tools when removing cells ([064a059](https://github.com/antvis/x6/commit/064a059daf009b5e37a80c2a7277d620ff2a70d1)) - -## @antv/x6 [1.3.7](https://github.com/antvis/x6/compare/@antv/x6@1.3.6...@antv/x6@1.3.7) (2020-12-08) - -## @antv/x6 [1.3.6](https://github.com/antvis/x6/compare/@antv/x6@1.3.5...@antv/x6@1.3.6) (2020-12-07) - - -### Bug Fixes - -* 🐛 should undelegate document events on dropped ([8696b45](https://github.com/antvis/x6/commit/8696b45d8ad0022af7c649ef2882cffb8dd5cc4d)), closes [#360](https://github.com/antvis/x6/issues/360) - -## @antv/x6 [1.3.5](https://github.com/antvis/x6/compare/@antv/x6@1.3.4...@antv/x6@1.3.5) (2020-12-07) - - -### Bug Fixes - -* 🐛 unselect cell by clicking cell and holding on the meta key ([41624d6](https://github.com/antvis/x6/commit/41624d6591e57274cad49a0c77032c5ce7380cb9)) -* 🐛 unselect cell by clicking the selection box and hold on the meta key ([acc9c98](https://github.com/antvis/x6/commit/acc9c98aa0da9d5dd7108d2af2a955e2236e2385)), closes [#364](https://github.com/antvis/x6/issues/364) - -## @antv/x6 [1.3.4](https://github.com/antvis/x6/compare/@antv/x6@1.3.3...@antv/x6@1.3.4) (2020-12-07) - - -### Bug Fixes - -* 🐛 type define ([47e6a1c](https://github.com/antvis/x6/commit/47e6a1c4d7775b38b608e8f5fd1759b932744ee4)) - -## @antv/x6 [1.3.3](https://github.com/antvis/x6/compare/@antv/x6@1.3.2...@antv/x6@1.3.3) (2020-12-07) - - -### Bug Fixes - -* 🐛 do not trigger getDropNode when drop at invalid area ([503fe7c](https://github.com/antvis/x6/commit/503fe7c3cfb3a069dc1a112f75251bd03220f407)) - - -### Performance Improvements - -* ⚡️ types of async function ([fb5168a](https://github.com/antvis/x6/commit/fb5168a08b0eb93173993c62620357f02299ef97)) - -## @antv/x6 [1.3.2](https://github.com/antvis/x6/compare/@antv/x6@1.3.1...@antv/x6@1.3.2) (2020-12-07) - - -### Bug Fixes - -* 🐛 do not trigger change:zIndex when auto set zIndex at adding ([e7bd44f](https://github.com/antvis/x6/commit/e7bd44f0018b4e2d72457e56fd0947566658380c)) - -## @antv/x6 [1.3.1](https://github.com/antvis/x6/compare/@antv/x6@1.3.0...@antv/x6@1.3.1) (2020-12-07) - - -### Bug Fixes - -* 🐛 auto rotate token by default when animate along path ([75d4b23](https://github.com/antvis/x6/commit/75d4b239e394629744e5a694ac9e6a18c7cee697)) -* 🐛 typos ([d6eb46a](https://github.com/antvis/x6/commit/d6eb46a97c40daa3725d85f7a9ea1a7317d0afda)) - -# @antv/x6 [1.3.0](https://github.com/antvis/x6/compare/@antv/x6@1.2.3...@antv/x6@1.3.0) (2020-12-07) - - -### Bug Fixes - -* 🐛 auto rotate token ([cc08ee0](https://github.com/antvis/x6/commit/cc08ee0e897e561d744a4ba3f160164f7433b7d9)) -* 🐛 do not trigger cell:move event when not moved ([db58d33](https://github.com/antvis/x6/commit/db58d335ade5a976fb99178d2997be7bad206f57)), closes [#355](https://github.com/antvis/x6/issues/355) -* 🐛 lint errors ([3a1ef7f](https://github.com/antvis/x6/commit/3a1ef7f213ccc3343a1eea5479cce5e7e2be0957)) -* 🐛 ref should be null after clean ([b655075](https://github.com/antvis/x6/commit/b65507550d37889bc23c0c7ab4eb8bd538a3aeda)) -* 🐛 restore dom after disposed ([d8e22f1](https://github.com/antvis/x6/commit/d8e22f1ce31299ad22255c48d62bccc5cf642d44)) - - -### Features - -* ✨ add dom snapshoot method ([ad21955](https://github.com/antvis/x6/commit/ad2195522b70f3b01a6672739f3a989c35db63d0)) -* add demo for tranform method ([a599300](https://github.com/antvis/x6/commit/a599300536751c3f4a360bdae36258e5014cf137)) -* compatible with zoom in graph and scroller ([9e9365f](https://github.com/antvis/x6/commit/9e9365fa7090942940481a354086ae18b4b59c68)) -* compatible with zoomFit centerContent ([979d207](https://github.com/antvis/x6/commit/979d207a7fd0c80af769566d09e27a6438f59491)) -* compatible with zoomRect zoomTo centerPoint ([71e5cc4](https://github.com/antvis/x6/commit/71e5cc47bf677922933e944c1da808ca40f48c63)) - -## @antv/x6 [1.2.3](https://github.com/antvis/x6/compare/@antv/x6@1.2.2...@antv/x6@1.2.3) (2020-12-04) - - -### Bug Fixes - -* 🐛 toggle visible ([13629d5](https://github.com/antvis/x6/commit/13629d50d894fcb7c567cc093d39b73f17c50f9d)) - -## @antv/x6 [1.2.2](https://github.com/antvis/x6/compare/@antv/x6@1.2.1...@antv/x6@1.2.2) (2020-12-04) - - -### Bug Fixes - -* 🐛 find dom node ([1b2757c](https://github.com/antvis/x6/commit/1b2757c42273ab4f87e05c8d779d37bcb380ffb7)) - -## @antv/x6 [1.2.1](https://github.com/antvis/x6/compare/@antv/x6@1.2.0...@antv/x6@1.2.1) (2020-12-04) - - -### Bug Fixes - -* 🐛 lint errors ([1757172](https://github.com/antvis/x6/commit/1757172237b31b1db4765251c9d3e85de5b37ec7)) -* 🐛 lint errors ([9b399a3](https://github.com/antvis/x6/commit/9b399a3d68d29679144054212a003b244bebbfa0)) -* 🐛 should auto update edge's parent when edge was added ([57fc8c8](https://github.com/antvis/x6/commit/57fc8c856657f0c58355e5a7fa88f71d1c76877d)) - -# @antv/x6 [1.2.0](https://github.com/antvis/x6/compare/@antv/x6@1.1.3...@antv/x6@1.2.0) (2020-12-02) - - -### Features - -* support panning on normal graph ([#352](https://github.com/antvis/x6/issues/352)) ([7a50f7a](https://github.com/antvis/x6/commit/7a50f7aace64f0a657943195e5ef6b3fd7a46fbf)), closes [#339](https://github.com/antvis/x6/issues/339) - -## @antv/x6 [1.1.3](https://github.com/antvis/x6/compare/@antv/x6@1.1.2...@antv/x6@1.1.3) (2020-12-02) - -## @antv/x6 [1.1.2](https://github.com/antvis/x6/compare/@antv/x6@1.1.1...@antv/x6@1.1.2) (2020-12-02) - - -### Bug Fixes - -* 🐛 inherit from poly ([97dc51f](https://github.com/antvis/x6/commit/97dc51f30a86b12a52d8b4d3cde7901616ccb490)) -* 🐛 not dispose graph in minmap ([67c1428](https://github.com/antvis/x6/commit/67c142852ac9f37b7bf2e8dd3bdc295122fee4b1)) -* 🐛 remove default pathData ([97950c3](https://github.com/antvis/x6/commit/97950c307680b4e22e732603aa5d037cbd56965c)) -* 🐛 render of circlePlus ([252a326](https://github.com/antvis/x6/commit/252a326aff4447736cb73d888e71ff6f55b3b602)) -* 🐛 text prop ([ec24e89](https://github.com/antvis/x6/commit/ec24e899902c26285f846ea39c5f9ba429c4ee7f)) - -## @antv/x6 [1.1.1](https://github.com/antvis/x6/compare/@antv/x6@1.1.0...@antv/x6@1.1.1) (2020-11-30) - - -### Bug Fixes - -* 🐛 not dispose graph in minmap ([0b849cc](https://github.com/antvis/x6/commit/0b849ccdbee22a3a74e0036041c44b09f5cc0d96)) - -# @antv/x6 [1.1.0](https://github.com/antvis/x6/compare/@antv/x6@1.0.9...@antv/x6@1.1.0) (2020-11-27) - - -### Features - -* Support jsdelivr and cdnjs CDN service [#335](https://github.com/antvis/x6/issues/335) ([#336](https://github.com/antvis/x6/issues/336)) ([f640235](https://github.com/antvis/x6/commit/f640235ce8f9fa0db74b35037e951d0410a7fb1f)) - -## @antv/x6 [1.0.9](https://github.com/antvis/x6/compare/@antv/x6@1.0.8...@antv/x6@1.0.9) (2020-11-27) - -## @antv/x6 [1.0.8](https://github.com/antvis/x6/compare/@antv/x6@1.0.7...@antv/x6@1.0.8) (2020-11-25) - -## @antv/x6 [1.0.7](https://github.com/antvis/x6/compare/@antv/x6@1.0.6...@antv/x6@1.0.7) (2020-11-25) - - -### Bug Fixes - -* 🐛 used in unpkg "Uncaught ReferenceError: module is not defined" ([2863a29](https://github.com/antvis/x6/commit/2863a29da595a4a690e0b6c786669924dd8151aa)), closes [#329](https://github.com/antvis/x6/issues/329) - -## @antv/x6 [1.0.6](https://github.com/antvis/x6/compare/@antv/x6@1.0.5...@antv/x6@1.0.6) (2020-11-24) - -## @antv/x6 [1.0.5](https://github.com/antvis/x6/compare/@antv/x6@1.0.4...@antv/x6@1.0.5) (2020-11-24) - - -### Bug Fixes - -* 🐛 chang the call order of scale and translate ([66e1ce6](https://github.com/antvis/x6/commit/66e1ce66b86dde2be75600ab5f73e08efd0fb1ae)) - -## @antv/x6 [1.0.4](https://github.com/antvis/x6/compare/@antv/x6@1.0.3...@antv/x6@1.0.4) (2020-11-24) - - -### Bug Fixes - -* 🐛 global `process` should be replaced when build with rollup ([b459b61](https://github.com/antvis/x6/commit/b459b61a7aa966ff83bfb5992586aed2583b8a46)), closes [#324](https://github.com/antvis/x6/issues/324) -* 🐛 node:xxx event was not triggered when interact with selection boxes ([34cd5a0](https://github.com/antvis/x6/commit/34cd5a0737b291357d398b8ef2f5c58b113a1fc3)), closes [#297](https://github.com/antvis/x6/issues/297) -* 🐛 shake of selection events triggering ([541be16](https://github.com/antvis/x6/commit/541be16366785d28882a33d1d2b07ba8aa026072)) - -## @antv/x6 [1.0.3](https://github.com/antvis/x6/compare/@antv/x6@1.0.2...@antv/x6@1.0.3) (2020-11-20) - - -### Bug Fixes - -* 🐛 fix lint error ([a73cf3f](https://github.com/antvis/x6/commit/a73cf3fb3559657189502dc434d3bef4d7174ef6)) - -## @antv/x6 [1.0.2](https://github.com/antvis/x6/compare/@antv/x6@1.0.1...@antv/x6@1.0.2) (2020-11-19) - - -### Bug Fixes - -* 🐛 filter not working when select cell by calling `select()` api or by click ([#314](https://github.com/antvis/x6/issues/314)) ([7a3e547](https://github.com/antvis/x6/commit/7a3e54731940f5dcc2a15b8d338aedf64fc63619)), closes [#305](https://github.com/antvis/x6/issues/305) - -## @antv/x6 [1.0.1](https://github.com/antvis/x6/compare/@antv/x6@1.0.0...@antv/x6@1.0.1) (2020-11-18) - - -### Bug Fixes - -* 🐛 remove default points attr of polygon and polyline ([ccab7a2](https://github.com/antvis/x6/commit/ccab7a2a1c30955239891149d1c1e9250160bbe5)), closes [#304](https://github.com/antvis/x6/issues/304) [#304](https://github.com/antvis/x6/issues/304) - - -### Performance Improvements - -* ⚡️ auto scroll graph on moving node ([b2fb417](https://github.com/antvis/x6/commit/b2fb4170a0939488500c349db9006c7f11d884f7)) -* ⚡️ clean everything and restore dom structure on graph disposed ([a834331](https://github.com/antvis/x6/commit/a834331779e76e57ccb409d2f39040406ef732ea)), closes [#291](https://github.com/antvis/x6/issues/291) [#292](https://github.com/antvis/x6/issues/292) -* ⚡️ restrict on resizing ([36107bf](https://github.com/antvis/x6/commit/36107bf81871b6ce083ae02bbd9ba72deb6aa9b8)), closes [#289](https://github.com/antvis/x6/issues/289) - -## @antv/x6 [0.13.7](https://github.com/antvis/x6/compare/@antv/x6@0.13.6...@antv/x6@0.13.7) (2020-11-17) - - -### Bug Fixes - -* 🐛 x6 version ([803cd3e](https://github.com/antvis/x6/commit/803cd3ee0bdc137ce4043e6ec8ab14b0c65fa40d)) -* 🐛 x6 version ([1eb5359](https://github.com/antvis/x6/commit/1eb535924ea0358ab7d8bb3b9dab009ec3c0c04c)) - -# @antv/x6 [1.0.0-beta.5](https://github.com/antvis/x6/compare/@antv/x6@1.0.0-beta.4...@antv/x6@1.0.0-beta.5) (2020-11-17) - - -### Bug Fixes - -* 🐛 apps router ([8324eaa](https://github.com/antvis/x6/commit/8324eaa0a85cb14873f5095fe8d2695d80b5215a)) -* 🐛 dnd events ([3e94b0b](https://github.com/antvis/x6/commit/3e94b0b1eafab8f43cff2601b088df24d1b062a4)), closes [#271](https://github.com/antvis/x6/issues/271) -* 🐛 do not render edge when any of it's terminal is not visible ([1b6c6a9](https://github.com/antvis/x6/commit/1b6c6a9b9d13a664abb7f843c5ee798eac6626b0)), closes [#300](https://github.com/antvis/x6/issues/300) [#300](https://github.com/antvis/x6/issues/300) -* 🐛 equal points ([c415c1d](https://github.com/antvis/x6/commit/c415c1d6acc27678de6bdb1e1fbb2a92a810c220)) -* 🐛 get bearing between me and the given point ([07d0c1d](https://github.com/antvis/x6/commit/07d0c1d6ba1e9362d235a1f1a85696febc65839a)) -* 🐛 guard option not available ([b8ffaaf](https://github.com/antvis/x6/commit/b8ffaaf376f1b7a69d96fccde48a8de82e951660)), closes [#281](https://github.com/antvis/x6/issues/281) -* 🐛 should not render cell when invisible ([c9535b5](https://github.com/antvis/x6/commit/c9535b5604cda94066d80df0d43c85921f0ab978)), closes [#300](https://github.com/antvis/x6/issues/300) - - -### Features - -* ✨ add minScale and maxScale options for mousewheel ([e474ac3](https://github.com/antvis/x6/commit/e474ac3c6a7c224ab5e9a9039c7b419f91554891)), closes [#283](https://github.com/antvis/x6/issues/283) [#283](https://github.com/antvis/x6/issues/283) -* ✨ add some ui events ([7781435](https://github.com/antvis/x6/commit/77814353097a96cc444d347f26309ce6ae8e7453)), closes [#275](https://github.com/antvis/x6/issues/275) [#273](https://github.com/antvis/x6/issues/273) -* ✨ add xxx classname to node when widget visible ([aa3dd12](https://github.com/antvis/x6/commit/aa3dd120a5457f189c0f09dad87d96c70b908abd)), closes [#279](https://github.com/antvis/x6/issues/279) -* ✨ node/edge move events ([67efad9](https://github.com/antvis/x6/commit/67efad9f9dac1657c0f04de15ca80c8fd50d395e)) - -# @antv/x6 [1.0.0-beta.4](https://github.com/antvis/x6/compare/@antv/x6@1.0.0-beta.3...@antv/x6@1.0.0-beta.4) (2020-11-05) -## @antv/x6 [0.13.6](https://github.com/antvis/x6/compare/@antv/x6@0.13.5...@antv/x6@0.13.6) (2020-11-17) - - -### Bug Fixes - -* 🐛 version error ([5c80d69](https://github.com/antvis/x6/commit/5c80d69f66217e131176fce89b95d30bd47e3c4c)) -* 🐛 do not render edge when any of it's terminal is not visible ([1b6c6a9](https://github.com/antvis/x6/commit/1b6c6a9b9d13a664abb7f843c5ee798eac6626b0)), closes [#300](https://github.com/antvis/x6/issues/300) [#300](https://github.com/antvis/x6/issues/300) - -## @antv/x6 [0.13.5](https://github.com/antvis/x6/compare/@antv/x6@0.13.4...@antv/x6@0.13.5) (2020-11-17) - - -### Bug Fixes - -* 🐛 should not render cell when invisible ([c9535b5](https://github.com/antvis/x6/commit/c9535b5604cda94066d80df0d43c85921f0ab978)), closes [#300](https://github.com/antvis/x6/issues/300) - -## @antv/x6 [0.13.4](https://github.com/antvis/x6/compare/@antv/x6@0.13.3...@antv/x6@0.13.4) (2020-11-13) - -## @antv/x6 [0.13.3](https://github.com/antvis/x6/compare/@antv/x6@0.13.2...@antv/x6@0.13.3) (2020-11-12) - -## @antv/x6 [0.13.2](https://github.com/antvis/x6/compare/@antv/x6@0.13.1...@antv/x6@0.13.2) (2020-11-12) - -## @antv/x6 [0.13.1](https://github.com/antvis/x6/compare/@antv/x6@0.13.0...@antv/x6@0.13.1) (2020-11-11) - - -### Bug Fixes - -* 🐛 equal points ([c415c1d](https://github.com/antvis/x6/commit/c415c1d6acc27678de6bdb1e1fbb2a92a810c220)) -* 🐛 get bearing between me and the given point ([07d0c1d](https://github.com/antvis/x6/commit/07d0c1d6ba1e9362d235a1f1a85696febc65839a)) - -# @antv/x6 [0.13.0](https://github.com/antvis/x6/compare/@antv/x6@0.12.1...@antv/x6@0.13.0) (2020-11-10) - - -### Features - -* ✨ add minScale and maxScale options for mousewheel ([e474ac3](https://github.com/antvis/x6/commit/e474ac3c6a7c224ab5e9a9039c7b419f91554891)), closes [#283](https://github.com/antvis/x6/issues/283) [#283](https://github.com/antvis/x6/issues/283) - -## @antv/x6 [0.12.1](https://github.com/antvis/x6/compare/@antv/x6@0.12.0...@antv/x6@0.12.1) (2020-11-10) - - -### Bug Fixes - -* 🐛 guard option not available ([b8ffaaf](https://github.com/antvis/x6/commit/b8ffaaf376f1b7a69d96fccde48a8de82e951660)), closes [#281](https://github.com/antvis/x6/issues/281) - -# @antv/x6 [0.12.0](https://github.com/antvis/x6/compare/@antv/x6@0.11.2...@antv/x6@0.12.0) (2020-11-09) - - -### Features - -* ✨ add xxx classname to node when widget visible ([aa3dd12](https://github.com/antvis/x6/commit/aa3dd120a5457f189c0f09dad87d96c70b908abd)), closes [#279](https://github.com/antvis/x6/issues/279) - -## @antv/x6 [0.11.2](https://github.com/antvis/x6/compare/@antv/x6@0.11.1...@antv/x6@0.11.2) (2020-11-09) - -## @antv/x6 [0.11.1](https://github.com/antvis/x6/compare/@antv/x6@0.11.0...@antv/x6@0.11.1) (2020-11-09) - -# @antv/x6 [0.11.0](https://github.com/antvis/x6/compare/@antv/x6@0.10.81...@antv/x6@0.11.0) (2020-11-09) - - -### Features - -* ✨ add some ui events ([7781435](https://github.com/antvis/x6/commit/77814353097a96cc444d347f26309ce6ae8e7453)), closes [#275](https://github.com/antvis/x6/issues/275) [#273](https://github.com/antvis/x6/issues/273) -* ✨ node/edge move events ([67efad9](https://github.com/antvis/x6/commit/67efad9f9dac1657c0f04de15ca80c8fd50d395e)) - -## @antv/x6 [0.10.81](https://github.com/antvis/x6/compare/@antv/x6@0.10.80...@antv/x6@0.10.81) (2020-11-09) - -## @antv/x6 [0.10.80](https://github.com/antvis/x6/compare/@antv/x6@0.10.79...@antv/x6@0.10.80) (2020-11-06) - - -### Bug Fixes - -* 🐛 dnd events ([3e94b0b](https://github.com/antvis/x6/commit/3e94b0b1eafab8f43cff2601b088df24d1b062a4)), closes [#271](https://github.com/antvis/x6/issues/271) - -## @antv/x6 [0.10.79](https://github.com/antvis/x6/compare/@antv/x6@0.10.78...@antv/x6@0.10.79) (2020-11-05) - - -### Bug Fixes - -* 🐛 apps router ([8324eaa](https://github.com/antvis/x6/commit/8324eaa0a85cb14873f5095fe8d2695d80b5215a)) diff --git a/packages/x6-core/LICENSE b/packages/x6/LICENSE similarity index 100% rename from packages/x6-core/LICENSE rename to packages/x6/LICENSE diff --git a/packages/x6-core/__tests__/util/index.test.ts b/packages/x6/__tests__/util/index.test.ts similarity index 100% rename from packages/x6-core/__tests__/util/index.test.ts rename to packages/x6/__tests__/util/index.test.ts diff --git a/packages/x6-core/index.ts b/packages/x6/index.ts similarity index 100% rename from packages/x6-core/index.ts rename to packages/x6/index.ts diff --git a/packages/x6/karma.conf.js b/packages/x6/karma.conf.js index 3c2356ff62c..e95908ebb15 100644 --- a/packages/x6/karma.conf.js +++ b/packages/x6/karma.conf.js @@ -1,10 +1,4 @@ module.exports = (config) => - require('../../configs/karma-config.js')( - config, - { - files: [{ pattern: 'src/**/*.ts' }], - }, - { - include: ['./src/**/*.ts', '../../node_modules/csstype/**/*'], - }, - ) + require('../../configs/karma-config.js')(config, { + files: [{ pattern: './src/**/*.ts' }, { pattern: './__tests__/**/*.ts' }], + }) diff --git a/packages/x6/package.json b/packages/x6/package.json index 6b1dc7d70a4..629ad9fd3e0 100644 --- a/packages/x6/package.json +++ b/packages/x6/package.json @@ -1,6 +1,6 @@ { "name": "@antv/x6", - "version": "1.30.2", + "version": "2.0.6-beta.6", "description": "JavaScript diagramming library that uses SVG and HTML for rendering.", "main": "lib/index.js", "module": "es/index.js", @@ -32,22 +32,21 @@ "lint:style": "stylelint 'src/**/*.less' --syntax less --fix", "lint": "run-s lint:ts lint:style", "build:esm": "tsc --module esnext --target es2015 --outDir ./es", - "build:cjs": "tsc --module commonjs --target es5 --outDir ./lib", + "build:cjs": "tsc --module commonjs --target es2015 --outDir ./lib", "build:umd": "rollup -c", "build:less": "node ./scripts/style", "build:readme": "node ./scripts/readme.js", "build:version": "node ../../scripts/version.js", - "build:csstype": "node ./scripts/csstype.js", - "build:dev": "run-p build:csstype build:less build:cjs build:esm", + "build:dev": "run-p build:less build:cjs build:esm", "build:watch": "yarn build:esm --w", "build:watch:esm": "yarn build:esm --w", "build:watch:cjs": "yarn build:cjs --w", - "build": "run-p build:readme build:version build:dev build:umd", + "build": "run-p build:readme build:dev build:umd", "prebuild": "run-s lint clean", "test": "karma start", "coveralls": "cat ./test/coverage/lcov.info | coveralls", "pretest": "run-p clean:coverage", - "prepare": "run-s build:version test build", + "prepare": "run-s test build", "precommit": "lint-staged" }, "lint-staged": { @@ -66,12 +65,8 @@ "@antv/x6-package-json/rollup.json" ], "dependencies": { - "csstype": "^3.0.3", - "jquery": "^3.5.1", - "jquery-mousewheel": "^3.1.13", - "lodash-es": "^4.17.15", - "mousetrap": "^1.6.5", - "utility-types": "^3.10.0" + "@antv/x6-common": "^2.0.6-beta.3", + "@antv/x6-geometry": "^2.0.6-beta.2" }, "devDependencies": { "@rollup/plugin-commonjs": "^20.0.0", @@ -79,10 +74,6 @@ "@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-typescript": "^8.2.5", "@types/jasmine": "^3.9.0", - "@types/jquery": "^3.5.5", - "@types/jquery-mousewheel": "^3.1.8", - "@types/lodash-es": "^4.17.4", - "@types/mousetrap": "^1.6.5", "@types/node": "^16.9.1", "@types/resize-observer-browser": "^0.1.5", "@types/sinon": "^10.0.2", @@ -132,13 +123,9 @@ "stylelint-order": "^4.1.0", "ts-node": "^10.2.1", "tslib": "^2.3.1", - "typescript": "^4.4.3" + "typescript": "^4.4.3", + "utility-types": "^3.10.0" }, - "browserslist": [ - "last 2 versions", - "Firefox ESR", - "> 1%" - ], "author": { "name": "bubkoo", "email": "bubkoo.wy@gmail.com" @@ -157,5 +144,6 @@ "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" - } + }, + "gitHead": "576fa342fa65a6867ead29f6801a30dcb31bcdb5" } diff --git a/packages/x6/rollup.config.js b/packages/x6/rollup.config.js index b02a229a29f..ff5e34ff77a 100644 --- a/packages/x6/rollup.config.js +++ b/packages/x6/rollup.config.js @@ -3,10 +3,11 @@ import config from '../../configs/rollup-config' export default config({ output: [ { - name: 'X6', + name: 'X6Next', format: 'umd', file: 'dist/x6.js', sourcemap: true, }, ], + context: 'window', }) diff --git a/packages/x6/scripts/csstype.js b/packages/x6/scripts/csstype.js deleted file mode 100644 index 24ab3418719..00000000000 --- a/packages/x6/scripts/csstype.js +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node - -// copy csstype - -const fs = require('fs') -const path = require('path') - -const cwd = process.cwd() -const src = path.join(cwd, '../../node_modules/csstype/index.d.ts') -const dist = path.join(cwd, 'src/types/csstype.ts') - -const content = fs.readFileSync(src, { encoding: 'utf8' }) -const prev = fs.readFileSync(dist, { encoding: 'utf8' }) -const next = `/* eslint-disable */ - -/** -* Auto generated file by copying from node_modules, do not modify it! -* Fix karma error "Can't find csstype [undefined] (required by ..." -*/ - -${content} -` - -if (prev !== next) { - fs.writeFileSync(dist, next, { encoding: 'utf8' }) -} diff --git a/packages/x6/src/addon/autosave/index.ts b/packages/x6/src/addon/autosave/index.ts deleted file mode 100644 index bf0b4c042b6..00000000000 --- a/packages/x6/src/addon/autosave/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Disablable } from '../../common' -import { Graph } from '../../graph' - -export class AutoSave extends Disablable { - protected readonly options: AutoSave.Options - - protected get graph() { - return this.options.graph - } - - delay = 10 - throttle = 2 - threshold = 5 - - private changeCount = 0 - private timestamp = 0 - - constructor(options: AutoSave.Options) { - super() - this.options = { - ...AutoSave.defaultOptions, - ...options, - } - this.graph.model.on('cell:change:*', this.onModelChanged, this) - } - - private onModelChanged() { - if (this.disabled) { - return - } - - const now = new Date().getTime() - const dt = (now - this.timestamp) / 1000 - - if ( - dt > this.delay || - (this.changeCount >= this.threshold && dt > this.throttle) - ) { - this.save() - this.reset() - } else { - this.changeCount += 1 - } - } - - private save() { - this.trigger('save') - } - - reset() { - this.changeCount = 0 - this.timestamp = new Date().getTime() - } - - @Disablable.dispose() - dispose() { - this.graph.model.off('cell:change:*', this.onModelChanged, this) - } -} - -export namespace AutoSave { - export interface Options { - graph: Graph - /** - * Minimum amount of seconds between two consecutive autosaves. - */ - delay?: number - /** - * Minimum amount of seconds and more than `threshold` changes - * between two consecutive autosaves. - */ - throttle?: number - /** - * Minimum amount of ignored changes before an autosave. - */ - threshold?: number - } - - export const defaultOptions: Partial = { - delay: 10, - throttle: 2, - threshold: 5, - } - - export interface EventArgs { - save?: null - } -} diff --git a/packages/x6/src/addon/clipboard/index.ts b/packages/x6/src/addon/clipboard/index.ts deleted file mode 100644 index 122bdb508f2..00000000000 --- a/packages/x6/src/addon/clipboard/index.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { ArrayExt } from '../../util' -import { Config } from '../../global/config' -import { Graph } from '../../graph/graph' -import { Cell } from '../../model/cell' -import { Edge } from '../../model/edge' -import { Node } from '../../model/node' -import { Model } from '../../model/model' - -export class Clipboard { - protected options: Clipboard.Options - public cells: Cell[] = [] - - copy( - cells: Cell[], - graph: Graph | Model, - options: Clipboard.CopyOptions = {}, - ) { - this.options = { ...options } - const model = Model.isModel(graph) ? graph : graph.model - const cloned = model.cloneSubGraph(cells, options) - - // sort asc by cell type - this.cells = ArrayExt.sortBy( - Object.keys(cloned).map((key) => cloned[key]), - (cell) => (cell.isEdge() ? 2 : 1), - ) - - this.serialize(options) - } - - cut( - cells: Cell[], - graph: Graph | Model, - options: Clipboard.CopyOptions = {}, - ) { - this.copy(cells, graph, options) - const model = Graph.isGraph(graph) ? graph.model : graph - model.batchUpdate('cut', () => { - cells.forEach((cell) => cell.remove()) - }) - } - - paste(graph: Graph | Model, options: Clipboard.PasteOptions = {}) { - const localOptions = { ...this.options, ...options } - const { offset, edgeProps, nodeProps } = localOptions - - let dx = 20 - let dy = 20 - if (offset) { - dx = typeof offset === 'number' ? offset : offset.dx - dy = typeof offset === 'number' ? offset : offset.dy - } - - this.deserialize(localOptions) - const cells = this.cells - - cells.forEach((cell) => { - cell.model = null - cell.removeProp('zIndex') - if (dx || dy) { - cell.translate(dx, dy) - } - - if (nodeProps && cell.isNode()) { - cell.prop(nodeProps) - } - - if (edgeProps && cell.isEdge()) { - cell.prop(edgeProps) - } - }) - - const model = Graph.isGraph(graph) ? graph.model : graph - model.batchUpdate('paste', () => { - model.addCells(this.cells) - }) - - this.copy(cells, graph, options) - - return cells - } - - serialize(options: Clipboard.PasteOptions) { - if (options.useLocalStorage !== false) { - Storage.save(this.cells) - } - } - - deserialize(options: Clipboard.PasteOptions) { - if (options.useLocalStorage) { - const cells = Storage.fetch() - if (cells) { - this.cells = cells - } - } - } - - isEmpty() { - return this.cells.length <= 0 - } - - clean() { - this.options = {} - this.cells = [] - Storage.clean() - } -} - -export namespace Clipboard { - export interface Options { - useLocalStorage?: boolean - } - - export interface CopyOptions extends Options { - deep?: boolean - } - - export interface PasteOptions extends Options { - /** - * Set of properties to be set on each copied node on every `paste()` call. - * It is defined as an object. e.g. `{ zIndex: 1 }`. - */ - nodeProps?: Node.Properties - /** - * Set of properties to be set on each copied edge on every `paste()` call. - * It is defined as an object. e.g. `{ zIndex: 1 }`. - */ - edgeProps?: Edge.Properties - - /** - * An increment that is added to the pasted cells position on every - * `paste()` call. It can be either a number or an object with `dx` - * and `dy` attributes. It defaults to `{ dx: 20, dy: 20 }`. - */ - offset?: number | { dx: number; dy: number } - } -} - -namespace Storage { - const LOCAL_STORAGE_KEY = `${Config.prefixCls}.clipboard.cells` - - export function save(cells: Cell[]) { - if (window.localStorage) { - const data = cells.map((cell) => cell.toJSON()) - localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(data)) - } - } - - export function fetch() { - if (window.localStorage) { - const raw = localStorage.getItem(LOCAL_STORAGE_KEY) - const cells = raw ? JSON.parse(raw) : [] - if (cells) { - return Model.fromJSON(cells) - } - } - } - - export function clean() { - if (window.localStorage) { - localStorage.removeItem(LOCAL_STORAGE_KEY) - } - } -} diff --git a/packages/x6/src/addon/common/handle.less b/packages/x6/src/addon/common/handle.less deleted file mode 100644 index d70be0a5a95..00000000000 --- a/packages/x6/src/addon/common/handle.less +++ /dev/null @@ -1,530 +0,0 @@ -@import '../../style/index'; -@handle-prefix-cls: ~'@{x6-prefix}-widget-handle'; -@handle-wrap-prefix-cls: ~'@{handle-prefix-cls}-wrap'; - -@keyframes halo-pie-visibility { - 0% { - visibility: hidden; - } - 100% { - visibility: visible; - } -} - -@keyframes halo-pie-opening { - 0% { - transform: scale(0.4) rotate(-20deg); - } - 100% { - transform: scale(1) rotate(0); - } -} - -.@{handle-prefix-cls} { - position: absolute; - width: 20px; - height: 20px; - background-color: transparent; - background-repeat: no-repeat; - background-position: 0 0; - background-size: 20px 20px; - cursor: pointer; - user-select: none; - pointer-events: auto; - - -webkit-user-drag: none; - user-drag: none; /* stylelint-disable-line */ - - &.hidden { - display: none; - } - - &-selected { - background-color: rgba(0, 0, 0, 0.1); - border-radius: 3px; - } - - &-remove { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M15.386%2C3.365c-3.315-3.314-8.707-3.313-12.021%2C0c-3.314%2C3.315-3.314%2C8.706%2C0%2C12.02%20c3.314%2C3.314%2C8.707%2C3.314%2C12.021%2C0S18.699%2C6.68%2C15.386%2C3.365L15.386%2C3.365z%20M4.152%2C14.598C1.273%2C11.719%2C1.273%2C7.035%2C4.153%2C4.154%20c2.88-2.88%2C7.563-2.88%2C10.443%2C0c2.881%2C2.88%2C2.881%2C7.562%2C0%2C10.443C11.716%2C17.477%2C7.032%2C17.477%2C4.152%2C14.598L4.152%2C14.598z%22%2F%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M12.157%2C11.371L7.38%2C6.593C7.162%2C6.375%2C6.809%2C6.375%2C6.592%2C6.592c-0.218%2C0.219-0.218%2C0.572%2C0%2C0.79%20l4.776%2C4.776c0.218%2C0.219%2C0.571%2C0.219%2C0.79%2C0C12.375%2C11.941%2C12.375%2C11.588%2C12.157%2C11.371L12.157%2C11.371z%22%2F%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M11.369%2C6.593l-4.777%2C4.778c-0.217%2C0.217-0.217%2C0.568%2C0%2C0.787c0.219%2C0.219%2C0.571%2C0.217%2C0.788%2C0l4.777-4.777%20c0.218-0.218%2C0.218-0.571%2C0.001-0.789C11.939%2C6.375%2C11.587%2C6.375%2C11.369%2C6.593L11.369%2C6.593z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - cursor: pointer; - - &:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M15.386%2C3.365c-3.315-3.314-8.707-3.313-12.021%2C0c-3.314%2C3.315-3.314%2C8.706%2C0%2C12.02%20c3.314%2C3.314%2C8.707%2C3.314%2C12.021%2C0S18.699%2C6.68%2C15.386%2C3.365L15.386%2C3.365z%22%2F%3E%3Cpath%20fill%3D%22%23FFFFFF%22%20d%3D%22M12.157%2C11.371L7.38%2C6.593C7.162%2C6.375%2C6.809%2C6.375%2C6.592%2C6.592c-0.218%2C0.219-0.218%2C0.572%2C0%2C0.79%20l4.776%2C4.776c0.218%2C0.219%2C0.571%2C0.219%2C0.79%2C0C12.375%2C11.941%2C12.375%2C11.588%2C12.157%2C11.371L12.157%2C11.371z%22%2F%3E%3Cpath%20fill%3D%22%23FFFFFF%22%20d%3D%22M11.369%2C6.593l-4.777%2C4.778c-0.217%2C0.217-0.217%2C0.568%2C0%2C0.787c0.219%2C0.219%2C0.571%2C0.217%2C0.788%2C0l4.777-4.777%20c0.218-0.218%2C0.218-0.571%2C0.001-0.789C11.939%2C6.375%2C11.587%2C6.375%2C11.369%2C6.593L11.369%2C6.593z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - } - } - - &-rotate { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M9.374%2C17.592c-4.176%2C0-7.57-3.401-7.57-7.575c0-4.175%2C3.395-7.574%2C7.57-7.574c0.28%2C0%2C0.56%2C0.018%2C0.837%2C0.05%20V1.268c0-0.158%2C0.099-0.3%2C0.239-0.36c0.151-0.058%2C0.315-0.026%2C0.428%2C0.086l2.683%2C2.688c0.152%2C0.154%2C0.152%2C0.399%2C0%2C0.553l-2.68%2C2.693%20c-0.115%2C0.112-0.279%2C0.147-0.431%2C0.087c-0.141-0.063-0.239-0.205-0.239-0.361V5.296C9.934%2C5.243%2C9.654%2C5.22%2C9.374%2C5.22%20c-2.646%2C0-4.796%2C2.152-4.796%2C4.797s2.154%2C4.798%2C4.796%2C4.798c2.645%2C0%2C4.798-2.153%2C4.798-4.798c0-0.214%2C0.174-0.391%2C0.391-0.391h1.991%20c0.217%2C0%2C0.394%2C0.177%2C0.394%2C0.391C16.947%2C14.19%2C13.549%2C17.592%2C9.374%2C17.592L9.374%2C17.592z%20M9.374%2C17.592%22%2F%3E%3C%2Fsvg%3E%20'); - cursor: move; - - &:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FD6EB6%22%20d%3D%22M9.374%2C17.592c-4.176%2C0-7.57-3.401-7.57-7.575c0-4.175%2C3.395-7.574%2C7.57-7.574c0.28%2C0%2C0.56%2C0.018%2C0.837%2C0.05%20V1.268c0-0.158%2C0.099-0.3%2C0.239-0.36c0.151-0.058%2C0.315-0.026%2C0.428%2C0.086l2.683%2C2.688c0.152%2C0.154%2C0.152%2C0.399%2C0%2C0.553l-2.68%2C2.693%20c-0.115%2C0.112-0.279%2C0.147-0.431%2C0.087c-0.141-0.063-0.239-0.205-0.239-0.361V5.296C9.934%2C5.243%2C9.654%2C5.22%2C9.374%2C5.22%20c-2.646%2C0-4.796%2C2.152-4.796%2C4.797s2.154%2C4.798%2C4.796%2C4.798c2.645%2C0%2C4.798-2.153%2C4.798-4.798c0-0.214%2C0.174-0.391%2C0.391-0.391h1.991%20c0.217%2C0%2C0.394%2C0.177%2C0.394%2C0.391C16.947%2C14.19%2C13.549%2C17.592%2C9.374%2C17.592L9.374%2C17.592z%20M9.374%2C17.592%22%2F%3E%3C%2Fsvg%3E%20'); - } - } - - &-resize { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20height%3D%2224px%22%20version%3D%221.1%22%20viewBox%3D%220%200%2024%2024%22%20width%3D%2224px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Asketch%3D%22http%3A%2F%2Fwww.bohemiancoding.com%2Fsketch%2Fns%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Ctitle%2F%3E%3Cdesc%2F%3E%3Cdefs%2F%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%20id%3D%22miu%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%3E%3Cg%20id%3D%22Artboard-1%22%20transform%3D%22translate(-251.000000%2C%20-443.000000)%22%3E%3Cg%20id%3D%22slice%22%20transform%3D%22translate(215.000000%2C%20119.000000)%22%2F%3E%3Cpath%20d%3D%22M252%2C448%20L256%2C448%20L256%2C444%20L252%2C444%20L252%2C448%20Z%20M257%2C448%20L269%2C448%20L269%2C446%20L257%2C446%20L257%2C448%20Z%20M257%2C464%20L269%2C464%20L269%2C462%20L257%2C462%20L257%2C464%20Z%20M270%2C444%20L270%2C448%20L274%2C448%20L274%2C444%20L270%2C444%20Z%20M252%2C462%20L252%2C466%20L256%2C466%20L256%2C462%20L252%2C462%20Z%20M270%2C462%20L270%2C466%20L274%2C466%20L274%2C462%20L270%2C462%20Z%20M254%2C461%20L256%2C461%20L256%2C449%20L254%2C449%20L254%2C461%20Z%20M270%2C461%20L272%2C461%20L272%2C449%20L270%2C449%20L270%2C461%20Z%22%20fill%3D%22%236A6C8A%22%20id%3D%22editor-crop-glyph%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E'); - cursor: se-resize; - - &:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20height%3D%2224px%22%20version%3D%221.1%22%20viewBox%3D%220%200%2024%2024%22%20width%3D%2224px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Asketch%3D%22http%3A%2F%2Fwww.bohemiancoding.com%2Fsketch%2Fns%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Ctitle%2F%3E%3Cdesc%2F%3E%3Cdefs%2F%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%20id%3D%22miu%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%3E%3Cg%20id%3D%22Artboard-1%22%20transform%3D%22translate(-251.000000%2C%20-443.000000)%22%3E%3Cg%20id%3D%22slice%22%20transform%3D%22translate(215.000000%2C%20119.000000)%22%2F%3E%3Cpath%20d%3D%22M252%2C448%20L256%2C448%20L256%2C444%20L252%2C444%20L252%2C448%20Z%20M257%2C448%20L269%2C448%20L269%2C446%20L257%2C446%20L257%2C448%20Z%20M257%2C464%20L269%2C464%20L269%2C462%20L257%2C462%20L257%2C464%20Z%20M270%2C444%20L270%2C448%20L274%2C448%20L274%2C444%20L270%2C444%20Z%20M252%2C462%20L252%2C466%20L256%2C466%20L256%2C462%20L252%2C462%20Z%20M270%2C462%20L270%2C466%20L274%2C466%20L274%2C462%20L270%2C462%20Z%20M254%2C461%20L256%2C461%20L256%2C449%20L254%2C449%20L254%2C461%20Z%20M270%2C461%20L272%2C461%20L272%2C449%20L270%2C449%20L270%2C461%20Z%22%20fill%3D%22%23FD6EB6%22%20id%3D%22editor-crop-glyph%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E'); - } - } - - &-clone { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M12.852%2C0.875h-9.27c-0.853%2C0-1.547%2C0.694-1.547%2C1.547v10.816h1.547V2.422h9.27V0.875z%20M15.172%2C3.965h-8.5%20c-0.849%2C0-1.547%2C0.698-1.547%2C1.547v10.816c0%2C0.849%2C0.698%2C1.547%2C1.547%2C1.547h8.5c0.85%2C0%2C1.543-0.698%2C1.543-1.547V5.512%20C16.715%2C4.663%2C16.021%2C3.965%2C15.172%2C3.965L15.172%2C3.965z%20M15.172%2C16.328h-8.5V5.512h8.5V16.328z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - cursor: move; - - &:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cpath%20fill%3D%22%23FD6EB6%22%20d%3D%22M12.852%2C0.875h-9.27c-0.853%2C0-1.547%2C0.694-1.547%2C1.547v10.816h1.547V2.422h9.27V0.875z%20M15.172%2C3.965h-8.5%20c-0.849%2C0-1.547%2C0.698-1.547%2C1.547v10.816c0%2C0.849%2C0.698%2C1.547%2C1.547%2C1.547h8.5c0.849%2C0%2C1.543-0.698%2C1.543-1.547V5.512%20C16.715%2C4.663%2C16.021%2C3.965%2C15.172%2C3.965L15.172%2C3.965z%20M15.172%2C16.328h-8.5V5.512h8.5V16.328z%20M15.172%2C16.328%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - } - } - - &-link { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M9.884%2C9.838c0.54-0.551%2C1.005-0.955%2C1.384-1.201c0.463-0.308%2C0.749-0.352%2C0.887-0.352h1.34v1.367%20c0%2C0.104%2C0.061%2C0.2%2C0.154%2C0.242s0.204%2C0.027%2C0.284-0.038l3.168-2.669c0.06-0.051%2C0.096-0.125%2C0.096-0.203S17.16%2C6.83%2C17.101%2C6.781%20l-3.168-2.677c-0.08-0.067-0.19-0.081-0.284-0.038c-0.094%2C0.045-0.154%2C0.139-0.154%2C0.242v1.414h-1.343%20c-1.24%2C0.014-2.215%2C0.67-2.927%2C1.242c-0.797%2C0.65-1.533%2C1.447-2.245%2C2.217c-0.361%2C0.391-0.7%2C0.759-1.044%2C1.1%20c-0.541%2C0.549-1.011%2C0.951-1.395%2C1.199c-0.354%2C0.231-0.678%2C0.357-0.921%2C0.357h-1.8c-0.146%2C0-0.266%2C0.12-0.266%2C0.265v2.029%20c0%2C0.148%2C0.12%2C0.268%2C0.266%2C0.268h1.8l0%2C0c1.255-0.014%2C2.239-0.667%2C2.958-1.24c0.82-0.661%2C1.572-1.475%2C2.297-2.256%20C9.225%2C10.524%2C9.555%2C10.169%2C9.884%2C9.838z%22%2F%3E%3C%2Fsvg%3E%20'); - cursor: move; - cursor: -moz-grabbing; - cursor: -webkit-grabbing; - - &:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FD6EB6%22%20d%3D%22M9.884%2C9.838c0.54-0.551%2C1.005-0.955%2C1.384-1.201c0.463-0.308%2C0.749-0.352%2C0.887-0.352h1.34v1.367%20c0%2C0.104%2C0.061%2C0.2%2C0.154%2C0.242s0.204%2C0.027%2C0.284-0.038l3.168-2.669c0.06-0.051%2C0.096-0.125%2C0.096-0.203S17.16%2C6.83%2C17.101%2C6.781%20l-3.168-2.677c-0.08-0.067-0.19-0.081-0.284-0.038c-0.094%2C0.045-0.154%2C0.139-0.154%2C0.242v1.414h-1.343%20c-1.24%2C0.014-2.215%2C0.67-2.927%2C1.242c-0.797%2C0.65-1.533%2C1.447-2.245%2C2.217c-0.361%2C0.391-0.7%2C0.759-1.044%2C1.1%20c-0.541%2C0.549-1.011%2C0.951-1.395%2C1.199c-0.354%2C0.231-0.678%2C0.357-0.921%2C0.357h-1.8c-0.146%2C0-0.266%2C0.12-0.266%2C0.265v2.029%20c0%2C0.148%2C0.12%2C0.268%2C0.266%2C0.268h1.8l0%2C0c1.255-0.014%2C2.239-0.667%2C2.958-1.24c0.82-0.661%2C1.572-1.475%2C2.297-2.256%20C9.225%2C10.524%2C9.555%2C10.169%2C9.884%2C9.838z%22%2F%3E%3C%2Fsvg%3E%20'); - } - } - - &-fork { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cg%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20fill%3D%22%236A6C8A%22%20d%3D%22M13.307%2C11.593c-0.69%2C0-1.299%2C0.33-1.693%2C0.835l-4.136-2.387%20C7.552%2C9.82%2C7.602%2C9.589%2C7.602%2C9.344c0-0.25-0.051-0.487-0.129-0.71l4.097-2.364c0.393%2C0.536%2C1.022%2C0.888%2C1.737%2C0.888%20c1.193%2C0%2C2.16-0.967%2C2.16-2.159s-0.967-2.159-2.16-2.159c-1.191%2C0-2.158%2C0.967-2.158%2C2.159c0%2C0.076%2C0.014%2C0.149%2C0.021%2C0.223%20L6.848%2C7.716C6.469%2C7.39%2C5.982%2C7.185%2C5.442%2C7.185c-1.191%2C0-2.158%2C0.967-2.158%2C2.159s0.967%2C2.159%2C2.158%2C2.159%20c0.545%2C0%2C1.037-0.208%2C1.417-0.541l4.319%2C2.493c-0.014%2C0.098-0.029%2C0.194-0.029%2C0.296c0%2C1.193%2C0.967%2C2.159%2C2.158%2C2.159%20c1.193%2C0%2C2.16-0.966%2C2.16-2.159C15.467%2C12.559%2C14.5%2C11.593%2C13.307%2C11.593z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - cursor: move; - - &:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cg%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20fill%3D%22%23FD6EB6%22%20d%3D%22M13.307%2C11.593c-0.69%2C0-1.299%2C0.33-1.693%2C0.835l-4.136-2.387%20c0.075-0.22%2C0.125-0.452%2C0.125-0.697c0-0.25-0.051-0.487-0.129-0.71l4.097-2.365c0.394%2C0.536%2C1.022%2C0.888%2C1.737%2C0.888%20c1.193%2C0%2C2.16-0.967%2C2.16-2.159s-0.967-2.159-2.16-2.159c-1.191%2C0-2.158%2C0.967-2.158%2C2.159c0%2C0.076%2C0.015%2C0.148%2C0.022%2C0.223%20L6.848%2C7.716C6.469%2C7.39%2C5.981%2C7.185%2C5.442%2C7.185c-1.191%2C0-2.158%2C0.967-2.158%2C2.159s0.967%2C2.159%2C2.158%2C2.159%20c0.545%2C0%2C1.037-0.208%2C1.417-0.541l4.319%2C2.493c-0.013%2C0.098-0.029%2C0.194-0.029%2C0.296c0%2C1.193%2C0.967%2C2.159%2C2.158%2C2.159%20c1.193%2C0%2C2.16-0.966%2C2.16-2.159C15.467%2C12.559%2C14.5%2C11.593%2C13.307%2C11.593z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - } - } - - &-unlink { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cg%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M12.285%2C9.711l-2.104-0.302L9.243%2C8.568L6.669%2C7.095C6.948%2C6.6%2C6.995%2C6.026%2C6.845%2C5.474%20c-0.191-0.698-0.695-1.36-1.438-1.786C4.068%2C2.922%2C2.464%2C3.214%2C1.82%2C4.338C1.536%2C4.836%2C1.489%2C5.414%2C1.64%2C5.97%20c0.189%2C0.698%2C0.694%2C1.36%2C1.438%2C1.787c0.328%2C0.187%2C0.67%2C0.31%2C1.01%2C0.372c0.002%2C0%2C0.006%2C0.002%2C0.008%2C0.004%20c0.027%2C0.004%2C0.057%2C0.009%2C0.088%2C0.011c2.12%2C0.316%2C3.203%2C0.915%2C3.73%2C1.337c-0.527%2C0.424-1.61%2C1.021-3.731%2C1.339%20c-0.029%2C0.003-0.058%2C0.007-0.087%2C0.012c-0.002%2C0.002-0.004%2C0.002-0.007%2C0.003c-0.341%2C0.062-0.684%2C0.187-1.013%2C0.374%20c-0.74%2C0.425-1.246%2C1.089-1.437%2C1.787c-0.149%2C0.555-0.105%2C1.133%2C0.181%2C1.632c0.011%2C0.018%2C0.021%2C0.033%2C0.033%2C0.049l0.883%2C0.783%20c0.765%2C0.366%2C1.775%2C0.328%2C2.67-0.184c0.744-0.425%2C1.248-1.088%2C1.439-1.786c0.148-0.552%2C0.104-1.126-0.176-1.62l2.573-1.473%20c0.573%2C0.287%2C2.299%2C1.292%2C2.299%2C1.292s3.602%2C1.445%2C4.241%2C1.812c0.773%2C0.191%2C0.566-0.151%2C0.566-0.151L12.285%2C9.711z%20M5.571%2C6.482%20C5.279%2C6.993%2C4.425%2C7.076%2C3.705%2C6.664C3.282%2C6.424%2C2.966%2C6.039%2C2.856%2C5.64C2.81%2C5.464%2C2.778%2C5.203%2C2.917%2C4.963%20c0.291-0.51%2C1.146-0.593%2C1.866-0.182C5.21%2C5.027%2C5.521%2C5.4%2C5.632%2C5.807C5.679%2C5.98%2C5.708%2C6.242%2C5.571%2C6.482z%20M5.632%2C13.159%20c-0.111%2C0.406-0.422%2C0.778-0.848%2C1.025c-0.719%2C0.409-1.576%2C0.327-1.867-0.184c-0.137-0.239-0.106-0.499-0.06-0.676%20c0.108-0.398%2C0.426-0.781%2C0.847-1.022c0.72-0.412%2C1.574-0.329%2C1.866%2C0.181C5.708%2C12.723%2C5.679%2C12.983%2C5.632%2C13.159z%20M16.181%2C5.139%20c-0.448%2C0.258-4.435%2C1.9-4.435%2C1.9s-1.556%2C0.855-2.104%2C1.13l0.937%2C0.843l2.057-0.229l4.11-3.638%20C16.745%2C5.146%2C17.013%2C4.664%2C16.181%2C5.139z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - &:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cg%3E%3Cpath%20fill%3D%22%23FD6EB6%22%20d%3D%22M12.285%2C9.711l-2.104-0.302L9.243%2C8.568L6.669%2C7.095C6.948%2C6.6%2C6.995%2C6.026%2C6.845%2C5.474%20c-0.191-0.698-0.695-1.36-1.438-1.786C4.068%2C2.922%2C2.464%2C3.214%2C1.82%2C4.338C1.536%2C4.836%2C1.489%2C5.414%2C1.64%2C5.97%20c0.189%2C0.698%2C0.694%2C1.36%2C1.438%2C1.787c0.328%2C0.187%2C0.67%2C0.31%2C1.01%2C0.372c0.002%2C0%2C0.006%2C0.002%2C0.008%2C0.004%20c0.027%2C0.004%2C0.057%2C0.009%2C0.088%2C0.011c2.12%2C0.316%2C3.203%2C0.915%2C3.73%2C1.337c-0.527%2C0.424-1.61%2C1.021-3.731%2C1.339%20c-0.029%2C0.003-0.058%2C0.007-0.087%2C0.012c-0.002%2C0.002-0.004%2C0.002-0.007%2C0.003c-0.341%2C0.062-0.684%2C0.187-1.013%2C0.374%20c-0.74%2C0.425-1.246%2C1.089-1.437%2C1.787c-0.149%2C0.555-0.105%2C1.133%2C0.181%2C1.632c0.011%2C0.018%2C0.021%2C0.033%2C0.033%2C0.049l0.883%2C0.783%20c0.765%2C0.366%2C1.775%2C0.328%2C2.67-0.184c0.744-0.425%2C1.248-1.088%2C1.439-1.786c0.148-0.552%2C0.104-1.126-0.176-1.62l2.573-1.473%20c0.573%2C0.287%2C2.299%2C1.292%2C2.299%2C1.292s3.602%2C1.445%2C4.241%2C1.812c0.773%2C0.191%2C0.566-0.151%2C0.566-0.151L12.285%2C9.711z%20M5.571%2C6.482%20C5.279%2C6.993%2C4.425%2C7.076%2C3.705%2C6.664C3.282%2C6.424%2C2.966%2C6.039%2C2.856%2C5.64C2.81%2C5.464%2C2.778%2C5.203%2C2.917%2C4.963%20c0.291-0.51%2C1.146-0.593%2C1.866-0.182C5.21%2C5.027%2C5.521%2C5.4%2C5.632%2C5.807C5.679%2C5.98%2C5.708%2C6.242%2C5.571%2C6.482z%20M5.632%2C13.159%20c-0.111%2C0.406-0.422%2C0.778-0.848%2C1.025c-0.719%2C0.409-1.576%2C0.327-1.867-0.184c-0.137-0.239-0.106-0.499-0.06-0.676%20c0.108-0.398%2C0.426-0.781%2C0.847-1.022c0.72-0.412%2C1.574-0.329%2C1.866%2C0.181C5.708%2C12.723%2C5.679%2C12.983%2C5.632%2C13.159z%20M16.181%2C5.139%20c-0.448%2C0.258-4.435%2C1.9-4.435%2C1.9s-1.556%2C0.855-2.104%2C1.13l0.937%2C0.843l2.057-0.229l4.11-3.638%20C16.745%2C5.146%2C17.013%2C4.664%2C16.181%2C5.139z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - } - } - - &-direction { - background-image: url("data:image/svg+xml;charset=UTF-8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3C!DOCTYPE%20svg%20%20PUBLIC%20'-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN'%20%20'http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd'%3E%3Csvg%20id%3D%22Layer_1%22%20style%3D%22enable-background%3Anew%200%200%20512%20512%3B%22%20version%3D%221.1%22%20viewBox%3D%220%200%20512%20512%22%20xml%3Aspace%3D%22preserve%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%236A6C8A%3Bstroke%3A%236A6C8A%3Bstroke-width%3A30%7D%0A%09.dot%7Bfill%3A%236A6C8A%3B%7D%0A%3C%2Fstyle%3E%3Cg%3E%3Cg%20id%3D%22XMLID_475_%22%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M133.1%2C277.1c1.8%2C0%2C3.7-0.6%2C5.4-1.7c4.1-3%2C5-8.7%2C2-12.8c-3-4.1-8.7-5-12.8-2c0%2C0%2C0%2C0%2C0%2C0%20%20%20%20%20c-4.1%2C3-5%2C8.7-2%2C12.8C127.5%2C275.8%2C130.3%2C277.1%2C133.1%2C277.1z%22%20id%3D%22XMLID_489_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M138.5%2C359.6c-4.1-3-9.8-2.1-12.8%2C2c-3%2C4.1-2.1%2C9.8%2C2%2C12.8c1.6%2C1.2%2C3.5%2C1.7%2C5.4%2C1.7%20%20%20%20%20c2.8%2C0%2C5.6-1.3%2C7.4-3.7C143.5%2C368.3%2C142.6%2C362.6%2C138.5%2C359.6z%22%20id%3D%22XMLID_726_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M108.1%2C327.7c-4.8%2C1.6-7.4%2C6.7-5.9%2C11.5c1.3%2C3.9%2C4.8%2C6.3%2C8.7%2C6.3c0.9%2C0%2C1.9-0.1%2C2.8-0.4%20%20%20%20%20c4.8-1.6%2C7.4-6.7%2C5.9-11.5C118%2C328.8%2C112.9%2C326.2%2C108.1%2C327.7z%22%20id%3D%22XMLID_776_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M108.1%2C307.3c0.9%2C0.3%2C1.9%2C0.4%2C2.8%2C0.4c3.8%2C0%2C7.4-2.4%2C8.7-6.3c1.6-4.8-1.1-9.9-5.9-11.5%20%20%20%20%20c-4.8-1.6-9.9%2C1.1-11.5%2C5.9C100.7%2C300.6%2C103.3%2C305.7%2C108.1%2C307.3z%22%20id%3D%22XMLID_777_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M169.2%2C265.4c2.4%2C0%2C4.7-1%2C6.5-2.6c1.7-1.7%2C2.7-4.1%2C2.7-6.5c0-2.4-1-4.8-2.7-6.5%20%20%20%20%20c-1.7-1.7-4.1-2.7-6.5-2.7s-4.7%2C1-6.5%2C2.7c-1.7%2C1.7-2.7%2C4-2.7%2C6.5c0%2C2.4%2C1%2C4.7%2C2.7%2C6.5C164.4%2C264.4%2C166.8%2C265.4%2C169.2%2C265.4z%22%20id%3D%22XMLID_797_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M247.7%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C243.7%2C265.4%2C247.7%2C261.3%2C247.7%2C256.3z%22%20id%3D%22XMLID_798_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M213%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C208.9%2C265.4%2C213%2C261.3%2C213%2C256.3z%22%20id%3D%22XMLID_799_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M317.2%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C313.1%2C265.4%2C317.2%2C261.3%2C317.2%2C256.3z%22%20id%3D%22XMLID_800_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M282.5%2C256.3c0-5-4.1-9.1-9.1-9.1s-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20S282.5%2C261.3%2C282.5%2C256.3z%22%20id%3D%22XMLID_801_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M401.1%2C185.2c0.9%2C0%2C1.9-0.1%2C2.8-0.5c4.8-1.6%2C7.4-6.7%2C5.9-11.5c-1.6-4.8-6.7-7.4-11.5-5.8%20%20%20%20%20c-4.8%2C1.6-7.4%2C6.7-5.8%2C11.5C393.6%2C182.8%2C397.2%2C185.2%2C401.1%2C185.2z%22%20id%3D%22XMLID_802_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M403.9%2C205.2c-4.8-1.6-9.9%2C1-11.5%2C5.9l0%2C0c-1.6%2C4.8%2C1.1%2C9.9%2C5.9%2C11.5%20%20%20%20%20c0.9%2C0.3%2C1.9%2C0.5%2C2.8%2C0.5c3.9%2C0%2C7.4-2.5%2C8.7-6.3c0%2C0%2C0%2C0%2C0%2C0C411.3%2C211.9%2C408.7%2C206.8%2C403.9%2C205.2z%22%20id%3D%22XMLID_803_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M373.5%2C237.2L373.5%2C237.2c-4.1%2C3-5%2C8.7-2%2C12.8c1.8%2C2.4%2C4.6%2C3.7%2C7.4%2C3.7%20%20%20%20%20c1.8%2C0%2C3.7-0.6%2C5.4-1.8c4.1-3%2C4.9-8.7%2C2-12.8C383.3%2C235.1%2C377.6%2C234.2%2C373.5%2C237.2z%22%20id%3D%22XMLID_804_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M373.5%2C152.9c1.6%2C1.2%2C3.5%2C1.8%2C5.4%2C1.8c2.8%2C0%2C5.6-1.3%2C7.4-3.8c3-4.1%2C2.1-9.8-2-12.7%20%20%20%20%20c-4.1-3-9.8-2.1-12.7%2C2C368.5%2C144.2%2C369.4%2C149.9%2C373.5%2C152.9z%22%20id%3D%22XMLID_805_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M342.8%2C247.1c-2.4%2C0-4.8%2C1-6.5%2C2.7c-1.7%2C1.7-2.7%2C4-2.7%2C6.5c0%2C2.4%2C1%2C4.7%2C2.7%2C6.4%20%20%20%20%20c1.7%2C1.7%2C4%2C2.7%2C6.5%2C2.7c2.4%2C0%2C4.7-1%2C6.5-2.7c1.7-1.7%2C2.7-4%2C2.7-6.4c0-2.4-1-4.8-2.7-6.5C347.6%2C248.1%2C345.2%2C247.1%2C342.8%2C247.1z%22%20id%3D%22XMLID_806_%22%2F%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M342.8%2C124.7H206.6l36.4-36.4c3.6-3.6%2C3.6-9.3%2C0-12.9c-3.6-3.6-9.3-3.6-12.9%2C0l-51.5%2C51.5%20%20%20%20%20c-1.9%2C1.9-2.8%2C4.4-2.7%2C6.9c-0.1%2C2.5%2C0.7%2C5%2C2.7%2C6.9l51.5%2C51.5c1.8%2C1.8%2C4.1%2C2.7%2C6.5%2C2.7c2.3%2C0%2C4.7-0.9%2C6.5-2.7%20%20%20%20%20c3.6-3.6%2C3.6-9.3%2C0-12.9l-36.4-36.4h136.1c0%2C0%2C0.1%2C0%2C0.1%2C0c0.6%2C0%2C1.2-0.1%2C1.8-0.2c0.2%2C0%2C0.4-0.1%2C0.6-0.1c0.1%2C0%2C0.2%2C0%2C0.3-0.1%20%20%20%20%20c3.2-1%2C5.6-3.6%2C6.3-6.9c0.1-0.6%2C0.2-1.2%2C0.2-1.8c0-0.6-0.1-1.2-0.2-1.8C351%2C127.8%2C347.3%2C124.7%2C342.8%2C124.7z%22%20id%3D%22XMLID_807_%22%2F%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M322.1%2C371.3l-51.5-51.5c-3.6-3.6-9.3-3.6-12.9%2C0c-3.6%2C3.6-3.6%2C9.3%2C0%2C12.9l36.9%2C36.9H169.2%20%20%20%20%20c-2.8%2C0-5.4%2C1.3-7%2C3.3c-0.1%2C0.1-0.2%2C0.2-0.3%2C0.4c-0.1%2C0.1-0.2%2C0.2-0.2%2C0.3c-0.1%2C0.1-0.1%2C0.2-0.2%2C0.4c-0.1%2C0.1-0.2%2C0.3-0.2%2C0.4%20%20%20%20%20c0%2C0.1-0.1%2C0.2-0.1%2C0.2c-0.1%2C0.2-0.2%2C0.4-0.3%2C0.6c0%2C0%2C0%2C0%2C0%2C0.1c-0.4%2C1.1-0.7%2C2.2-0.7%2C3.4c0%2C1.5%2C0.4%2C2.9%2C1%2C4.2c0%2C0%2C0%2C0.1%2C0.1%2C0.1%20%20%20%20%20c0.1%2C0.1%2C0.1%2C0.2%2C0.2%2C0.3c0.4%2C0.7%2C0.9%2C1.3%2C1.4%2C1.8c0.4%2C0.4%2C0.7%2C0.7%2C1.2%2C1c0.1%2C0.1%2C0.1%2C0.1%2C0.2%2C0.2c0%2C0%2C0.1%2C0%2C0.1%2C0.1%20%20%20%20%20c1.4%2C0.9%2C3.1%2C1.5%2C5%2C1.5h124.4l-36%2C36c-3.6%2C3.6-3.6%2C9.3%2C0%2C12.9c1.8%2C1.8%2C4.1%2C2.7%2C6.5%2C2.7c2.3%2C0%2C4.7-0.9%2C6.5-2.7l51.5-51.5%20%20%20%20%20c1.9-1.9%2C2.8-4.4%2C2.7-6.9C324.8%2C375.7%2C324%2C373.2%2C322.1%2C371.3z%22%20id%3D%22XMLID_808_%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E"); - &:hover { - background-image: url("data:image/svg+xml;charset=UTF-8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3C!DOCTYPE%20svg%20%20PUBLIC%20'-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN'%20%20'http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd'%3E%3Csvg%20id%3D%22Layer_1%22%20style%3D%22enable-background%3Anew%200%200%20512%20512%3B%22%20version%3D%221.1%22%20viewBox%3D%220%200%20512%20512%22%20xml%3Aspace%3D%22preserve%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%23FD6EB6%3Bstroke%3A%23FD6EB6%3Bstroke-width%3A30%7D%0A%09.dot%7Bfill%3A%23FD6EB6%3B%7D%0A%3C%2Fstyle%3E%3Cg%3E%3Cg%20id%3D%22XMLID_475_%22%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M133.1%2C277.1c1.8%2C0%2C3.7-0.6%2C5.4-1.7c4.1-3%2C5-8.7%2C2-12.8c-3-4.1-8.7-5-12.8-2c0%2C0%2C0%2C0%2C0%2C0%20%20%20%20%20c-4.1%2C3-5%2C8.7-2%2C12.8C127.5%2C275.8%2C130.3%2C277.1%2C133.1%2C277.1z%22%20id%3D%22XMLID_489_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M138.5%2C359.6c-4.1-3-9.8-2.1-12.8%2C2c-3%2C4.1-2.1%2C9.8%2C2%2C12.8c1.6%2C1.2%2C3.5%2C1.7%2C5.4%2C1.7%20%20%20%20%20c2.8%2C0%2C5.6-1.3%2C7.4-3.7C143.5%2C368.3%2C142.6%2C362.6%2C138.5%2C359.6z%22%20id%3D%22XMLID_726_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M108.1%2C327.7c-4.8%2C1.6-7.4%2C6.7-5.9%2C11.5c1.3%2C3.9%2C4.8%2C6.3%2C8.7%2C6.3c0.9%2C0%2C1.9-0.1%2C2.8-0.4%20%20%20%20%20c4.8-1.6%2C7.4-6.7%2C5.9-11.5C118%2C328.8%2C112.9%2C326.2%2C108.1%2C327.7z%22%20id%3D%22XMLID_776_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M108.1%2C307.3c0.9%2C0.3%2C1.9%2C0.4%2C2.8%2C0.4c3.8%2C0%2C7.4-2.4%2C8.7-6.3c1.6-4.8-1.1-9.9-5.9-11.5%20%20%20%20%20c-4.8-1.6-9.9%2C1.1-11.5%2C5.9C100.7%2C300.6%2C103.3%2C305.7%2C108.1%2C307.3z%22%20id%3D%22XMLID_777_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M169.2%2C265.4c2.4%2C0%2C4.7-1%2C6.5-2.6c1.7-1.7%2C2.7-4.1%2C2.7-6.5c0-2.4-1-4.8-2.7-6.5%20%20%20%20%20c-1.7-1.7-4.1-2.7-6.5-2.7s-4.7%2C1-6.5%2C2.7c-1.7%2C1.7-2.7%2C4-2.7%2C6.5c0%2C2.4%2C1%2C4.7%2C2.7%2C6.5C164.4%2C264.4%2C166.8%2C265.4%2C169.2%2C265.4z%22%20id%3D%22XMLID_797_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M247.7%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C243.7%2C265.4%2C247.7%2C261.3%2C247.7%2C256.3z%22%20id%3D%22XMLID_798_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M213%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C208.9%2C265.4%2C213%2C261.3%2C213%2C256.3z%22%20id%3D%22XMLID_799_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M317.2%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C313.1%2C265.4%2C317.2%2C261.3%2C317.2%2C256.3z%22%20id%3D%22XMLID_800_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M282.5%2C256.3c0-5-4.1-9.1-9.1-9.1s-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20S282.5%2C261.3%2C282.5%2C256.3z%22%20id%3D%22XMLID_801_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M401.1%2C185.2c0.9%2C0%2C1.9-0.1%2C2.8-0.5c4.8-1.6%2C7.4-6.7%2C5.9-11.5c-1.6-4.8-6.7-7.4-11.5-5.8%20%20%20%20%20c-4.8%2C1.6-7.4%2C6.7-5.8%2C11.5C393.6%2C182.8%2C397.2%2C185.2%2C401.1%2C185.2z%22%20id%3D%22XMLID_802_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M403.9%2C205.2c-4.8-1.6-9.9%2C1-11.5%2C5.9l0%2C0c-1.6%2C4.8%2C1.1%2C9.9%2C5.9%2C11.5%20%20%20%20%20c0.9%2C0.3%2C1.9%2C0.5%2C2.8%2C0.5c3.9%2C0%2C7.4-2.5%2C8.7-6.3c0%2C0%2C0%2C0%2C0%2C0C411.3%2C211.9%2C408.7%2C206.8%2C403.9%2C205.2z%22%20id%3D%22XMLID_803_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M373.5%2C237.2L373.5%2C237.2c-4.1%2C3-5%2C8.7-2%2C12.8c1.8%2C2.4%2C4.6%2C3.7%2C7.4%2C3.7%20%20%20%20%20c1.8%2C0%2C3.7-0.6%2C5.4-1.8c4.1-3%2C4.9-8.7%2C2-12.8C383.3%2C235.1%2C377.6%2C234.2%2C373.5%2C237.2z%22%20id%3D%22XMLID_804_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M373.5%2C152.9c1.6%2C1.2%2C3.5%2C1.8%2C5.4%2C1.8c2.8%2C0%2C5.6-1.3%2C7.4-3.8c3-4.1%2C2.1-9.8-2-12.7%20%20%20%20%20c-4.1-3-9.8-2.1-12.7%2C2C368.5%2C144.2%2C369.4%2C149.9%2C373.5%2C152.9z%22%20id%3D%22XMLID_805_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M342.8%2C247.1c-2.4%2C0-4.8%2C1-6.5%2C2.7c-1.7%2C1.7-2.7%2C4-2.7%2C6.5c0%2C2.4%2C1%2C4.7%2C2.7%2C6.4%20%20%20%20%20c1.7%2C1.7%2C4%2C2.7%2C6.5%2C2.7c2.4%2C0%2C4.7-1%2C6.5-2.7c1.7-1.7%2C2.7-4%2C2.7-6.4c0-2.4-1-4.8-2.7-6.5C347.6%2C248.1%2C345.2%2C247.1%2C342.8%2C247.1z%22%20id%3D%22XMLID_806_%22%2F%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M342.8%2C124.7H206.6l36.4-36.4c3.6-3.6%2C3.6-9.3%2C0-12.9c-3.6-3.6-9.3-3.6-12.9%2C0l-51.5%2C51.5%20%20%20%20%20c-1.9%2C1.9-2.8%2C4.4-2.7%2C6.9c-0.1%2C2.5%2C0.7%2C5%2C2.7%2C6.9l51.5%2C51.5c1.8%2C1.8%2C4.1%2C2.7%2C6.5%2C2.7c2.3%2C0%2C4.7-0.9%2C6.5-2.7%20%20%20%20%20c3.6-3.6%2C3.6-9.3%2C0-12.9l-36.4-36.4h136.1c0%2C0%2C0.1%2C0%2C0.1%2C0c0.6%2C0%2C1.2-0.1%2C1.8-0.2c0.2%2C0%2C0.4-0.1%2C0.6-0.1c0.1%2C0%2C0.2%2C0%2C0.3-0.1%20%20%20%20%20c3.2-1%2C5.6-3.6%2C6.3-6.9c0.1-0.6%2C0.2-1.2%2C0.2-1.8c0-0.6-0.1-1.2-0.2-1.8C351%2C127.8%2C347.3%2C124.7%2C342.8%2C124.7z%22%20id%3D%22XMLID_807_%22%2F%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M322.1%2C371.3l-51.5-51.5c-3.6-3.6-9.3-3.6-12.9%2C0c-3.6%2C3.6-3.6%2C9.3%2C0%2C12.9l36.9%2C36.9H169.2%20%20%20%20%20c-2.8%2C0-5.4%2C1.3-7%2C3.3c-0.1%2C0.1-0.2%2C0.2-0.3%2C0.4c-0.1%2C0.1-0.2%2C0.2-0.2%2C0.3c-0.1%2C0.1-0.1%2C0.2-0.2%2C0.4c-0.1%2C0.1-0.2%2C0.3-0.2%2C0.4%20%20%20%20%20c0%2C0.1-0.1%2C0.2-0.1%2C0.2c-0.1%2C0.2-0.2%2C0.4-0.3%2C0.6c0%2C0%2C0%2C0%2C0%2C0.1c-0.4%2C1.1-0.7%2C2.2-0.7%2C3.4c0%2C1.5%2C0.4%2C2.9%2C1%2C4.2c0%2C0%2C0%2C0.1%2C0.1%2C0.1%20%20%20%20%20c0.1%2C0.1%2C0.1%2C0.2%2C0.2%2C0.3c0.4%2C0.7%2C0.9%2C1.3%2C1.4%2C1.8c0.4%2C0.4%2C0.7%2C0.7%2C1.2%2C1c0.1%2C0.1%2C0.1%2C0.1%2C0.2%2C0.2c0%2C0%2C0.1%2C0%2C0.1%2C0.1%20%20%20%20%20c1.4%2C0.9%2C3.1%2C1.5%2C5%2C1.5h124.4l-36%2C36c-3.6%2C3.6-3.6%2C9.3%2C0%2C12.9c1.8%2C1.8%2C4.1%2C2.7%2C6.5%2C2.7c2.3%2C0%2C4.7-0.9%2C6.5-2.7l51.5-51.5%20%20%20%20%20c1.9-1.9%2C2.8-4.4%2C2.7-6.9C324.8%2C375.7%2C324%2C373.2%2C322.1%2C371.3z%22%20id%3D%22XMLID_808_%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E"); - } - } -} - -.@{handle-prefix-cls} { - &-surround & { - &-animate { - .@{handle-prefix-cls} { - transition: background-size 80ms, width 80ms, height 80ms, top 150ms, - left 150ms, bottom 150ms, right 150ms; - } - } - - & { - &-pos-se { - right: -25px; - bottom: -25px; - } - - &-pos-nw { - top: -21px; - left: -25px; - } - - &-pos-n { - top: -22px; - left: 50%; - margin-left: -10px; - } - - &-pos-e { - top: -webkit-calc(50% - 10px); - top: calc(50% - 10px); - right: -25px; - } - - &-pos-ne { - top: -21px; - right: -25px; - } - - &-pos-w { - top: 50%; - left: -25px; - margin-top: -10px; - } - - &-pos-sw { - bottom: -25px; - left: -25px; - } - - &-pos-s { - bottom: -24px; - left: 50%; - margin-left: -10px; - } - } - - &-small { - .@{handle-prefix-cls} { - width: 15px; - height: 15px; - font-size: 15px; - background-size: 15px 15px; - - &-pos-se { - right: -19px; - bottom: -19px; - } - - &-pos-nw { - top: -19px; - left: -19px; - } - - &-pos-n { - top: -19px; - margin-left: -7.5px; - } - - &-pos-e { - top: -webkit-calc(50% - 8px); - top: calc(50% - 8px); - right: -19px; - } - - &-pos-ne { - top: -19px; - right: -19px; - } - - &-pos-w { - left: -19px; - margin-top: -8px; - } - - &-pos-sw { - bottom: -19px; - left: -19px; - } - - &-pos-s { - bottom: -19px; - margin-left: -7.5px; - } - } - } - - &-tiny { - .@{handle-prefix-cls} { - width: 10px; - height: 10px; - font-size: 10px; - background-size: 10px 10px; - - &-pos-se { - right: -15px; - bottom: -13px; - } - - &-pos-nw { - top: -13px; - left: -15px; - } - - &-pos-n { - top: -13px; - margin-left: -5px; - } - - &-pos-e { - top: -webkit-calc(50% - 5px); - top: calc(50% - 5px); - right: -15px; - } - - &-pos-ne { - top: -13px; - right: -15px; - } - - &-pos-w { - left: -15px; - margin-top: -5px; - } - - &-pos-sw { - bottom: -13px; - left: -15px; - } - - &-pos-s { - bottom: -13px; - margin-left: -5px; - } - } - } - } - - &-toolbar { - position: absolute; - top: -50px; - display: table-row; - padding: 7px 5px; - - &::after { - position: absolute; - top: 100%; - left: 10px; - width: 0; - height: 0; - margin-top: 4px; - border-right: 10px solid transparent; - border-left: 10px solid transparent; - content: ''; - } - - .@{handle-prefix-cls} { - position: relative; - display: table-cell; - min-width: 20px; - margin: 0 2px; - background-position: 3px 3px; - background-size: 16px 16px; - - &::after { - position: absolute; - bottom: -11px; - width: 100%; - content: ''; - } - } - } - - &-pie { - position: absolute; - top: -webkit-calc(50% - 50px); - top: calc(50% - 50px); - right: -50px; - z-index: 1; - display: none; - width: 100px; - height: 100px; - margin: -2px -2px 0 0; - border-radius: 50%; - cursor: default; - pointer-events: visiblePainted; - - .@{handle-prefix-cls} { - width: 1px; - height: auto; - pointer-events: visiblePainted; - } - - &-slice-svg { - width: 100%; - height: 100%; - overflow: visible !important; - } - - &-slice-img, - &-slice-txt { - display: none; - pointer-events: none; - } - - &[data-pie-toggle-position='e'] { - top: calc(50% - 50px); - right: -50px; - left: auto; - } - - &[data-pie-toggle-position='w'] { - top: calc(50% - 50px); - right: auto; - left: -52px; - } - - &[data-pie-toggle-position='n'] { - top: -50px; - right: auto; - bottom: auto; - left: calc(50% - 52px); - } - - &[data-pie-toggle-position='s'] { - top: auto; - right: auto; - bottom: -52px; - left: calc(50% - 52px); - } - - &-opened { - display: block; - animation: halo-pie-visibility 0.1s, halo-pie-opening 0.1s; - animation-timing-function: step-end, ease; - animation-delay: 0s, 0.1s; - } - - &-toggle { - position: absolute; - top: -webkit-calc(50% - 15px); - top: calc(50% - 15px); - right: -15px; - z-index: 2; - display: block; - box-sizing: border-box; - width: 30px; - height: 30px; - background-repeat: no-repeat; - background-position: center; - background-size: 20px 20px; - border-radius: 50%; - cursor: pointer; - user-select: none; - pointer-events: visiblePainted; - - -webkit-user-drag: none; - user-drag: none; /* stylelint-disable-line */ - - &-pos-e { - top: -webkit-calc(50% - 15px); - top: calc(50% - 15px); - right: -15px; - bottom: auto; - left: auto; - } - - &-pos-w { - top: -webkit-calc(50% - 15px); - top: calc(50% - 15px); - right: auto; - bottom: auto; - left: -15px; - } - - &-pos-n { - top: -15px; - right: auto; - bottom: auto; - left: -webkit-calc(50% - 15px); - left: calc(50% - 15px); - } - - &-pos-s { - top: auto; - right: auto; - bottom: -15px; - left: -webkit-calc(50% - 15px); - left: calc(50% - 15px); - } - - &-opened { - transition: 0.1s background-image; - } - } - } -} - -// Default theme - -.@{handle-prefix-cls} { - &-toolbar { - position: static; - display: inline-block; - margin-top: -50px; - margin-left: 45px; - white-space: nowrap; - vertical-align: top; - background-color: #f5f5f5; - border-bottom: 3px solid #333; - border-radius: 5px; - box-shadow: 0 1px 2px #222; - - &::after { - top: -12px; - left: 55px; - margin-top: 0; - border-top: 6px solid #333; - border-right: 10px solid transparent; - border-left: 10px solid transparent; - } - - .@{handle-prefix-cls} { - display: inline-block; - vertical-align: top; - - &:hover::after { - border-bottom: 4px solid #fc6cb8; - } - - &-rotate { - position: absolute; - top: 100%; - right: 100%; - margin-top: 3px; - margin-right: 6px; - } - - &-remove:hover::after, - &-rotate:hover::after { - border-bottom: none; - } - } - - .@{handle-prefix-cls} + .@{handle-prefix-cls} { - margin-left: 4px; - } - } - - &-pie { - box-sizing: content-box; - background-color: #f5f5f5; - border: 2px solid #404040; - - &-slice { - fill: transparent; - stroke: #e9e9e9; - stroke-width: 1; - - &:hover { - fill: #fff; - } - - &-img { - display: block; - } - } - - .@{handle-prefix-cls}-selected &-slice { - fill: #fff; - } - - &-toggle { - background-color: #f6f6f6; - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20height%3D%2216px%22%20id%3D%22Layer_1%22%20style%3D%22enable-background%3Anew%200%200%2016%2016%3B%22%20version%3D%221.1%22%20viewBox%3D%220%200%2016%2016%22%20width%3D%2216px%22%20xml%3Aspace%3D%22preserve%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M15%2C6h-5V1c0-0.55-0.45-1-1-1H7C6.45%2C0%2C6%2C0.45%2C6%2C1v5H1C0.45%2C6%2C0%2C6.45%2C0%2C7v2c0%2C0.55%2C0.45%2C1%2C1%2C1h5v5c0%2C0.55%2C0.45%2C1%2C1%2C1h2%20c0.55%2C0%2C1-0.45%2C1-1v-5h5c0.55%2C0%2C1-0.45%2C1-1V7C16%2C6.45%2C15.55%2C6%2C15%2C6z%22%2F%3E%3C%2Fsvg%3E'); - background-size: 16px 16px; - border: 2px solid #3b425f; - - &:hover { - background-color: #fff; - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20height%3D%2216px%22%20id%3D%22Layer_1%22%20style%3D%22enable-background%3Anew%200%200%2016%2016%3B%22%20version%3D%221.1%22%20viewBox%3D%220%200%2016%2016%22%20width%3D%2216px%22%20xml%3Aspace%3D%22preserve%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cpath%20fill%3D%22%23FD6EB6%22%20d%3D%22M15%2C6h-5V1c0-0.55-0.45-1-1-1H7C6.45%2C0%2C6%2C0.45%2C6%2C1v5H1C0.45%2C6%2C0%2C6.45%2C0%2C7v2c0%2C0.55%2C0.45%2C1%2C1%2C1h5v5c0%2C0.55%2C0.45%2C1%2C1%2C1h2%20c0.55%2C0%2C1-0.45%2C1-1v-5h5c0.55%2C0%2C1-0.45%2C1-1V7C16%2C6.45%2C15.55%2C6%2C15%2C6z%22%2F%3E%3C%2Fsvg%3E'); - border-color: #fd6eb6; - } - } - - &-toggle { - &-opened { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22no%22%3F%3E%3Csvg%20xmlns%3Adc%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%22%20xmlns%3Acc%3D%22http%3A%2F%2Fcreativecommons.org%2Fns%23%22%20xmlns%3Ardf%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%22%20xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20id%3D%22Layer_1%22%20xml%3Aspace%3D%22preserve%22%3E%3Cmetadata%20id%3D%22metadata9%22%3E%3Crdf%3ARDF%3E%3Ccc%3AWork%20rdf%3Aabout%3D%22%22%3E%3Cdc%3Aformat%3Eimage%2Fsvg%2Bxml%3C%2Fdc%3Aformat%3E%3Cdc%3Atype%20rdf%3Aresource%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Fdcmitype%2FStillImage%22%20%2F%3E%3Cdc%3Atitle%3E%3C%2Fdc%3Atitle%3E%3C%2Fcc%3AWork%3E%3C%2Frdf%3ARDF%3E%3C%2Fmetadata%3E%3Cdefs%20id%3D%22defs7%22%20%2F%3E%3Cpath%20d%3D%22M%2015%2C6%2010%2C6%20C%201.0301983%2C6.00505%2015.002631%2C6.011353%206%2C6%20L%201%2C6%20C%200.45%2C6%200%2C6.45%200%2C7%20l%200%2C2%20c%200%2C0.55%200.45%2C1%201%2C1%20l%205%2C0%20c%208.988585%2C-0.019732%20-5.02893401%2C-0.018728%204%2C0%20l%205%2C0%20c%200.55%2C0%201%2C-0.45%201%2C-1%20L%2016%2C7%20C%2016%2C6.45%2015.55%2C6%2015%2C6%20z%22%20id%3D%22path3%22%20style%3D%22fill%3A%236a6c8a%22%20%2F%3E%3C%2Fsvg%3E'); - &:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22no%22%3F%3E%3Csvg%20xmlns%3Adc%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%22%20xmlns%3Acc%3D%22http%3A%2F%2Fcreativecommons.org%2Fns%23%22%20xmlns%3Ardf%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%22%20xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20id%3D%22Layer_1%22%20xml%3Aspace%3D%22preserve%22%3E%3Cmetadata%20id%3D%22metadata9%22%3E%3Crdf%3ARDF%3E%3Ccc%3AWork%20rdf%3Aabout%3D%22%22%3E%3Cdc%3Aformat%3Eimage%2Fsvg%2Bxml%3C%2Fdc%3Aformat%3E%3Cdc%3Atype%20rdf%3Aresource%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Fdcmitype%2FStillImage%22%20%2F%3E%3Cdc%3Atitle%3E%3C%2Fdc%3Atitle%3E%3C%2Fcc%3AWork%3E%3C%2Frdf%3ARDF%3E%3C%2Fmetadata%3E%3Cdefs%20id%3D%22defs7%22%20%2F%3E%3Cpath%20d%3D%22M%2015%2C6%2010%2C6%20C%201.0301983%2C6.00505%2015.002631%2C6.011353%206%2C6%20L%201%2C6%20C%200.45%2C6%200%2C6.45%200%2C7%20l%200%2C2%20c%200%2C0.55%200.45%2C1%201%2C1%20l%205%2C0%20c%208.988585%2C-0.019732%20-5.02893401%2C-0.018728%204%2C0%20l%205%2C0%20c%200.55%2C0%201%2C-0.45%201%2C-1%20L%2016%2C7%20C%2016%2C6.45%2015.55%2C6%2015%2C6%20z%22%20id%3D%22path3%22%20style%3D%22fill%3A%23FD6EB6%22%20%2F%3E%3C%2Fsvg%3E'); - } - } - } - } -} diff --git a/packages/x6/src/addon/common/handle.ts b/packages/x6/src/addon/common/handle.ts deleted file mode 100644 index 94f540cecf9..00000000000 --- a/packages/x6/src/addon/common/handle.ts +++ /dev/null @@ -1,541 +0,0 @@ -import { Dom, Vector } from '../../util' -import { View } from '../../view/view' -import { Graph } from '../../graph/graph' -import { Point, Angle } from '../../geometry' - -export class Handle { - public readonly graph: Graph - protected readonly handleOptions: Handle.Options - protected handles: Handle.Metadata[] - protected $handleContainer: JQuery - protected $pieToggles: { [name: string]: JQuery } - - protected get handleClassName() { - return ClassNames.handle - } - - protected get pie() { - return { - ...Handle.defaultPieOptions, - ...this.handleOptions.pie, - } - } - - protected initHandles(this: Handle & View) { - this.handles = [] - - if (this.handleOptions.handles) { - this.handleOptions.handles.forEach((handle) => this.addHandle(handle)) - } - - if (this.handleOptions.type === 'pie') { - if (this.pie.toggles) { - const className = ClassNames.pieToggle - this.$pieToggles = {} - this.pie.toggles.forEach((item) => { - const $elem = this.$('
') - this.applyAttrs($elem, item.attrs) - $elem - .addClass(className) - .addClass(`${className}-pos-${item.position || 'e'}`) - .attr('data-name', item.name) - .appendTo(this.container) - this.$pieToggles[item.name] = $elem - }) - } - - this.setPieIcons() - } - - if (this.$handleContainer) { - const type = this.handleOptions.type || 'surround' - this.$handleContainer - .addClass(ClassNames.wrap) - .addClass(ClassNames.animate) - .addClass(`${ClassNames.handle}-${type}`) - } - - this.delegateEvents({ - [`mousedown .${ClassNames.handle}`]: 'onHandleMouseDown', - [`touchstart .${ClassNames.handle}`]: 'onHandleMouseDown', - [`mousedown .${ClassNames.pieToggle}`]: 'onPieToggleMouseDown', - [`touchstart .${ClassNames.pieToggle}`]: 'onPieToggleMouseDown', - }) - } - - protected onHandleMouseDown(this: Handle & View, evt: JQuery.MouseDownEvent) { - const action = this.$(evt.currentTarget) - .closest(`.${ClassNames.handle}`) - .attr('data-action') - - if (action) { - evt.preventDefault() - evt.stopPropagation() - this.setEventData(evt, { - action, - clientX: evt.clientX, - clientY: evt.clientY, - startX: evt.clientX, - startY: evt.clientY, - }) - - if (evt.type === 'mousedown' && evt.button === 2) { - this.triggerHandleAction(action, 'contextmenu', evt) - } else { - this.triggerHandleAction(action, 'mousedown', evt) - this.delegateDocumentEvents( - { - mousemove: 'onHandleMouseMove', - touchmove: 'onHandleMouseMove', - mouseup: 'onHandleMouseUp', - touchend: 'onHandleMouseUp', - touchcancel: 'onHandleMouseUp', - }, - evt.data, - ) - } - } - } - - protected onHandleMouseMove(this: Handle & View, evt: JQuery.MouseMoveEvent) { - const data = this.getEventData(evt) - const action = data.action - if (action) { - this.triggerHandleAction(action, 'mousemove', evt) - } - } - - protected onHandleMouseUp(this: Handle & View, evt: JQuery.MouseUpEvent) { - const data = this.getEventData(evt) - const action = data.action - if (action) { - this.triggerHandleAction(action, 'mouseup', evt) - this.undelegateDocumentEvents() - } - } - - protected triggerHandleAction( - this: Handle & View, - action: string, - eventName: string, - evt: JQuery.TriggeredEvent, - args?: any, - ) { - evt.preventDefault() - evt.stopPropagation() - - const e = this.normalizeEvent(evt) - const data = this.getEventData(e) - const local = this.graph.snapToGrid(e.clientX!, e.clientY!) - const origin = this.graph.snapToGrid(data.clientX, data.clientY) - const dx = local.x - origin.x - const dy = local.y - origin.y - - this.trigger(`action:${action}:${eventName}`, { - e, - dx, - dy, - x: local.x, - y: local.y, - offsetX: evt.clientX! - data.startX, - offsetY: evt.clientY! - data.startY, - ...args, - }) - - data.clientX = evt.clientX! - data.clientY = evt.clientY! - } - - protected onPieToggleMouseDown( - this: Handle & View, - evt: JQuery.MouseDownEvent, - ) { - evt.stopPropagation() - const name = this.$(evt.target) - .closest(`.${ClassNames.pieToggle}`) - .attr('data-name') - if (!this.isOpen(name)) { - if (this.isOpen()) { - this.toggleState() - } - } - this.toggleState(name) - } - - protected setPieIcons(this: Handle & View) { - if (this.handleOptions.type === 'pie') { - this.$handleContainer.find(`.${ClassNames.handle}`).each((_, elem) => { - const $elem = this.$(elem) - const action = $elem.attr('data-action')! - const className = ClassNames.pieSlice - const handle = this.getHandle(action) - - if (!handle || !handle.icon) { - const contect = window - .getComputedStyle(elem, ':before') - .getPropertyValue('content') - if (contect && contect !== 'none') { - const $icons = $elem.find(`.${className}-txt`) - if ($icons.length) { - Vector.create($icons[0]).text(contect.replace(/['"]/g, '')) - } - } - - const bgImg = $elem.css('background-image') - if (bgImg) { - const matches = bgImg.match(/url\(['"]?([^'"]+)['"]?\)/) - if (matches) { - const href = matches[1] - const $imgs = $elem.find(`.${className}-img`) - if ($imgs.length > 0) { - Vector.create($imgs[0]).attr('xlink:href', href) - } - } - } - } - }) - } - } - - getHandleIdx(name: string) { - return this.handles.findIndex((item) => item.name === name) - } - - hasHandle(name: string) { - return this.getHandleIdx(name) >= 0 - } - - getHandle(name: string) { - return this.handles.find((item) => item.name === name) - } - - renderHandle(this: Handle & View, handle: Handle.Metadata) { - const $handle = this.$('
') - .addClass(`${ClassNames.handle} ${ClassNames.handle}-${handle.name}`) - .attr('data-action', handle.name) - .prop('draggable', false) - - if (this.handleOptions.type === 'pie') { - const index = this.getHandleIdx(handle.name) - const pie = this.pie - const outerRadius = pie.outerRadius - const innerRadius = pie.innerRadius - const offset = (outerRadius + innerRadius) / 2 - const ratio = new Point(outerRadius, outerRadius) - const delta = Angle.toRad(pie.sliceAngle) - const curRad = index * delta + Angle.toRad(pie.startAngle) - const nextRad = curRad + delta - const pathData = Dom.createSlicePathData( - innerRadius, - outerRadius, - curRad, - nextRad, - ) - - const vSvg = Vector.create('svg').addClass(`${ClassNames.pieSlice}-svg`) - const vPath = Vector.create('path') - .addClass(ClassNames.pieSlice) - .attr('d', pathData) - .translate(outerRadius, outerRadius) - const pos = Point.fromPolar(offset, -curRad - delta / 2, ratio).toJSON() - const iconSize = pie.iconSize - const vImg = Vector.create('image') - .attr(pos) - .addClass(`${ClassNames.pieSlice}-img`) - pos.y = pos.y + iconSize - 2 - const vText = Vector.create('text', { 'font-size': iconSize }) - .attr(pos) - .addClass(`${ClassNames.pieSlice}-txt`) - - vImg.attr({ - width: iconSize, - height: iconSize, - }) - vImg.translate(-iconSize / 2, -iconSize / 2) - vText.translate(-iconSize / 2, -iconSize / 2) - vSvg.append([vPath, vImg, vText]) - $handle.append(vSvg.node) - } else { - $handle.addClass(`${ClassNames.handle}-pos-${handle.position}`) - if (handle.content) { - if (typeof handle.content === 'string') { - $handle.html(handle.content) - } else { - $handle.append(handle.content) - } - } - } - - this.updateHandleIcon($handle, handle.icon) - this.applyAttrs($handle, handle.attrs) - - return $handle - } - - addHandle(this: Handle & View, handle: Handle.Metadata) { - if (!this.hasHandle(handle.name)) { - this.handles.push(handle) - - const events = handle.events - if (events) { - Object.keys(events).forEach((action) => { - const callback = events[action] - const name = `action:${handle.name}:${action}` as any - if (typeof callback === 'string') { - this.on(name, (this as any)[callback], this) - } else { - this.on(name, callback) - } - }) - } - - if (this.$handleContainer) { - this.$handleContainer.append(this.renderHandle(handle)) - } - } - - return this - } - - addHandles(this: Handle & View, handles: Handle.Metadata[]) { - handles.forEach((handle) => this.addHandle(handle)) - return this - } - - removeHandles(this: Handle & View) { - while (this.handles.length) { - this.removeHandle(this.handles[0].name) - } - return this - } - - removeHandle(this: Handle & View, name: string) { - const index = this.getHandleIdx(name) - const handle = this.handles[index] - if (handle) { - if (handle.events) { - Object.keys(handle.events).forEach((event) => { - this.off(`action:${name}:${event}` as any) - }) - } - this.getHandleElem(name).remove() - this.handles.splice(index, 1) - } - return this - } - - changeHandle( - this: Handle & View, - name: string, - newHandle: Partial, - ) { - const handle = this.getHandle(name) - if (handle) { - this.removeHandle(name) - this.addHandle({ - ...handle, - ...newHandle, - }) - } - return this - } - - toggleHandle(this: Handle & View, name: string, selected?: boolean) { - const handle = this.getHandle(name) - if (handle) { - const $handle = this.getHandleElem(name) - const className = `${ClassNames.handle}-selected` - if (selected === undefined) { - selected = !$handle.hasClass(className) // eslint-disable-line - } - - $handle.toggleClass(className, selected) - const icon = selected ? handle.iconSelected : handle.icon - if (icon) { - this.updateHandleIcon($handle, icon) - } - } - return this - } - - selectHandle(this: Handle & View, name: string) { - return this.toggleHandle(name, true) - } - - deselectHandle(this: Handle & View, name: string) { - return this.toggleHandle(name, false) - } - - deselectAllHandles(this: Handle & View) { - this.handles.forEach((handle) => this.deselectHandle(handle.name)) - return this - } - - protected getHandleElem(name: string) { - return this.$handleContainer.find( - `.${ClassNames.handle}-${name}`, - ) - } - - protected updateHandleIcon( - this: Handle & View, - $handle: JQuery, - icon?: string | null, - ) { - if (this.handleOptions.type === 'pie') { - const $icons = $handle.find(`.${ClassNames.pieSliceImg}`) - this.$($icons[0]).attr('xlink:href', icon || '') - } else { - $handle.css('background-image', icon ? `url(${icon})` : '') - } - } - - protected isRendered() { - return this.$handleContainer != null - } - - protected isOpen(name?: string) { - if (this.isRendered()) { - return name - ? this.$pieToggles[name].hasClass(ClassNames.pieToggleOpened) - : this.$handleContainer.hasClass(`${ClassNames.pieOpended}`) - } - return false - } - - protected toggleState(this: Handle & View, name?: string) { - if (this.isRendered()) { - const $handleContainer = this.$handleContainer - - Object.keys(this.$pieToggles).forEach((key) => { - const $toggle = this.$pieToggles[key] - $toggle.removeClass(ClassNames.pieToggleOpened) - }) - - if (this.isOpen()) { - this.trigger('pie:close', { name }) - $handleContainer.removeClass(ClassNames.pieOpended) - } else { - this.trigger('pie:open', { name }) - if (name) { - const toggles = this.pie.toggles - const toggle = toggles && toggles.find((i) => i.name === name) - if (toggle) { - $handleContainer.attr({ - 'data-pie-toggle-name': toggle.name, - 'data-pie-toggle-position': toggle.position, - }) - } - this.$pieToggles[name].addClass(ClassNames.pieToggleOpened) - } - $handleContainer.addClass(ClassNames.pieOpended) - } - } - } - - protected applyAttrs( - elem: HTMLElement | JQuery, - attrs?: { [selector: string]: JQuery.PlainObject }, - ) { - if (attrs) { - const $elem = View.$(elem) - Object.keys(attrs).forEach((selector) => { - const $element = $elem.find(selector).addBack().filter(selector) - const { class: cls, ...attr } = attrs[selector] - if (cls) { - $element.addClass(cls) - } - $element.attr(attr) - }) - } - } -} - -export namespace Handle { - export type Type = 'surround' | 'pie' | 'toolbar' - export type OrthPosition = 'e' | 'w' | 's' | 'n' - export type Position = OrthPosition | 'se' | 'sw' | 'ne' | 'nw' - - export interface Metadata { - /** - * The name of the custom tool. This name will be also set as a - * CSS class to the handle DOM element making it easy to select - * it your CSS stylesheet. - */ - name: string - position: Position - /** - * The icon url used to render the tool. This icons is set as a - * background image on the tool handle DOM element. - */ - icon?: string | null - iconSelected?: string | null - content?: string | Element - events?: { [event: string]: string | ((args: EventArgs) => void) } - attrs?: { [selector: string]: JQuery.PlainObject } - } - - export interface Pie { - innerRadius: number - outerRadius: number - sliceAngle: number - startAngle: number - iconSize: number - toggles: { - name: string - position: OrthPosition - attrs?: { [selector: string]: JQuery.PlainObject } - }[] - } - - export interface Options { - type?: Type - pie?: Partial - handles?: Metadata[] | null - tinyThreshold?: number - smallThreshold?: number - } - - export const defaultPieOptions: Pie = { - innerRadius: 20, - outerRadius: 50, - sliceAngle: 45, - startAngle: 0, - iconSize: 14, - toggles: [ - { - name: 'default', - position: 'e', - }, - ], - } - - export interface EventArgs { - e: JQuery.TriggeredEvent - x: number - y: number - dx: number - dy: number - offsetX: number - offsetY: number - } - - export interface EventData { - action: string - clientX: number - clientY: number - startX: number - startY: number - } -} - -namespace ClassNames { - export const handle = View.prototype.prefixClassName('widget-handle') - export const wrap = `${handle}-wrap` - export const animate = `${handle}-animate` - export const pieOpended = `${handle}-pie-opened` - export const pieToggle = `${handle}-pie-toggle` - export const pieToggleOpened = `${handle}-pie-toggle-opened` - export const pieSlice = `${handle}-pie-slice` - export const pieSliceImg = `${handle}-pie-slice-img` -} diff --git a/packages/x6/src/addon/common/index.ts b/packages/x6/src/addon/common/index.ts deleted file mode 100644 index 8708acf2b82..00000000000 --- a/packages/x6/src/addon/common/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './handle' -export * from './widget' diff --git a/packages/x6/src/addon/common/widget.ts b/packages/x6/src/addon/common/widget.ts deleted file mode 100644 index c62e1ec2d7b..00000000000 --- a/packages/x6/src/addon/common/widget.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { KeyValue } from '../../types' -import { View, CellView } from '../../view' -import { Cell, Node, Edge, Model } from '../../model' -import { Graph } from '../../graph' - -export class Widget< - Options extends Widget.Options = Widget.Options, - EventArgs = any, -> extends View { - // #region static - - private static readonly instanceCache: WeakMap< - typeof Widget, - KeyValue> - > = new WeakMap() - - private static ensureCache() { - if (!this.instanceCache.has(this)) { - this.instanceCache.set(this, {}) - } - return this.instanceCache.get(this)! - } - - public static register(instance: Widget, graph?: Graph) { - if (graph == null) { - // eslint-disable-next-line - graph = instance.graph - } - const dic = this.ensureCache() - let cache = dic[graph.view.cid] - if (cache == null) { - cache = dic[graph.view.cid] = {} - } - cache[instance.cid] = instance - } - - public static unregister(instance: Widget, graph?: Graph) { - if (graph == null) { - // eslint-disable-next-line - graph = instance.graph - } - const dic = this.ensureCache() - if (dic[graph.view.cid]) { - delete dic[graph.view.cid][instance.cid] - } - } - - public static removeInstances(graph: Graph) { - const dic = this.ensureCache() - const cache = dic[graph.view.cid] - if (cache) { - Object.keys(cache).forEach((cid) => { - const instance = cache[cid] - if (instance) { - instance.remove() - } - }) - } - } - - public static getInstances(graph: Graph) { - const dic = this.ensureCache() - return dic[graph.view.cid] || {} - } - - // #endregion - - public options: Options - public readonly cell: Cell - public readonly view: CellView - public readonly model: Model - public readonly graph: Graph - - constructor(options: Options & (Types.ViewOptions | Types.CellOptions)) { - super() - - const { view, cell, node, edge, graph, ...localOptions } = options as any - if (view) { - this.view = view - this.cell = view.cell - this.graph = view.graph - this.model = this.graph.model - } else if ((cell || edge || node) && graph) { - this.cell = node || edge || cell - this.view = (graph as Graph).renderer.findViewByCell(this.cell)! - this.graph = graph - this.model = this.graph.model - } - - const ctor = this.constructor as typeof Widget - if (options.clearAll !== false) { - ctor.removeInstances(this.graph) - } - - ctor.register(this) - this.init(localOptions) - } - - protected init(options: Options) {} // eslint-disable-line - - protected render() { - return this - } - - protected startListening() { - if (this.options.clearOnBlankMouseDown !== false) { - this.graph.on('blank:mousedown', this.remove, this) - } - } - - protected stopListening() { - if (this.options.clearOnBlankMouseDown !== false) { - this.graph.off('blank:mousedown', this.remove, this) - } - } - - remove() { - this.stopListening() - const ctor = this.constructor as typeof Widget - ctor.unregister(this) - return super.remove() - } - - @View.dispose() - dispose() { - this.remove() - } -} - -export namespace Widget { - export interface Options { - /** - * If set to `true` (the default value), clear all the existing widget - * from the page when a new widget is created. This is the most common - * behavior as it is assumed that there is only one widget visible on - * the page at a time. However, some applications might need to have more - * than one widget visible. In this case, set `clearAll` to `false` (and - * make sure to call `remove()` once you don't need a widget anymore) - */ - clearAll?: boolean - clearOnBlankMouseDown?: boolean - } -} - -namespace Types { - export interface ViewOptions { - view: CellView - } - - export interface CellOptions { - cell?: Cell - node?: Node - edge?: Edge - graph: Graph - } -} diff --git a/packages/x6/src/addon/dnd/index.less b/packages/x6/src/addon/dnd/index.less deleted file mode 100644 index a6660d223e1..00000000000 --- a/packages/x6/src/addon/dnd/index.less +++ /dev/null @@ -1,27 +0,0 @@ -@import '../../style/index'; - -@dnd-prefix-cls: ~'@{x6-prefix}-widget-dnd'; - -.@{dnd-prefix-cls} { - position: absolute; - top: -10000px; - left: -10000px; - z-index: 999999; - display: none; - cursor: move; - opacity: 0.7; - pointer-events: 'cursor'; - - &.dragging { - display: inline-block; - } - - &.dragging * { - pointer-events: none !important; - } - - .@{x6-prefix}-graph { - background: transparent; - box-shadow: none; - } -} diff --git a/packages/x6/src/addon/dnd/index.ts b/packages/x6/src/addon/dnd/index.ts deleted file mode 100644 index f89152aa26c..00000000000 --- a/packages/x6/src/addon/dnd/index.ts +++ /dev/null @@ -1,495 +0,0 @@ -import { Util } from '../../global' -import { FunctionExt } from '../../util' -import { Rectangle, Point } from '../../geometry' -import { Cell } from '../../model/cell' -import { Node } from '../../model/node' -import { View } from '../../view/view' -import { NodeView } from '../../view/node' -import { Graph } from '../../graph/graph' -import { EventArgs } from '../../graph/events' -import { Scroller } from '../scroller' - -export class Dnd extends View { - public readonly options: Dnd.Options - public readonly draggingGraph: Graph - protected readonly $container: JQuery - protected sourceNode: Node | null - protected draggingNode: Node | null - protected draggingView: NodeView | null - protected draggingBBox: Rectangle - protected geometryBBox: Rectangle - protected candidateEmbedView: NodeView | null - protected delta: Point | null - protected padding: number | null - protected snapOffset: Point.PointLike | null - protected originOffset: null | { left: number; top: number } - - protected get targetScroller() { - const target = this.options.target - return Graph.isGraph(target) ? target.scroller.widget : target - } - - protected get targetGraph() { - const target = this.options.target - return Graph.isGraph(target) ? target : target.graph - } - - protected get targetModel() { - return this.targetGraph.model - } - - protected get snapline() { - return this.targetGraph.snapline.widget - } - - constructor(options: Partial & { target: Graph | Scroller }) { - super() - - this.options = { - ...Dnd.defaults, - ...options, - } as Dnd.Options - - this.container = document.createElement('div') - this.$container = this.$(this.container).addClass( - this.prefixClassName('widget-dnd'), - ) - - this.draggingGraph = new Graph({ - ...this.options.delegateGraphOptions, - container: document.createElement('div'), - width: 1, - height: 1, - }) - - this.$container.append(this.draggingGraph.container) - } - - start(node: Node, evt: JQuery.MouseDownEvent | MouseEvent) { - const e = evt as JQuery.MouseDownEvent - - e.preventDefault() - - this.targetModel.startBatch('dnd') - this.$container - .addClass('dragging') - .appendTo(this.options.containerParent || document.body) - - this.sourceNode = node - this.prepareDragging(node, e.clientX, e.clientY) - - const local = this.updateNodePosition(e.clientX, e.clientY) - - if (this.isSnaplineEnabled()) { - this.snapline.captureCursorOffset({ - e, - node, - cell: node, - view: this.draggingView!, - x: local.x, - y: local.y, - }) - this.draggingNode!.on('change:position', this.snap, this) - } - - this.delegateDocumentEvents(Dnd.documentEvents, e.data) - } - - protected isSnaplineEnabled() { - return this.snapline && !this.snapline.disabled - } - - protected prepareDragging( - sourceNode: Node, - clientX: number, - clientY: number, - ) { - const draggingGraph = this.draggingGraph - const draggingModel = draggingGraph.model - const draggingNode = this.options.getDragNode(sourceNode, { - sourceNode, - draggingGraph, - targetGraph: this.targetGraph, - }) - - draggingNode.position(0, 0) - - let padding = 5 - if (this.isSnaplineEnabled()) { - padding += this.snapline.options.tolerance || 0 - } - - if (this.isSnaplineEnabled() || this.options.scaled) { - const scale = this.targetGraph.transform.getScale() - draggingGraph.scale(scale.sx, scale.sy) - padding *= Math.max(scale.sx, scale.sy) - } else { - draggingGraph.scale(1, 1) - } - - this.clearDragging() - - if (this.options.animation) { - this.$container.stop(true, true) - } - - draggingModel.resetCells([draggingNode]) - - const delegateView = draggingGraph.findViewByCell(draggingNode) as NodeView - delegateView.undelegateEvents() - delegateView.cell.off('changed') - draggingGraph.fitToContent({ - padding, - allowNewOrigin: 'any', - }) - - const bbox = delegateView.getBBox() - this.geometryBBox = delegateView.getBBox({ useCellGeometry: true }) - this.delta = this.geometryBBox.getTopLeft().diff(bbox.getTopLeft()) - this.draggingNode = draggingNode - this.draggingView = delegateView - this.draggingBBox = draggingNode.getBBox() - this.padding = padding - this.originOffset = this.updateGraphPosition(clientX, clientY) - } - - protected updateGraphPosition(clientX: number, clientY: number) { - const scrollTop = - document.body.scrollTop || document.documentElement.scrollTop - const delta = this.delta! - const nodeBBox = this.geometryBBox - const padding = this.padding || 5 - const offset = { - left: clientX - delta.x - nodeBBox.width / 2 - padding, - top: clientY - delta.y - nodeBBox.height / 2 - padding + scrollTop, - } - - if (this.draggingGraph) { - this.$container.offset(offset) - } - - return offset - } - - protected updateNodePosition(x: number, y: number) { - const local = this.targetGraph.clientToLocal(x, y) - const bbox = this.draggingBBox! - local.x -= bbox.width / 2 - local.y -= bbox.height / 2 - this.draggingNode!.position(local.x, local.y) - return local - } - - protected snap({ - cell, - current, - options, - }: Cell.EventArgs['change:position']) { - const node = cell as Node - if (options.snapped) { - const bbox = this.draggingBBox - node.position(bbox.x + options.tx, bbox.y + options.ty, { silent: true }) - this.draggingView!.translate() - node.position(current!.x, current!.y, { silent: true }) - - this.snapOffset = { - x: options.tx, - y: options.ty, - } - } else { - this.snapOffset = null - } - } - - protected onDragging(evt: JQuery.MouseMoveEvent) { - const draggingView = this.draggingView - if (draggingView) { - evt.preventDefault() - const e = this.normalizeEvent(evt) - const clientX = e.clientX - const clientY = e.clientY - - this.updateGraphPosition(clientX, clientY) - const local = this.updateNodePosition(clientX, clientY) - const embeddingMode = this.targetGraph.options.embedding.enabled - const isValidArea = - (embeddingMode || this.isSnaplineEnabled()) && - this.isInsideValidArea({ - x: clientX, - y: clientY, - }) - - if (embeddingMode) { - draggingView.setEventData(e, { - graph: this.targetGraph, - candidateEmbedView: this.candidateEmbedView, - }) - const data = draggingView.getEventData(e) - if (isValidArea) { - draggingView.processEmbedding(e, data) - } else { - draggingView.clearEmbedding(data) - } - this.candidateEmbedView = data.candidateEmbedView - } - - // update snapline - if (this.isSnaplineEnabled()) { - if (isValidArea) { - this.snapline.snapOnMoving({ - e, - view: draggingView!, - x: local.x, - y: local.y, - } as EventArgs['node:mousemove']) - } else { - this.snapline.hide() - } - } - } - } - - protected onDragEnd(evt: JQuery.MouseUpEvent) { - const draggingNode = this.draggingNode - if (draggingNode) { - const e = this.normalizeEvent(evt) - const draggingView = this.draggingView - const draggingBBox = this.draggingBBox - const snapOffset = this.snapOffset - let x = draggingBBox.x - let y = draggingBBox.y - - if (snapOffset) { - x += snapOffset.x - y += snapOffset.y - } - - draggingNode.position(x, y, { silent: true }) - - const ret = this.drop(draggingNode, { x: e.clientX, y: e.clientY }) - const callback = (node: null | Node) => { - if (node) { - this.onDropped(draggingNode) - if (this.targetGraph.options.embedding.enabled && draggingView) { - draggingView.setEventData(e, { - cell: node, - graph: this.targetGraph, - candidateEmbedView: this.candidateEmbedView, - }) - draggingView.finalizeEmbedding(e, draggingView.getEventData(e)) - } - } else { - this.onDropInvalid() - } - - this.candidateEmbedView = null - this.targetModel.stopBatch('dnd') - } - - if (FunctionExt.isAsync(ret)) { - // stop dragging - this.undelegateDocumentEvents() - ret.then(callback) // eslint-disable-line - } else { - callback(ret) - } - } - } - - protected clearDragging() { - if (this.draggingNode) { - this.sourceNode = null - this.draggingNode.remove() - this.draggingNode = null - this.draggingView = null - this.delta = null - this.padding = null - this.snapOffset = null - this.originOffset = null - this.undelegateDocumentEvents() - } - } - - protected onDropped(draggingNode: Node) { - if (this.draggingNode === draggingNode) { - this.clearDragging() - this.$container.removeClass('dragging').remove() - } - } - - protected onDropInvalid() { - const draggingNode = this.draggingNode - if (draggingNode) { - const anim = this.options.animation - if (anim) { - const duration = (typeof anim === 'object' && anim.duration) || 150 - const easing = (typeof anim === 'object' && anim.easing) || 'swing' - - this.draggingView = null - - this.$container.animate(this.originOffset!, duration, easing, () => - this.onDropped(draggingNode), - ) - } else { - this.onDropped(draggingNode) - } - } - } - - protected isInsideValidArea(p: Point.PointLike) { - let targetRect: Rectangle - const targetGraph = this.targetGraph - const targetScroller = this.targetScroller - - if (targetScroller) { - if (targetScroller.options.autoResize) { - targetRect = this.getDropArea(targetScroller.container) - } else { - const outter = this.getDropArea(targetScroller.container) - targetRect = this.getDropArea(targetGraph.container).intersectsWithRect( - outter, - )! - } - } else { - targetRect = this.getDropArea(targetGraph.container) - } - - return targetRect && targetRect.containsPoint(p) - } - - protected getDropArea(elem: Element) { - const $elem = this.$(elem) - const offset = $elem.offset()! - const scrollTop = - document.body.scrollTop || document.documentElement.scrollTop - const scrollLeft = - document.body.scrollLeft || document.documentElement.scrollLeft - - return Rectangle.create({ - x: - offset.left + parseInt($elem.css('border-left-width'), 10) - scrollLeft, - y: offset.top + parseInt($elem.css('border-top-width'), 10) - scrollTop, - width: $elem.innerWidth()!, - height: $elem.innerHeight()!, - }) - } - - protected drop(draggingNode: Node, pos: Point.PointLike) { - if (this.isInsideValidArea(pos)) { - const targetGraph = this.targetGraph - const targetModel = targetGraph.model - const local = targetGraph.clientToLocal(pos) - const sourceNode = this.sourceNode! - const droppingNode = this.options.getDropNode(draggingNode, { - sourceNode, - draggingNode, - targetGraph: this.targetGraph, - draggingGraph: this.draggingGraph, - }) - const bbox = droppingNode.getBBox() - local.x += bbox.x - bbox.width / 2 - local.y += bbox.y - bbox.height / 2 - const gridSize = this.snapOffset ? 1 : targetGraph.getGridSize() - - droppingNode.position( - Util.snapToGrid(local.x, gridSize), - Util.snapToGrid(local.y, gridSize), - ) - - droppingNode.removeZIndex() - - const validateNode = this.options.validateNode - const ret = validateNode - ? validateNode(droppingNode, { - sourceNode, - draggingNode, - droppingNode, - targetGraph, - draggingGraph: this.draggingGraph, - }) - : true - - if (typeof ret === 'boolean') { - if (ret) { - targetModel.addCell(droppingNode, { stencil: this.cid }) - return droppingNode - } - return null - } - - return FunctionExt.toDeferredBoolean(ret).then((valid) => { - if (valid) { - targetModel.addCell(droppingNode, { stencil: this.cid }) - return droppingNode - } - return null - }) - } - - return null - } - - protected onRemove() { - if (this.draggingGraph) { - this.draggingGraph.view.remove() - this.draggingGraph.dispose() - } - } - - @View.dispose() - dispose() { - this.remove() - } -} - -export namespace Dnd { - export interface Options { - target: Graph | Scroller - /** - * Should scale the dragging node or not. - */ - scaled?: boolean - delegateGraphOptions?: Graph.Options - animation?: - | boolean - | { - duration?: number - easing?: string - } - containerParent?: HTMLElement - getDragNode: (sourceNode: Node, options: GetDragNodeOptions) => Node - getDropNode: (draggingNode: Node, options: GetDropNodeOptions) => Node - validateNode?: ( - droppingNode: Node, - options: ValidateNodeOptions, - ) => boolean | Promise - } - - export interface GetDragNodeOptions { - sourceNode: Node - targetGraph: Graph - draggingGraph: Graph - } - - export interface GetDropNodeOptions extends GetDragNodeOptions { - draggingNode: Node - } - - export interface ValidateNodeOptions extends GetDropNodeOptions { - droppingNode: Node - } - - export const defaults: Partial = { - animation: false, - getDragNode: (sourceNode) => sourceNode.clone(), - getDropNode: (draggingNode) => draggingNode.clone(), - } - - export const documentEvents = { - mousemove: 'onDragging', - touchmove: 'onDragging', - mouseup: 'onDragEnd', - touchend: 'onDragEnd', - touchcancel: 'onDragEnd', - } -} diff --git a/packages/x6/src/addon/halo/edge-preset.ts b/packages/x6/src/addon/halo/edge-preset.ts deleted file mode 100644 index 9021f50e1ec..00000000000 --- a/packages/x6/src/addon/halo/edge-preset.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Edge } from '../../model/edge' -import { EdgeView } from '../../view/edge' -import { Halo } from './index' - -export class EdgePreset { - constructor(private halo: Halo) {} - - get options() { - return this.halo.options - } - - get graph() { - return this.halo.graph - } - - get model() { - return this.halo.model - } - - get view() { - return this.halo.view - } - - get cell() { - return this.halo.cell - } - - get edge() { - return this.cell as Edge - } - - getPresets(): Halo.Options { - return { - className: 'type-edge', - handles: [ - { - name: 'remove', - position: 'nw', - icon: null, - events: { - mousedown: this.removeEdge.bind(this), - }, - }, - { - name: 'direction', - position: 'se', - icon: null, - events: { - mousedown: this.directionSwap.bind(this), - }, - }, - ], - content: false, - bbox(view: EdgeView) { - return view.graph.localToGraph(view.getPointAtRatio(0.5)!) - }, - tinyThreshold: -1, - smallThreshold: -1, - } - } - - removeEdge() { - this.cell.remove() - } - - directionSwap() { - const source = this.edge.getSource() - const target = this.edge.getTarget() - - this.edge.prop({ - source: target, - target: source, - }) - } -} diff --git a/packages/x6/src/addon/halo/index.less b/packages/x6/src/addon/halo/index.less deleted file mode 100644 index a22b769b6f1..00000000000 --- a/packages/x6/src/addon/halo/index.less +++ /dev/null @@ -1,78 +0,0 @@ -@import '../../style/index'; -@import '../common/handle.less'; - -@halo-prefix-cls: ~'@{x6-prefix}-widget-halo'; - -.@{halo-prefix-cls} { - position: absolute; - pointer-events: none; - - &-content { - position: absolute; - top: 100%; - padding: 6px; - font-size: 10px; - line-height: 14px; - text-align: center; - border-radius: 6px; - } - - &-handles + &-content { - right: -20px; - left: -20px; - margin-top: 30px; - } - - &-handles.@{handle-prefix-cls}-small + &-content { - margin-top: 25px; - } - - &-handles.@{handle-prefix-cls}-small + &-content { - margin-top: 20px; - } - - &-handles.@{handle-prefix-cls}-pie + &-content { - right: 0; - left: 0; - margin-top: 10px; - } -} - -// Default theme -.@{halo-prefix-cls} { - &-content { - color: #fff; - background-color: #6a6b8a; - } - - &.type-node { - .@{handle-prefix-cls}-toolbar { - .@{handle-prefix-cls}-remove { - position: absolute; - right: 100%; - bottom: 100%; - margin-right: 6px; - margin-bottom: 3px; - } - } - } - - &.type-edge { - .@{handle-prefix-cls}-surround { - .@{handle-prefix-cls}-remove { - background-color: #fff; - border-radius: 50%; - } - } - - .@{handle-prefix-cls}-toolbar { - margin-top: -60px; - margin-left: -18px; - - &::after { - top: -22px; - left: -9px; - } - } - } -} diff --git a/packages/x6/src/addon/halo/index.ts b/packages/x6/src/addon/halo/index.ts deleted file mode 100644 index 55eed26ece0..00000000000 --- a/packages/x6/src/addon/halo/index.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { ObjectExt, FunctionExt } from '../../util' -import { Rectangle } from '../../geometry' -import { Cell } from '../../model/cell' -import { Edge } from '../../model/edge' -import { CellView } from '../../view/cell' -import { Widget, Handle } from '../common' -import { NodePreset } from './node-preset' -import { EdgePreset } from './edge-preset' - -export class Halo extends Widget implements Handle { - protected $container: JQuery - protected $content: JQuery - - protected get type() { - return this.options.type || 'surround' - } - - protected get handleOptions() { - return this.options - } - - init(options: Halo.Options) { - this.options = ObjectExt.merge( - Halo.defaultOptions, - this.cell.isNode() - ? new NodePreset(this).getPresets() - : this.cell.isEdge() - ? new EdgePreset(this).getPresets() - : null, - options, - ) - - this.render() - this.initHandles() - this.update() - this.startListening() - } - - protected startListening() { - const model = this.model - const graph = this.graph - const cell = this.view.cell - - cell.on('removed', this.remove, this) - model.on('reseted', this.remove, this) - graph.on('halo:destroy', this.remove, this) - - model.on('*', this.update, this) - graph.on('scale', this.update, this) - graph.on('translate', this.update, this) - - super.startListening() - } - - protected stopListening() { - const model = this.model - const graph = this.graph - const cell = this.view.cell - - this.undelegateEvents() - - cell.off('removed', this.remove, this) - model.off('reseted', this.remove, this) - graph.off('halo:destroy', this.remove, this) - - model.off('*', this.update, this) - graph.off('scale', this.update, this) - graph.off('translate', this.update, this) - - super.stopListening() - } - - protected render() { - const options = this.options - const cls = this.prefixClassName('widget-halo') - this.view.addClass(Private.NODE_CLS) - this.container = document.createElement('div') - this.$container = this.$(this.container) - .addClass(cls) - .attr('data-shape', this.view.cell.shape) - - if (options.className) { - this.$container.addClass(options.className) - } - - this.$handleContainer = this.$('
') - .addClass(`${cls}-handles`) - .appendTo(this.container) - - this.$content = this.$('
') - .addClass(`${cls}-content`) - .appendTo(this.container) - - this.$container.appendTo(this.graph.container) - - return this - } - - remove() { - this.stopBatch() - this.view.removeClass(Private.NODE_CLS) - return super.remove() - } - - protected update() { - if (this.isRendered()) { - this.updateContent() - const bbox = this.getBBox() - const tinyThreshold = this.options.tinyThreshold || 0 - const smallThreshold = this.options.smallThreshold || 0 - - this.$handleContainer.toggleClass( - `${this.handleClassName}-tiny`, - bbox.width < tinyThreshold && bbox.height < tinyThreshold, - ) - - const className = `${this.handleClassName}-small` - this.$handleContainer.toggleClass( - className, - !this.$handleContainer.hasClass(className) && - bbox.width < smallThreshold && - bbox.height < smallThreshold, - ) - - this.$container.css({ - width: bbox.width, - height: bbox.height, - left: bbox.x, - top: bbox.y, - }) - - if (this.hasHandle('unlink')) { - this.toggleUnlink() - } - - if (this.type === 'surround' || this.type === 'toolbar') { - if (this.hasHandle('fork')) { - this.toggleFork() - } - } - } - } - - protected updateContent() { - const content = this.options.content - if (typeof content === 'function') { - const ret = FunctionExt.call(content, this, this.view, this.$content[0]) - if (ret) { - this.$content.html(ret) - } - } else if (content) { - this.$content.html(content) - } else { - this.$content.remove() - } - } - - protected getBBox() { - const view = this.view - const bbox = this.options.bbox - const rect = - typeof bbox === 'function' ? FunctionExt.call(bbox, this, view) : bbox - - return Rectangle.create({ - x: 0, - y: 0, - width: 1, - height: 1, - ...rect, - }) - } - - protected removeCell() { - this.cell.remove() - } - - protected toggleFork() { - const cell = this.view.cell.clone() - const view = this.graph.hook.createCellView(cell)! - const valid = this.graph.hook.validateConnection( - this.view, - null, - view, - null, - 'target', - ) - this.$handleContainer.children('.fork').toggleClass('hidden', !valid) - view.remove() - } - - protected toggleUnlink() { - const hasEdges = this.model.getConnectedEdges(this.view.cell).length > 0 - this.$handleContainer.children('.unlink').toggleClass('hidden', !hasEdges) - } - - // #region batch - - startBatch() { - this.model.startBatch('halo', { - halo: this.cid, - }) - } - - stopBatch() { - if (this.model.hasActiveBatch('halo')) { - this.model.stopBatch('halo', { - halo: this.cid, - }) - } - } - - // #endregion -} - -export namespace Halo { - export interface Options extends Handle.Options, Widget.Options { - className?: string - /** - * The preferred side for a self-loop edge created from Halo - */ - loopEdgePreferredSide?: 'top' | 'bottom' | 'left' | 'right' - loopEdgeWidth?: number - rotateGrid?: number - rotateEmbeds?: boolean - content?: - | false - | string - | ((cellView: CellView, boxElement: HTMLElement) => string) - - /** - * If set to true, the cell position and dimensions will be used as a - * basis for the Halo tools position. By default, this is set to `false` - * which causes the Halo tools position be based on the bounding box of - * the element view. Sometimes though, your shapes can have certain SVG - * sub elements that stick out of the view and you don't want these sub - * elements to affect the Halo tools position. In this case, set the - * `useCellGeometry` to true. - */ - useCellGeometry?: boolean - - /** - * This function will be called when cloning or forking actions take - * place and it should return a clone of the original cell. This is - * useful e.g. if you want the clone to be moved by an offset after - * the user clicks the clone handle. - */ - clone?: (cell: Cell, opt: any) => Cell - - /** - * A bounding box within which the Halo view will be rendered. - */ - bbox?: - | Partial - | ((this: Halo, view: CellView) => Partial) - - magnet?: (cellView: CellView, terminal: Edge.TerminalType) => Element - } - - export type OrthPosition = 'e' | 'w' | 's' | 'n' - export type Position = OrthPosition | 'se' | 'sw' | 'ne' | 'nw' - - export interface PieToggle { - name: string - position?: OrthPosition - attrs?: { [selector: string]: JQuery.PlainObject } - } - - export const defaultOptions: Options = { - type: 'surround', - clearAll: true, - clearOnBlankMouseDown: true, - useCellGeometry: false, - clone: (cell) => cell.clone().removeZIndex(), - } -} - -export interface Halo extends Handle {} - -Object.getOwnPropertyNames(Handle.prototype).forEach((name) => { - if (name !== 'constructor') { - Object.defineProperty( - Halo.prototype, - name, - Object.getOwnPropertyDescriptor(Handle.prototype, name)!, - ) - } -}) - -namespace Private { - export const NODE_CLS = 'has-widget-halo' -} diff --git a/packages/x6/src/addon/halo/node-preset.ts b/packages/x6/src/addon/halo/node-preset.ts deleted file mode 100644 index fa333030007..00000000000 --- a/packages/x6/src/addon/halo/node-preset.ts +++ /dev/null @@ -1,505 +0,0 @@ -import { Util } from '../../global' -import { StringExt, FunctionExt } from '../../util' -import { Point, Rectangle, Angle } from '../../geometry' -import { Cell } from '../../model/cell' -import { Node } from '../../model/node' -import { Edge } from '../../model/edge' -import { CellView } from '../../view/cell' -import { NodeView } from '../../view/node' -import { EdgeView } from '../../view/edge' -import { Handle } from '../common' -import { notify } from '../transform/util' -import { Halo } from './index' - -export class NodePreset { - private edgeView: EdgeView | null - private flip: number - - constructor(private halo: Halo) {} - - get options() { - return this.halo.options - } - - get graph() { - return this.halo.graph - } - - get model() { - return this.halo.model - } - - get view() { - return this.halo.view - } - - get cell() { - return this.halo.cell - } - - get node() { - return this.cell as Node - } - - getPresets(): Halo.Options { - return { - className: 'type-node', - handles: [ - { - name: 'remove', - position: 'nw', - events: { - mousedown: this.removeCell.bind(this), - }, - icon: null, - }, - { - name: 'resize', - position: 'se', - events: { - mousedown: this.startResize.bind(this), - mousemove: this.doResize.bind(this), - mouseup: this.stopResize.bind(this), - }, - icon: null, - }, - { - name: 'clone', - position: 'n', - events: { - mousedown: this.startClone.bind(this), - mousemove: this.doClone.bind(this), - mouseup: this.stopClone.bind(this), - }, - icon: null, - }, - { - name: 'link', - position: 'e', - events: { - mousedown: this.startLink.bind(this), - mousemove: this.doLink.bind(this), - mouseup: this.stopLink.bind(this), - }, - icon: null, - }, - { - name: 'fork', - position: 'ne', - events: { - mousedown: this.startFork.bind(this), - mousemove: this.doFork.bind(this), - mouseup: this.stopFork.bind(this), - }, - icon: null, - }, - { - name: 'unlink', - position: 'w', - events: { - mousedown: this.unlink.bind(this), - }, - icon: null, - }, - { - name: 'rotate', - position: 'sw', - events: { - mousedown: this.startRotate.bind(this), - mousemove: this.doRotate.bind(this), - mouseup: this.stopRotate.bind(this), - }, - icon: null, - }, - ], - - bbox(view) { - if (this.options.useCellGeometry) { - const node = view.cell as Node - return node.getBBox() - } - return view.getBBox() - }, - - content(view) { - const template = StringExt.template( - 'x: <%= x %>, y: <%= y %>, width: <%= width %>, height: <%= height %>, angle: <%= angle %>', - ) - const cell = view.cell as Node - const bbox = cell.getBBox() - return template({ - x: Math.floor(bbox.x), - y: Math.floor(bbox.y), - width: Math.floor(bbox.width), - height: Math.floor(bbox.height), - angle: Math.floor(cell.getAngle()), - }) - }, - magnet(view) { - return view.container - }, - tinyThreshold: 40, - smallThreshold: 80, - loopEdgePreferredSide: 'top', - loopEdgeWidth: 40, - rotateGrid: 15, - rotateEmbeds: false, - } - } - - removeCell() { - this.model.removeConnectedEdges(this.cell) - this.cell.remove() - } - - // #region create edge - - startLink({ x, y }: Handle.EventArgs) { - this.halo.startBatch() - const graph = this.graph - const edge = this.createEdgeConnectedToSource() - edge.setTarget({ x, y }) - this.model.addEdge(edge, { - validation: false, - halo: this.halo.cid, - async: false, - }) - - graph.view.undelegateEvents() - this.edgeView = graph.renderer.findViewByCell(edge) as EdgeView - this.edgeView.prepareArrowheadDragging('target', { - x, - y, - fallbackAction: 'remove', - }) - } - - createEdgeConnectedToSource() { - const magnet = this.getMagnet(this.view, 'source') - const terminal = this.getEdgeTerminal(this.view, magnet) - const edge = this.graph.hook.getDefaultEdge(this.view, magnet) - edge.setSource(terminal) - return edge - } - - getMagnet(view: CellView, terminal: Edge.TerminalType) { - const magnet = this.options.magnet - if (typeof magnet === 'function') { - const val = FunctionExt.call(magnet, this.halo, view, terminal) - if (val instanceof SVGElement) { - return val - } - } - throw new Error('`magnet()` has to return an SVGElement') - } - - getEdgeTerminal(view: CellView, magnet: Element) { - const terminal: Edge.TerminalCellData = { - cell: view.cell.id, - } - if (magnet !== view.container) { - const port = magnet.getAttribute('port') - if (port) { - terminal.port = port - } else { - terminal.selector = view.getSelector(magnet) - } - } - return terminal - } - - doLink({ e, x, y }: Handle.EventArgs) { - if (this.edgeView) { - this.edgeView.onMouseMove(e as JQuery.MouseMoveEvent, x, y) - } - } - - stopLink({ e, x, y }: Handle.EventArgs) { - const edgeView = this.edgeView - if (edgeView) { - edgeView.onMouseUp(e as JQuery.MouseUpEvent, x, y) - const edge = edgeView.cell - if (edge.hasLoop()) { - this.makeLoopEdge(edge) - } - this.halo.stopBatch() - this.halo.trigger('action:edge:addde', { edge }) - this.edgeView = null - } - this.graph.view.delegateEvents() - } - - makeLoopEdge(edge: Edge) { - let vertex1: Point | null = null - let vertex2: Point | null = null - const loopEdgeWidth = this.options.loopEdgeWidth! - const graphOptions = this.graph.options - const graphRect = new Rectangle( - 0, - 0, - graphOptions.width, - graphOptions.height, - ) - - const bbox = this.graph.graphToLocal(this.view.getBBox()) - const found = [ - this.options.loopEdgePreferredSide, - 'top', - 'bottom', - 'left', - 'right', - ].some((position) => { - let point: Point | null = null - let dx = 0 - let dy = 0 - switch (position) { - case 'top': - point = new Point(bbox.x + bbox.width / 2, bbox.y - loopEdgeWidth) - dx = loopEdgeWidth / 2 - break - case 'bottom': - point = new Point( - bbox.x + bbox.width / 2, - bbox.y + bbox.height + loopEdgeWidth, - ) - dx = loopEdgeWidth / 2 - break - case 'left': - point = new Point(bbox.x - loopEdgeWidth, bbox.y + bbox.height / 2) - dy = loopEdgeWidth / 2 - break - case 'right': - point = new Point( - bbox.x + bbox.width + loopEdgeWidth, - bbox.y + bbox.height / 2, - ) - dy = loopEdgeWidth / 2 - break - default: - break - } - - if (point) { - vertex1 = point.translate(-dx, -dy) - vertex2 = point.translate(dx, dy) - - return ( - graphRect.containsPoint(vertex1) && graphRect.containsPoint(vertex2) - ) - } - return false - }) - - if (found && vertex1 && vertex2) { - edge.setVertices([vertex1, vertex2]) - } - } - - // #endregion - - // #region resize - - startResize({ e }: Handle.EventArgs) { - this.halo.startBatch() - this.flip = [1, 0, 0, 1, 1, 0, 0, 1][ - Math.floor(Angle.normalize(this.node.getAngle()) / 45) - ] - this.view.addClass('node-resizing') - notify('node:resize', e as JQuery.MouseDownEvent, this.view as NodeView) - } - - doResize({ e, dx, dy }: Handle.EventArgs) { - const size = this.node.getSize() - const width = Math.max(size.width + (this.flip ? dx : dy), 1) - const height = Math.max(size.height + (this.flip ? dy : dx), 1) - this.node.resize(width, height, { - absolute: true, - }) - notify('node:resizing', e as JQuery.MouseMoveEvent, this.view as NodeView) - } - - stopResize({ e }: Handle.EventArgs) { - this.view.removeClass('node-resizing') - notify('node:resized', e as JQuery.MouseUpEvent, this.view as NodeView) - this.halo.stopBatch() - } - - // #endregion - - // #region clone - - startClone({ e, x, y }: Handle.EventArgs) { - this.halo.startBatch() - const options = this.options - const cloned = options.clone!(this.cell, { - clone: true, - }) - - if (!Cell.isCell(cloned)) { - throw new Error("option 'clone()' has to return a cell") - } - - this.centerNodeAtCursor(cloned, x, y) - this.model.addCell(cloned, { - halo: this.halo.cid, - async: false, - }) - const cloneView = this.graph.renderer.findViewByCell(cloned) as NodeView - cloneView.onMouseDown(e as JQuery.MouseDownEvent, x, y) - this.halo.setEventData(e, { cloneView }) - } - - centerNodeAtCursor(cell: Cell, x: number, y: number) { - const center = cell.getBBox().getCenter() - const dx = x - center.x - const dy = y - center.y - cell.translate(dx, dy) - } - - doClone({ e, x, y }: Handle.EventArgs) { - const view = this.halo.getEventData(e).cloneView as CellView - if (view) { - view.onMouseMove(e as JQuery.MouseMoveEvent, x, y) - } - } - - stopClone({ e, x, y }: Handle.EventArgs) { - const nodeView = this.halo.getEventData(e).cloneView as NodeView - if (nodeView) { - nodeView.onMouseUp(e as JQuery.MouseUpEvent, x, y) - } - this.halo.stopBatch() - } - - // #endregion - - // #region fork - - startFork({ e, x, y }: Handle.EventArgs) { - this.halo.startBatch() - - const cloned = this.options.clone!(this.cell, { - fork: true, - }) - - if (!Cell.isCell(cloned)) { - throw new Error("option 'clone()' has to return a cell") - } - - this.centerNodeAtCursor(cloned, x, y) - this.model.addCell(cloned, { - halo: this.halo.cid, - async: false, - }) - - const edge = this.createEdgeConnectedToSource() - const cloneView = this.graph.renderer.findViewByCell(cloned) as CellView - const magnet = this.getMagnet(cloneView, 'target') - const terminal = this.getEdgeTerminal(cloneView, magnet) - - edge.setTarget(terminal) - this.model.addEdge(edge, { - halo: this.halo.cid, - async: false, - }) - - cloneView.onMouseDown(e as JQuery.MouseDownEvent, x, y) - this.halo.setEventData(e, { cloneView }) - } - - doFork({ e, x, y }: Handle.EventArgs) { - const view = this.halo.getEventData(e).cloneView as CellView - if (view) { - view.onMouseMove(e as JQuery.MouseMoveEvent, x, y) - } - } - - stopFork({ e, x, y }: Handle.EventArgs) { - const view = this.halo.getEventData(e).cloneView as CellView - if (view) { - view.onMouseUp(e as JQuery.MouseUpEvent, x, y) - } - this.halo.stopBatch() - } - - // #endregion - - // #region rotate - - startRotate({ e, x, y }: Handle.EventArgs) { - this.halo.startBatch() - const center = this.node.getBBox().getCenter() - const nodes = [this.node] - if (this.options.rotateEmbeds) { - this.node - .getDescendants({ - deep: true, - }) - .reduce((memo, cell) => { - if (cell.isNode()) { - memo.push(cell) - } - return memo - }, nodes) - } - - this.halo.setEventData(e, { - center, - nodes, - rotateStartAngles: nodes.map((node) => node.getAngle()), - clientStartAngle: new Point(x, y).theta(center), - }) - - nodes.forEach((node) => { - const view = this.graph.findViewByCell(node) as NodeView - if (view) { - view.addClass('node-rotating') - notify('node:rotate', e as JQuery.MouseDownEvent, view) - } - }) - } - - doRotate({ e, x, y }: Handle.EventArgs) { - const data = this.halo.getEventData(e) - const delta = data.clientStartAngle - new Point(x, y).theta(data.center) - data.nodes.forEach((node: Node, index: number) => { - const startAngle = data.rotateStartAngles[index] - const targetAngle = Util.snapToGrid( - startAngle + delta, - this.options.rotateGrid!, - ) - node.rotate(targetAngle, { - absolute: true, - center: data.center, - halo: this.halo.cid, - }) - notify( - 'node:rotating', - e as JQuery.MouseMoveEvent, - this.graph.findViewByCell(node) as NodeView, - ) - }) - } - - stopRotate({ e }: Handle.EventArgs) { - const data = this.halo.getEventData(e) - data.nodes.forEach((node: Node) => { - const view = this.graph.findViewByCell(node) as NodeView - view.removeClass('node-rotating') - notify('node:rotated', e as JQuery.MouseUpEvent, view) - }) - this.halo.stopBatch() - } - - // #endregion - - // #region unlink - - unlink() { - this.halo.startBatch() - this.model.removeConnectedEdges(this.cell) - this.halo.stopBatch() - } - - // #endregion -} diff --git a/packages/x6/src/addon/index.ts b/packages/x6/src/addon/index.ts deleted file mode 100644 index 7df6c61c479..00000000000 --- a/packages/x6/src/addon/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './autosave' -export * from './clipboard' -export * from './halo' -export * from './minimap' -export * from './scroller' -export * from './selection' -export * from './snapline' -export * from './transform' -export * from './knob' -export * from './dnd' -export * from './stencil' diff --git a/packages/x6/src/addon/knob/index.less b/packages/x6/src/addon/knob/index.less deleted file mode 100644 index 9cd33657cb9..00000000000 --- a/packages/x6/src/addon/knob/index.less +++ /dev/null @@ -1,39 +0,0 @@ -@import '../../style/index'; - -@knob-prefix-cls: ~'@{x6-prefix}-widget-knob'; - -.@{knob-prefix-cls} { - position: absolute; - box-sizing: border-box; - width: 16px; - height: 16px; - margin-top: -8px; - margin-left: -8px; - cursor: pointer; - user-select: none; - - &::before, - &::after { - position: absolute; - transform: rotate(45deg); - content: ''; - } - - &::before { - top: 4px; - left: 4px; - box-sizing: border-box; - width: 8px; - height: 8px; - background-color: #fff; - } - - &::after { - top: 5px; - left: 5px; - box-sizing: border-box; - width: 6px; - height: 6px; - background-color: #fca000; - } -} diff --git a/packages/x6/src/addon/knob/index.ts b/packages/x6/src/addon/knob/index.ts deleted file mode 100644 index 54818a3b928..00000000000 --- a/packages/x6/src/addon/knob/index.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { Widget } from '../common' -import { KeyValue } from '../../types' -import { Cell } from '../../model/cell' -import { Node } from '../../model/node' -import { Edge } from '../../model/edge' -import { NodeView } from '../../view/node' -import { EdgeView } from '../../view/edge' -import { Graph } from '../../graph/graph' -import { Dom } from '../../util' -import { Angle, Point } from '../../geometry' - -export class Knob extends Widget { - public container: HTMLDivElement - - protected get node() { - return this.cell as Node - } - - protected get metadata() { - const meta = this.cell.prop('knob') - if (Array.isArray(meta)) { - if (this.options.index != null) { - return meta[this.options.index] - } - return null - } - return meta as Knob.Metadata - } - - protected init(options: Knob.Options) { - this.options = { ...options } - this.render() - this.startListening() - } - - protected startListening() { - this.delegateEvents({ - mousedown: 'onMouseDown', - touchstart: 'onMouseDown', - }) - - this.model.on('*', this.update, this) - this.graph.on('scale', this.update, this) - this.graph.on('translate', this.update, this) - - this.model.on('reseted', this.remove, this) - this.node.on('removed', this.remove, this) - - this.view.on('node:resize:mousedown', this.onTransform, this) - this.view.on('node:rotate:mousedown', this.onTransform, this) - this.view.on('node:resize:mouseup', this.onTransformed, this) - this.view.on('node:rotate:mouseup', this.onTransformed, this) - this.view.on('cell:knob:mousedown', this.onKnobMouseDown, this) - this.view.on('cell:knob:mouseup', this.onKnobMouseUp, this) - - super.startListening() - } - - protected stopListening() { - this.undelegateEvents() - - this.model.off('*', this.update, this) - this.graph.off('scale', this.update, this) - this.graph.off('translate', this.update, this) - - this.model.off('reseted', this.remove, this) - this.node.off('removed', this.remove, this) - - this.view.off('node:resize:mousedown', this.onTransform, this) - this.view.off('node:rotate:mousedown', this.onTransform, this) - this.view.off('node:resize:mouseup', this.onTransformed, this) - this.view.off('node:rotate:mouseup', this.onTransformed, this) - this.view.off('cell:knob:mousedown', this.onKnobMouseDown, this) - this.view.off('cell:knob:mouseup', this.onKnobMouseUp, this) - - super.stopListening() - } - - render() { - this.container = document.createElement('div') - Dom.addClass(this.container, this.prefixClassName('widget-knob')) - if (this.options.className) { - Dom.addClass(this.container, this.options.className) - } - - this.view.addClass(Private.KNOB) - this.graph.container.appendChild(this.container) - this.update() - - return this - } - - remove() { - this.view.removeClass(Private.KNOB) - return super.remove() - } - - protected update() { - if (this.metadata) { - const { update, position } = this.metadata - const args = { - knob: this, - cell: this.cell, - node: this.node, - } - - if (position) { - const pos = position.call(this.graph, { ...args }) - if (pos) { - const ctm = this.graph.matrix() - const bbox = this.node.getBBox() - const angle = Angle.normalize(this.node.getAngle()) - const local = Point.create(pos) - if (angle !== 0) { - local.rotate(-angle, { x: bbox.width / 2, y: bbox.height / 2 }) - } - local.translate(bbox).scale(ctm.a, ctm.d).translate(ctm.e, ctm.f) - this.container.style.left = `${local.x}px` - this.container.style.top = `${local.y}px` - } - } - - if (update) { - update.call(this.graph, { ...args }) - } - } - } - - protected hide() { - this.container.style.display = 'none' - } - - protected show() { - this.container.style.display = '' - } - - protected onTransform() { - this.hide() - } - - protected onTransformed() { - this.show() - } - - protected onKnobMouseDown({ knob }: { knob: Knob }) { - if (this.cid !== knob.cid) { - this.hide() - } - } - - protected onKnobMouseUp() { - this.show() - } - - protected notify(name: string, evt: JQuery.TriggeredEvent) { - if (this.view) { - const e = this.view.normalizeEvent(evt) as JQuery.MouseDownEvent - const localPoint = this.graph.snapToGrid(e.clientX, e.clientY) - this.view.notify(`cell:${name}`, { - e, - view: this.view, - node: this.node, - cell: this.cell, - x: localPoint.x, - y: localPoint.y, - knob: this, - }) - - if (this.cell.isNode()) { - this.view.notify(`node:${name}`, { - e, - view: this.view as NodeView, - node: this.node, - cell: this.cell, - x: localPoint.x, - y: localPoint.y, - knob: this, - }) - } else if (this.cell.isEdge()) { - this.view.notify(`edge:${name}`, { - e, - view: this.view as EdgeView, - edge: this.cell as Edge, - cell: this.cell, - x: localPoint.x, - y: localPoint.y, - knob: this, - }) - } - } - } - - protected onMouseDown(e: JQuery.MouseDownEvent) { - e.stopPropagation() - - this.setEventData(e, { - knobbing: false, - originX: e.clientX, - originY: e.clientY, - clientX: e.clientX, - clientY: e.clientY, - }) - - this.graph.view.undelegateEvents() - this.delegateDocumentEvents(Private.documentEvents, e.data) - if (this.metadata && this.metadata.onMouseDown) { - this.metadata.onMouseDown.call(this.graph, { - e, - data: this.getEventData(e), - knob: this, - cell: this.cell, - node: this.node, - }) - } - this.notify('knob:mousedown', e) - } - - protected onMouseMove(e: JQuery.MouseMoveEvent) { - const data = this.getEventData(e) - const view = this.graph.findViewByCell(this.node) as NodeView - - if (!data.knobbing) { - data.knobbing = true - if (view) { - view.addClass(Private.KNOBBING) - this.notify('knob', e) - } - this.model.startBatch('knob', { cid: this.cid }) - } - - data.clientX = e.clientX - data.clientY = e.clientY - - if (this.metadata && this.metadata.onMouseMove) { - const ctm = this.graph.matrix() - const dx = (e.clientX - data.originX) / ctm.a - const dy = (e.clientY - data.originY) / ctm.d - const angle = this.node.getAngle() - const delta = new Point(dx, dy).rotate(angle) - this.metadata.onMouseMove.call(this.graph, { - e, - data, - deltaX: delta.x, - deltaY: delta.y, - knob: this, - cell: this.cell, - node: this.node, - }) - } - - this.notify('knobbing', e) - this.notify('knob:mousemove', e) - } - - protected onMouseUp(e: JQuery.MouseUpEvent) { - this.undelegateDocumentEvents() - this.graph.view.delegateEvents() - const data = this.getEventData(e) - const view = this.graph.findViewByCell(this.node) as NodeView - if (data.knobbing) { - if (view) { - view.removeClass(Private.KNOBBING) - } - - if (this.metadata && this.metadata.onMouseUp) { - this.metadata.onMouseUp.call(this.graph, { - e, - data, - knob: this, - cell: this.cell, - node: this.node, - }) - } - - this.model.stopBatch('knob', { cid: this.cid }) - this.notify('knobbed', e) - } - this.notify('knob:mouseup', e) - } -} - -export namespace Knob { - export interface Options extends Widget.Options { - className?: string - index?: number - } - - interface UpdateArgs { - cell: Cell - node: Node - knob: Knob - } - - interface HandlerArgs extends UpdateArgs { - e: T - data: EventData.Knob - } - - export interface Metadata { - enabled?: boolean | ((this: Graph, node: Node) => boolean) - update?: (this: Graph, args: UpdateArgs) => void - position?: (this: Graph, args: UpdateArgs) => Point.PointLike - onMouseDown?: ( - this: Graph, - args: HandlerArgs, - ) => void - onMouseMove?: ( - this: Graph, - args: HandlerArgs & { - deltaX: number - deltaY: number - }, - ) => void - onMouseUp?: (this: Graph, args: HandlerArgs) => void - } -} - -namespace Private { - export const KNOB = 'has-widget-knob' - export const KNOBBING = 'node-knobbing' - - export const documentEvents = { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - } -} - -namespace EventData { - export interface Knob extends KeyValue { - knobbing: boolean - originX: number - originY: number - clientX: number - clientY: number - } -} diff --git a/packages/x6/src/addon/minimap/index.less b/packages/x6/src/addon/minimap/index.less deleted file mode 100644 index 45f47b496d1..00000000000 --- a/packages/x6/src/addon/minimap/index.less +++ /dev/null @@ -1,52 +0,0 @@ -@import '../../style/index'; - -@minimap-prefix-cls: ~'@{x6-prefix}-widget-minimap'; - -.@{minimap-prefix-cls} { - position: relative; - display: table-cell; - box-sizing: border-box; - overflow: hidden; - text-align: center; - vertical-align: middle; - background-color: #fff; - user-select: none; - - .@{x6-prefix}-graph { - display: inline-block; - box-shadow: 0 0 4px 0 #eee; - cursor: pointer; - - > svg { - pointer-events: none; - shape-rendering: optimizeSpeed; - } - - .@{x6-prefix}-node * { - /* stylelint-disable-next-line */ - vector-effect: initial; - } - } - - &-viewport { - position: absolute; - box-sizing: content-box !important; - margin: -2px 0 0 -2px; - border: 2px solid #31d0c6; - cursor: move; - } - - &-viewport-zoom { - position: absolute; - right: 0; - bottom: 0; - box-sizing: border-box; - width: 12px; - height: 12px; - margin: 0 -6px -6px 0; - background-color: #fff; - border: 2px solid #31d0c6; - border-radius: 50%; - cursor: nwse-resize; - } -} diff --git a/packages/x6/src/addon/minimap/index.ts b/packages/x6/src/addon/minimap/index.ts deleted file mode 100644 index 0afcb3d21b3..00000000000 --- a/packages/x6/src/addon/minimap/index.ts +++ /dev/null @@ -1,388 +0,0 @@ -import { FunctionExt } from '../../util' -import { View } from '../../view/view' -import { Graph } from '../../graph/graph' -import { EventArgs } from '../../graph/events' -import { Point } from '../../geometry' - -namespace ClassName { - export const root = 'widget-minimap' - export const viewport = `${root}-viewport` - export const zoom = `${viewport}-zoom` -} - -export class MiniMap extends View { - public readonly options: MiniMap.Options - public readonly container: HTMLDivElement - public readonly $container: JQuery - protected readonly zoomHandle: HTMLDivElement - protected readonly $viewport: JQuery - protected readonly sourceGraph: Graph - protected readonly targetGraph: Graph - protected geometry: Util.ViewGeometry - protected ratio: number - // Marks whether targetGraph is being transformed or scaled - // If yes we update updateViewport only - private targetGraphTransforming: boolean - - protected get graph() { - return this.options.graph - } - - protected get scroller() { - return this.graph.scroller.widget - } - - protected get graphContainer() { - if (this.scroller) { - return this.scroller.container - } - return this.graph.container - } - - protected get $graphContainer() { - if (this.scroller) { - return this.scroller.$container - } - return this.$(this.graph.container) - } - - constructor(options: Partial & { graph: Graph }) { - super() - - this.options = { - ...Util.defaultOptions, - ...options, - } as MiniMap.Options - - this.updateViewport = FunctionExt.debounce( - this.updateViewport.bind(this), - 0, - ) - - this.container = document.createElement('div') - this.$container = this.$(this.container).addClass( - this.prefixClassName(ClassName.root), - ) - - const graphContainer = document.createElement('div') - this.container.appendChild(graphContainer) - - this.$viewport = this.$('
').addClass( - this.prefixClassName(ClassName.viewport), - ) - - if (this.options.scalable) { - this.zoomHandle = this.$('
') - .addClass(this.prefixClassName(ClassName.zoom)) - .appendTo(this.$viewport) - .get(0) - } - - this.$container.append(this.$viewport).css({ - width: this.options.width, - height: this.options.height, - padding: this.options.padding, - }) - - if (this.options.container) { - this.options.container.appendChild(this.container) - } - - this.sourceGraph = this.graph - const targetGraphOptions: Graph.Options = { - ...this.options.graphOptions, - container: graphContainer, - model: this.sourceGraph.model, - frozen: true, - async: this.sourceGraph.isAsync(), - interacting: false, - grid: false, - background: false, - rotating: false, - resizing: false, - embedding: false, - selecting: false, - snapline: false, - clipboard: false, - history: false, - scroller: false, - } - - this.targetGraph = this.options.createGraph - ? this.options.createGraph(targetGraphOptions) - : new Graph(targetGraphOptions) - - this.targetGraph.renderer.unfreeze() - - this.updatePaper( - this.sourceGraph.options.width, - this.sourceGraph.options.height, - ) - - this.startListening() - } - - protected startListening() { - if (this.scroller) { - this.$graphContainer.on( - `scroll${this.getEventNamespace()}`, - this.updateViewport, - ) - } else { - this.sourceGraph.on('translate', this.onSourceGraphTransform, this) - this.sourceGraph.on('scale', this.onSourceGraphTransform, this) - } - this.sourceGraph.on('resize', this.updatePaper, this) - this.delegateEvents({ - mousedown: 'startAction', - touchstart: 'startAction', - [`mousedown .${this.prefixClassName('graph')}`]: 'scrollTo', - [`touchstart .${this.prefixClassName('graph')}`]: 'scrollTo', - }) - } - - protected stopListening() { - if (this.scroller) { - this.$graphContainer.off(this.getEventNamespace()) - } else { - this.sourceGraph.off('translate', this.onSourceGraphTransform, this) - this.sourceGraph.off('scale', this.onSourceGraphTransform, this) - } - this.sourceGraph.off('resize', this.updatePaper, this) - this.undelegateEvents() - } - - protected onRemove() { - this.targetGraph.view.remove() - this.stopListening() - this.targetGraph.dispose() - } - - protected onSourceGraphTransform() { - if (!this.targetGraphTransforming) { - this.updatePaper( - this.sourceGraph.options.width, - this.sourceGraph.options.height, - ) - } else { - this.updateViewport() - } - } - - protected updatePaper(width: number, height: number): this - protected updatePaper({ width, height }: EventArgs['resize']): this - protected updatePaper(w: number | EventArgs['resize'], h?: number) { - let width: number - let height: number - if (typeof w === 'object') { - width = w.width - height = w.height - } else { - width = w - height = h as number - } - - const origin = this.sourceGraph.options - const scale = this.sourceGraph.transform.getScale() - const maxWidth = this.options.width - 2 * this.options.padding - const maxHeight = this.options.height - 2 * this.options.padding - - width /= scale.sx // eslint-disable-line - height /= scale.sy // eslint-disable-line - - this.ratio = Math.min(maxWidth / width, maxHeight / height) - - const ratio = this.ratio - const x = (origin.x * ratio) / scale.sx - const y = (origin.y * ratio) / scale.sy - - width *= ratio // eslint-disable-line - height *= ratio // eslint-disable-line - this.targetGraph.resizeGraph(width, height) - this.targetGraph.translate(x, y) - this.targetGraph.scale(ratio, ratio) - this.updateViewport() - return this - } - - protected updateViewport() { - const ratio = this.ratio - const scale = this.sourceGraph.transform.getScale() - - let origin = null - if (this.scroller) { - origin = this.scroller.clientToLocalPoint(0, 0) - } else { - const ctm = this.sourceGraph.matrix() - origin = new Point(-ctm.e / ctm.a, -ctm.f / ctm.d) - } - - const position = this.$(this.targetGraph.container).position() - const translation = this.targetGraph.translate() - translation.ty = translation.ty || 0 - - this.geometry = { - top: position.top + origin.y * ratio + translation.ty, - left: position.left + origin.x * ratio + translation.tx, - width: (this.$graphContainer.innerWidth()! * ratio) / scale.sx, - height: (this.$graphContainer.innerHeight()! * ratio) / scale.sy, - } - this.$viewport.css(this.geometry) - } - - protected startAction(evt: JQuery.MouseDownEvent) { - const e = this.normalizeEvent(evt) - const action = e.target === this.zoomHandle ? 'zooming' : 'panning' - const { tx, ty } = this.sourceGraph.translate() - const eventData: Util.EventData = { - action, - clientX: e.clientX, - clientY: e.clientY, - scrollLeft: this.graphContainer.scrollLeft, - scrollTop: this.graphContainer.scrollTop, - zoom: this.sourceGraph.zoom(), - scale: this.sourceGraph.transform.getScale(), - geometry: this.geometry, - translateX: tx, - translateY: ty, - } - this.targetGraphTransforming = true - this.delegateDocumentEvents(Util.documentEvents, eventData) - } - - protected doAction(evt: JQuery.MouseMoveEvent) { - const e = this.normalizeEvent(evt) - const clientX = e.clientX - const clientY = e.clientY - const data = e.data as Util.EventData - switch (data.action) { - case 'panning': { - const scale = this.sourceGraph.transform.getScale() - const rx = (clientX - data.clientX) * scale.sx - const ry = (clientY - data.clientY) * scale.sy - if (this.scroller) { - this.graphContainer.scrollLeft = data.scrollLeft + rx / this.ratio - this.graphContainer.scrollTop = data.scrollTop + ry / this.ratio - } else { - this.sourceGraph.translate( - data.translateX - rx / this.ratio, - data.translateY - ry / this.ratio, - ) - } - break - } - - case 'zooming': { - const startScale = data.scale - const startGeometry = data.geometry - const delta = - 1 + (data.clientX - clientX) / startGeometry.width / startScale.sx - - if (data.frameId) { - cancelAnimationFrame(data.frameId) - } - - data.frameId = requestAnimationFrame(() => { - this.sourceGraph.zoom(delta * data.zoom, { - absolute: true, - minScale: this.options.minScale, - maxScale: this.options.maxScale, - }) - }) - break - } - - default: - break - } - } - - protected stopAction() { - this.undelegateDocumentEvents() - this.targetGraphTransforming = false - } - - protected scrollTo(evt: JQuery.MouseDownEvent) { - const e = this.normalizeEvent(evt) - - let x - let y - - const ts = this.targetGraph.translate() - ts.ty = ts.ty || 0 - - if (e.offsetX == null) { - const offset = this.$(this.targetGraph.container).offset()! - x = e.pageX - offset.left - y = e.pageY - offset.top - } else { - x = e.offsetX - y = e.offsetY - } - - const cx = (x - ts.tx) / this.ratio - const cy = (y - ts.ty) / this.ratio - this.sourceGraph.centerPoint(cx, cy) - } - - @View.dispose() - dispose() { - this.remove() - } -} - -export namespace MiniMap { - export interface Options { - graph: Graph - container?: HTMLElement - width: number - height: number - padding: number - scalable?: boolean - minScale?: number - maxScale?: number - createGraph?: (options: Graph.Options) => Graph - graphOptions?: Graph.Options - } -} - -namespace Util { - export const defaultOptions: Partial = { - width: 300, - height: 200, - padding: 10, - scalable: true, - minScale: 0.01, - maxScale: 16, - graphOptions: {}, - createGraph: (options) => new Graph(options), - } - - export const documentEvents = { - mousemove: 'doAction', - touchmove: 'doAction', - mouseup: 'stopAction', - touchend: 'stopAction', - } - - export interface ViewGeometry extends JQuery.PlainObject { - top: number - left: number - width: number - height: number - } - - export interface EventData { - frameId?: number - action: 'zooming' | 'panning' - clientX: number - clientY: number - scrollLeft: number - scrollTop: number - zoom: number - scale: { sx: number; sy: number } - geometry: ViewGeometry - translateX: number - translateY: number - } -} diff --git a/packages/x6/src/addon/scroller/index.less b/packages/x6/src/addon/scroller/index.less deleted file mode 100644 index de827cbc8df..00000000000 --- a/packages/x6/src/addon/scroller/index.less +++ /dev/null @@ -1,78 +0,0 @@ -@import '../../style/index'; - -@scroller-prefix-cls: ~'@{x6-prefix}-graph-scroller'; - -.@{scroller-prefix-cls} { - position: relative; - box-sizing: border-box; - overflow: scroll; - outline: none; - - &-content { - position: relative; - } - - &-background { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - } - - .@{x6-prefix}-graph { - position: absolute; - display: inline-block; - margin: 0; - box-shadow: none; - - > svg { - display: block; - } - } - - &&-paged { - .@{x6-prefix}-graph { - box-shadow: 0 0 4px 0 #eee; - } - } - - &&-pannable[data-panning='false'] { - cursor: grab; - cursor: -moz-grab; - cursor: -webkit-grab; - } - - &&-pannable[data-panning='true'] { - cursor: grabbing; - cursor: -moz-grabbing; - cursor: -webkit-grabbing; - user-select: none; - } -} - -.@{x6-prefix}-graph-pagebreak { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - - &-vertical { - position: absolute; - top: 0; - bottom: 0; - box-sizing: border-box; - width: 1px; - border-left: 1px dashed #bdbdbd; - } - - &-horizontal { - position: absolute; - right: 0; - left: 0; - box-sizing: border-box; - height: 1px; - border-top: 1px dashed #bdbdbd; - } -} diff --git a/packages/x6/src/addon/scroller/index.ts b/packages/x6/src/addon/scroller/index.ts deleted file mode 100644 index 42d2d7aac5a..00000000000 --- a/packages/x6/src/addon/scroller/index.ts +++ /dev/null @@ -1,1329 +0,0 @@ -import { Platform, NumberExt, ObjectExt, Dom, FunctionExt } from '../../util' -import { Point, Rectangle } from '../../geometry' -import { Model } from '../../model/model' -import { Cell } from '../../model/cell' -import { View } from '../../view/view' -import { Graph } from '../../graph' -import { Renderer } from '../../graph/renderer' -import { GraphView } from '../../graph/view' -import { EventArgs } from '../../graph/events' -import { TransformManager } from '../../graph/transform' -import { BackgroundManager } from '../../graph/background' - -export class Scroller extends View { - public readonly options: Scroller.Options - public readonly container: HTMLDivElement - public readonly content: HTMLDivElement - public readonly background: HTMLDivElement - public readonly $container: JQuery - public readonly backgroundManager: Scroller.Background - protected readonly $background: JQuery - protected readonly $content: JQuery - protected pageBreak: HTMLDivElement | null - - public get graph() { - return this.options.graph - } - - public get model() { - return this.graph.model - } - - protected sx: number - protected sy: number - protected clientX: number - protected clientY: number - protected padding = { left: 0, top: 0, right: 0, bottom: 0 } - protected cachedScrollLeft: number | null - protected cachedScrollTop: number | null - protected cachedCenterPoint: Point.PointLike | null - protected cachedClientSize: { width: number; height: number } | null - protected delegatedHandlers: { [name: string]: (...args: any) => any } - - constructor(options: Scroller.Options) { - super() - - this.options = Util.getOptions(options) - - const scale = this.graph.transform.getScale() - this.sx = scale.sx - this.sy = scale.sy - - const width = this.options.width || this.graph.options.width - const height = this.options.height || this.graph.options.height - this.container = document.createElement('div') - this.$container = this.$(this.container) - .addClass(this.prefixClassName(Util.containerClass)) - .css({ width, height }) - - if (this.options.pageVisible) { - this.$container.addClass(this.prefixClassName(Util.pagedClass)) - } - - if (this.options.className) { - this.$container.addClass(this.options.className) - } - - const graphContainer = this.graph.container - - if (graphContainer.parentNode) { - this.$container.insertBefore(graphContainer) - } - - // copy style - const style = graphContainer.getAttribute('style') - if (style) { - const obj: { [name: string]: string } = {} - const styles = style.split(';') - styles.forEach((item) => { - const section = item.trim() - if (section) { - const pair = section.split(':') - if (pair.length) { - obj[pair[0].trim()] = pair[1] ? pair[1].trim() : '' - } - } - }) - - Object.keys(obj).forEach((key: any) => { - if (key === 'width' || key === 'height') { - return - } - - graphContainer.style[key] = '' - this.container.style[key] = obj[key] - }) - } - - this.content = document.createElement('div') - this.$content = this.$(this.content) - .addClass(this.prefixClassName(Util.contentClass)) - .css({ - width: this.graph.options.width, - height: this.graph.options.height, - }) - - // custom background - this.background = document.createElement('div') - this.$background = this.$(this.background).addClass( - this.prefixClassName(Util.backgroundClass), - ) - this.$content.append(this.background) - - if (!this.options.pageVisible) { - this.$content.append(this.graph.view.grid) - } - this.$content.append(graphContainer) - this.$content.appendTo(this.container) - - this.startListening() - - if (!this.options.pageVisible) { - this.graph.grid.update() - } - - this.backgroundManager = new Scroller.Background(this) - - if (!this.options.autoResize) { - this.update() - } - } - - protected startListening() { - const graph = this.graph - const model = this.model - - graph.on('scale', this.onScale, this) - graph.on('resize', this.onResize, this) - graph.on('before:print', this.storeScrollPosition, this) - graph.on('before:export', this.storeScrollPosition, this) - graph.on('after:print', this.restoreScrollPosition, this) - graph.on('after:export', this.restoreScrollPosition, this) - - graph.on('render:done', this.onRenderDone, this) - graph.on('unfreeze', this.onUpdate, this) - model.on('reseted', this.onUpdate, this) - model.on('cell:added', this.onUpdate, this) - model.on('cell:removed', this.onUpdate, this) - model.on('cell:changed', this.onUpdate, this) - model.on('batch:stop', this.onBatchStop, this) - - this.delegateBackgroundEvents() - } - - protected stopListening() { - const graph = this.graph - const model = this.model - - graph.off('scale', this.onScale, this) - graph.off('resize', this.onResize, this) - graph.off('beforeprint', this.storeScrollPosition, this) - graph.off('beforeexport', this.storeScrollPosition, this) - graph.off('afterprint', this.restoreScrollPosition, this) - graph.off('afterexport', this.restoreScrollPosition, this) - - graph.off('render:done', this.onRenderDone, this) - graph.off('unfreeze', this.onUpdate, this) - model.off('reseted', this.onUpdate, this) - model.off('cell:added', this.onUpdate, this) - model.off('cell:removed', this.onUpdate, this) - model.off('cell:changed', this.onUpdate, this) - model.off('batch:stop', this.onBatchStop, this) - - this.undelegateBackgroundEvents() - } - - public enableAutoResize() { - this.options.autoResize = true - } - - public disableAutoResize() { - this.options.autoResize = false - } - - protected onUpdate() { - if (this.graph.isAsync() || !this.options.autoResize) { - return - } - - this.update() - } - - onBatchStop(args: { name: Model.BatchName }) { - if (this.graph.isAsync() || !this.options.autoResize) { - return - } - - if (Renderer.UPDATE_DELAYING_BATCHES.includes(args.name)) { - this.update() - } - } - - protected delegateBackgroundEvents(events?: View.Events) { - const evts = events || GraphView.events - this.delegatedHandlers = Object.keys(evts).reduce<{ - [name: string]: (...args: any) => any - }>((memo, name) => { - const handler = evts[name] - if (name.indexOf(' ') === -1) { - if (typeof handler === 'function') { - memo[name] = handler as (...args: any) => any - } else { - let method = this.graph.view[handler as keyof GraphView] - if (typeof method === 'function') { - method = method.bind(this.graph.view) - memo[name] = method as (...args: any) => any - } - } - } - return memo - }, {}) - - this.onBackgroundEvent = this.onBackgroundEvent.bind(this) - Object.keys(this.delegatedHandlers).forEach((name) => { - this.delegateEvent( - name, - { - guarded: false, - }, - this.onBackgroundEvent, - ) - }) - } - - protected undelegateBackgroundEvents() { - Object.keys(this.delegatedHandlers).forEach((name) => { - this.undelegateEvent(name, this.onBackgroundEvent) - }) - } - - protected onBackgroundEvent(e: JQuery.TriggeredEvent) { - let valid = false - const target = e.target - - if (!this.options.pageVisible) { - const view = this.graph.view - valid = view.background === target || view.grid === target - } else if (this.options.background) { - valid = this.background === target - } else { - valid = this.content === target - } - - if (valid) { - const handler = this.delegatedHandlers[e.type] - if (typeof handler === 'function') { - handler.apply(this.graph, arguments) // eslint-disable-line - } - } - } - - protected onRenderDone({ stats }: EventArgs['render:done']) { - if (this.options.autoResize && stats.priority < 2) { - this.update() - } - } - - protected onResize() { - if (this.cachedCenterPoint) { - this.centerPoint(this.cachedCenterPoint.x, this.cachedCenterPoint.y) - this.updatePageBreak() - } - } - - protected onScale({ sx, sy, ox, oy }: EventArgs['scale']) { - this.updateScale(sx, sy) - - if (ox || oy) { - this.centerPoint(ox, oy) - this.updatePageBreak() - } - - const autoResizeOptions = - this.options.autoResizeOptions || this.options.fitTocontentOptions - - if (typeof autoResizeOptions === 'function') { - this.update() - } - } - - protected storeScrollPosition() { - this.cachedScrollLeft = this.container.scrollLeft - this.cachedScrollTop = this.container.scrollTop - } - - protected restoreScrollPosition() { - this.container.scrollLeft = this.cachedScrollLeft! - this.container.scrollTop = this.cachedScrollTop! - this.cachedScrollLeft = null - this.cachedScrollTop = null - } - - protected storeClientSize() { - this.cachedClientSize = { - width: this.container.clientWidth, - height: this.container.clientHeight, - } - } - - protected restoreClientSize() { - this.cachedClientSize = null - } - - protected beforeManipulation() { - if (Platform.IS_IE || Platform.IS_EDGE) { - this.$container.css('visibility', 'hidden') - } - } - - protected afterManipulation() { - if (Platform.IS_IE || Platform.IS_EDGE) { - this.$container.css('visibility', 'visible') - } - } - - public updatePageSize(width?: number, height?: number) { - if (width != null) { - this.options.pageWidth = width - } - - if (height != null) { - this.options.pageHeight = height - } - - this.updatePageBreak() - } - - protected updatePageBreak() { - if (this.pageBreak && this.pageBreak.parentNode) { - this.pageBreak.parentNode.removeChild(this.pageBreak) - } - - this.pageBreak = null - - if (this.options.pageVisible && this.options.pageBreak) { - const graphWidth = this.graph.options.width - const graphHeight = this.graph.options.height - const pageWidth = this.options.pageWidth! * this.sx - const pageHeight = this.options.pageHeight! * this.sy - if (graphWidth > pageWidth || graphHeight > pageHeight) { - let hasPageBreak = false - const container = document.createElement('div') - - for (let i = 1, l = Math.floor(graphWidth / pageWidth); i < l; i += 1) { - this.$('
') - .addClass(this.prefixClassName(`graph-pagebreak-vertical`)) - .css({ left: i * pageWidth }) - .appendTo(container) - hasPageBreak = true - } - - for ( - let i = 1, l = Math.floor(graphHeight / pageHeight); - i < l; - i += 1 - ) { - this.$('
') - .addClass(this.prefixClassName(`graph-pagebreak-horizontal`)) - .css({ top: i * pageHeight }) - .appendTo(container) - hasPageBreak = true - } - - if (hasPageBreak) { - Dom.addClass(container, this.prefixClassName('graph-pagebreak')) - this.$(this.graph.view.grid).after(container) - this.pageBreak = container - } - } - } - } - - update() { - const size = this.getClientSize() - this.cachedCenterPoint = this.clientToLocalPoint( - size.width / 2, - size.height / 2, - ) - - let resizeOptions = - this.options.autoResizeOptions || this.options.fitTocontentOptions - if (typeof resizeOptions === 'function') { - resizeOptions = FunctionExt.call(resizeOptions, this, this) - } - - const options: TransformManager.FitToContentFullOptions = { - gridWidth: this.options.pageWidth, - gridHeight: this.options.pageHeight, - allowNewOrigin: 'negative', - ...resizeOptions, - } - - this.graph.fitToContent(this.getFitToContentOptions(options)) - } - - protected getFitToContentOptions( - options: TransformManager.FitToContentFullOptions, - ) { - const sx = this.sx - const sy = this.sy - - options.gridWidth && (options.gridWidth *= sx) - options.gridHeight && (options.gridHeight *= sy) - options.minWidth && (options.minWidth *= sx) - options.minHeight && (options.minHeight *= sy) - - if (typeof options.padding === 'object') { - options.padding = { - left: (options.padding.left || 0) * sx, - right: (options.padding.right || 0) * sx, - top: (options.padding.top || 0) * sy, - bottom: (options.padding.bottom || 0) * sy, - } - } else if (typeof options.padding === 'number') { - options.padding *= sx - } - - if (!this.options.autoResize) { - options.contentArea = Rectangle.create() - } - - return options - } - - protected updateScale(sx: number, sy: number) { - const options = this.graph.options - - const dx = sx / this.sx - const dy = sy / this.sy - - this.sx = sx - this.sy = sy - - this.graph.translate(options.x * dx, options.y * dy) - this.graph.resizeGraph(options.width * dx, options.height * dy) - } - - scrollbarPosition(): { left: number; top: number } - scrollbarPosition( - left?: number, - top?: number, - options?: Scroller.ScrollOptions, - ): this - scrollbarPosition( - left?: number, - top?: number, - options?: Scroller.ScrollOptions, - ) { - if (left == null && top == null) { - return { - left: this.container.scrollLeft, - top: this.container.scrollTop, - } - } - - const prop: { [key: string]: number } = {} - if (typeof left === 'number') { - prop.scrollLeft = left - } - - if (typeof top === 'number') { - prop.scrollTop = top - } - - if (options && options.animation) { - this.$container.animate(prop, options.animation) - } else { - this.$container.prop(prop) - } - - return this - } - - /** - * Try to scroll to ensure that the position (x,y) on the graph (in local - * coordinates) is at the center of the viewport. If only one of the - * coordinates is specified, only scroll in the specified dimension and - * keep the other coordinate unchanged. - */ - scrollToPoint( - x: number | null | undefined, - y: number | null | undefined, - options?: Scroller.ScrollOptions, - ) { - const size = this.getClientSize() - const ctm = this.graph.matrix() - const prop: { [key: string]: number } = {} - - if (typeof x === 'number') { - prop.scrollLeft = x - size.width / 2 + ctm.e + (this.padding.left || 0) - } - - if (typeof y === 'number') { - prop.scrollTop = y - size.height / 2 + ctm.f + (this.padding.top || 0) - } - - if (options && options.animation) { - this.$container.animate(prop, options.animation) - } else { - this.$container.prop(prop) - } - - return this - } - - /** - * Try to scroll to ensure that the center of graph content is at the - * center of the viewport. - */ - scrollToContent(options?: Scroller.ScrollOptions) { - const sx = this.sx - const sy = this.sy - const center = this.graph.getContentArea().getCenter() - return this.scrollToPoint(center.x * sx, center.y * sy, options) - } - - /** - * Try to scroll to ensure that the center of cell is at the center of - * the viewport. - */ - scrollToCell(cell: Cell, options?: Scroller.ScrollOptions) { - const sx = this.sx - const sy = this.sy - const center = cell.getBBox().getCenter() - return this.scrollToPoint(center.x * sx, center.y * sy, options) - } - - /** - * The center methods are more aggressive than the scroll methods. These - * methods position the graph so that a specific point on the graph lies - * at the center of the viewport, adding paddings around the paper if - * necessary (e.g. if the requested point lies in a corner of the paper). - * This means that the requested point will always move into the center - * of the viewport. (Use the scroll functions to avoid adding paddings - * and only scroll the viewport as far as the graph boundary.) - */ - - /** - * Position the center of graph to the center of the viewport. - */ - center(optons?: Scroller.CenterOptions) { - return this.centerPoint(optons) - } - - /** - * Position the point (x,y) on the graph (in local coordinates) to the - * center of the viewport. If only one of the coordinates is specified, - * only center along the specified dimension and keep the other coordinate - * unchanged. - */ - centerPoint( - x: number, - y: null | number, - options?: Scroller.CenterOptions, - ): this - centerPoint( - x: null | number, - y: number, - options?: Scroller.CenterOptions, - ): this - centerPoint(optons?: Scroller.CenterOptions): this - centerPoint( - x?: number | null | Scroller.CenterOptions, - y?: number | null, - options?: Scroller.CenterOptions, - ) { - const ctm = this.graph.matrix() - const sx = ctm.a - const sy = ctm.d - const tx = -ctm.e - const ty = -ctm.f - const tWidth = tx + this.graph.options.width - const tHeight = ty + this.graph.options.height - - let localOptions: Scroller.CenterOptions | null | undefined - - this.storeClientSize() // avoid multilple reflow - - if (typeof x === 'number' || typeof y === 'number') { - localOptions = options - const visibleCenter = this.getVisibleArea().getCenter() - if (typeof x === 'number') { - x *= sx // eslint-disable-line - } else { - x = visibleCenter.x // eslint-disable-line - } - - if (typeof y === 'number') { - y *= sy // eslint-disable-line - } else { - y = visibleCenter.y // eslint-disable-line - } - } else { - localOptions = x - x = (tx + tWidth) / 2 // eslint-disable-line - y = (ty + tHeight) / 2 // eslint-disable-line - } - - if (localOptions && localOptions.padding) { - return this.positionPoint({ x, y }, '50%', '50%', localOptions) - } - - const padding = this.getPadding() - const clientSize = this.getClientSize() - const cx = clientSize.width / 2 - const cy = clientSize.height / 2 - const left = cx - padding.left - x + tx - const right = cx - padding.right + x - tWidth - const top = cy - padding.top - y + ty - const bottom = cy - padding.bottom + y - tHeight - - this.addPadding( - Math.max(left, 0), - Math.max(right, 0), - Math.max(top, 0), - Math.max(bottom, 0), - ) - - const result = this.scrollToPoint(x, y, localOptions || undefined) - - this.restoreClientSize() - - return result - } - - centerContent(options?: Scroller.PositionContentOptions) { - return this.positionContent('center', options) - } - - centerCell(cell: Cell, options?: Scroller.CenterOptions) { - return this.positionCell(cell, 'center', options) - } - - /** - * The position methods are a more general version of the center methods. - * They position the graph so that a specific point on the graph lies at - * requested coordinates inside the viewport. - */ - - /** - * - */ - positionContent( - pos: Scroller.Direction, - options?: Scroller.PositionContentOptions, - ) { - const rect = this.graph.getContentArea(options) - return this.positionRect(rect, pos, options) - } - - positionCell( - cell: Cell, - pos: Scroller.Direction, - options?: Scroller.CenterOptions, - ) { - const bbox = cell.getBBox() - return this.positionRect(bbox, pos, options) - } - - positionRect( - rect: Rectangle.RectangleLike, - pos: Scroller.Direction, - options?: Scroller.CenterOptions, - ) { - const bbox = Rectangle.create(rect) - switch (pos) { - case 'center': - return this.positionPoint(bbox.getCenter(), '50%', '50%', options) - case 'top': - return this.positionPoint(bbox.getTopCenter(), '50%', 0, options) - case 'top-right': - return this.positionPoint(bbox.getTopRight(), '100%', 0, options) - case 'right': - return this.positionPoint(bbox.getRightMiddle(), '100%', '50%', options) - case 'bottom-right': - return this.positionPoint( - bbox.getBottomRight(), - '100%', - '100%', - options, - ) - case 'bottom': - return this.positionPoint( - bbox.getBottomCenter(), - '50%', - '100%', - options, - ) - case 'bottom-left': - return this.positionPoint(bbox.getBottomLeft(), 0, '100%', options) - case 'left': - return this.positionPoint(bbox.getLeftMiddle(), 0, '50%', options) - case 'top-left': - return this.positionPoint(bbox.getTopLeft(), 0, 0, options) - default: - return this - } - } - - positionPoint( - point: Point.PointLike, - x: number | string, - y: number | string, - options: Scroller.CenterOptions = {}, - ) { - const { padding: pad, ...localOptions } = options - const padding = NumberExt.normalizeSides(pad) - const clientRect = Rectangle.fromSize(this.getClientSize()) - const targetRect = clientRect.clone().moveAndExpand({ - x: padding.left, - y: padding.top, - width: -padding.right - padding.left, - height: -padding.top - padding.bottom, - }) - - // eslint-disable-next-line - x = NumberExt.normalizePercentage(x, Math.max(0, targetRect.width)) - if (x < 0) { - x = targetRect.width + x // eslint-disable-line - } - - // eslint-disable-next-line - y = NumberExt.normalizePercentage(y, Math.max(0, targetRect.height)) - if (y < 0) { - y = targetRect.height + y // eslint-disable-line - } - - const origin = targetRect.getTopLeft().translate(x, y) - const diff = clientRect.getCenter().diff(origin) - const scale = this.zoom() - const rawDiff = diff.scale(1 / scale, 1 / scale) - const result = Point.create(point).translate(rawDiff) - return this.centerPoint(result.x, result.y, localOptions) - } - - zoom(): number - zoom(factor: number, options?: TransformManager.ZoomOptions): this - zoom(factor?: number, options?: TransformManager.ZoomOptions) { - if (factor == null) { - return this.sx - } - - options = options || {} // eslint-disable-line - - let cx - let cy - const clientSize = this.getClientSize() - const center = this.clientToLocalPoint( - clientSize.width / 2, - clientSize.height / 2, - ) - - let sx = factor - let sy = factor - - if (!options.absolute) { - sx += this.sx - sy += this.sy - } - - if (options.scaleGrid) { - sx = Math.round(sx / options.scaleGrid) * options.scaleGrid - sy = Math.round(sy / options.scaleGrid) * options.scaleGrid - } - - if (options.maxScale) { - sx = Math.min(options.maxScale, sx) - sy = Math.min(options.maxScale, sy) - } - - if (options.minScale) { - sx = Math.max(options.minScale, sx) - sy = Math.max(options.minScale, sy) - } - - sx = this.graph.transform.clampScale(sx) - sy = this.graph.transform.clampScale(sy) - - if (options.center) { - const fx = sx / this.sx - const fy = sy / this.sy - cx = options.center.x - (options.center.x - center.x) / fx - cy = options.center.y - (options.center.y - center.y) / fy - } else { - cx = center.x - cy = center.y - } - - this.beforeManipulation() - this.graph.transform.scale(sx, sy) - this.centerPoint(cx, cy) - this.afterManipulation() - - return this - } - - zoomToRect( - rect: Rectangle.RectangleLike, - options: TransformManager.ScaleContentToFitOptions = {}, - ) { - const area = Rectangle.create(rect) - const graph = this.graph - - options.contentArea = area - if (options.viewportArea == null) { - options.viewportArea = { - x: graph.options.x, - y: graph.options.y, - width: this.$container.width()!, - height: this.$container.height()!, - } - } - - this.beforeManipulation() - graph.transform.scaleContentToFitImpl(options, false) - const center = area.getCenter() - this.centerPoint(center.x, center.y) - this.afterManipulation() - - return this - } - - zoomToFit( - options: TransformManager.GetContentAreaOptions & - TransformManager.ScaleContentToFitOptions = {}, - ) { - return this.zoomToRect(this.graph.getContentArea(options), options) - } - - transitionToPoint( - p: Point.PointLike, - options?: Scroller.TransitionOptions, - ): this - transitionToPoint( - x: number, - y: number, - options?: Scroller.TransitionOptions, - ): this - transitionToPoint( - x: number | Point.PointLike, - y?: number | Scroller.TransitionOptions, - options?: Scroller.TransitionOptions, - ) { - if (typeof x === 'object') { - options = y as Scroller.TransitionOptions // eslint-disable-line - y = x.y // eslint-disable-line - x = x.x // eslint-disable-line - } else { - y = y as number // eslint-disable-line - } - - if (options == null) { - options = {} // eslint-disable-line - } - - let transform - let transformOrigin - const scale = this.sx - const targetScale = Math.max(options.scale || scale, 0.000001) - const clientSize = this.getClientSize() - const targetPoint = new Point(x, y) - const localPoint = this.clientToLocalPoint( - clientSize.width / 2, - clientSize.height / 2, - ) - - if (scale === targetScale) { - const translate = localPoint.diff(targetPoint).scale(scale, scale).round() - transform = `translate(${translate.x}px,${translate.y}px)` - } else { - const delta = - (targetScale / (scale - targetScale)) * targetPoint.distance(localPoint) - const range = localPoint.clone().move(targetPoint, delta) - const origin = this.localToBackgroundPoint(range).round() - transform = `scale(${targetScale / scale})` - transformOrigin = `${origin.x}px ${origin.y}px` - } - - const onTransitionEnd = options.onTransitionEnd - this.$container.addClass(Util.transitionClassName) - this.$content - .off(Util.transitionEventName) - .on(Util.transitionEventName, (e) => { - this.syncTransition(targetScale, { x: x as number, y: y as number }) - if (typeof onTransitionEnd === 'function') { - FunctionExt.call( - onTransitionEnd, - this, - e.originalEvent as TransitionEvent, - ) - } - }) - .css({ - transform, - transformOrigin, - transition: 'transform', - transitionDuration: options.duration || '1s', - transitionDelay: options.delay, - transitionTimingFunction: options.timing, - } as JQuery.PlainObject) - - return this - } - - protected syncTransition(scale: number, p: Point.PointLike) { - this.beforeManipulation() - this.graph.scale(scale) - this.removeTransition() - this.centerPoint(p.x, p.y) - this.afterManipulation() - return this - } - - protected removeTransition() { - this.$container.removeClass(Util.transitionClassName) - this.$content.off(Util.transitionEventName).css({ - transform: '', - transformOrigin: '', - transition: '', - transitionDuration: '', - transitionDelay: '', - transitionTimingFunction: '', - }) - return this - } - - transitionToRect( - rectangle: Rectangle.RectangleLike, - options: Scroller.TransitionToRectOptions = {}, - ) { - const rect = Rectangle.create(rectangle) - const maxScale = options.maxScale || Infinity - const minScale = options.minScale || Number.MIN_VALUE - const scaleGrid = options.scaleGrid || null - const PIXEL_SIZE = options.visibility || 1 - const center = options.center - ? Point.create(options.center) - : rect.getCenter() - const clientSize = this.getClientSize() - const w = clientSize.width * PIXEL_SIZE - const h = clientSize.height * PIXEL_SIZE - let scale = new Rectangle( - center.x - w / 2, - center.y - h / 2, - w, - h, - ).getMaxUniformScaleToFit(rect, center) - - scale = Math.min(scale, maxScale) - if (scaleGrid) { - scale = Math.floor(scale / scaleGrid) * scaleGrid - } - scale = Math.max(minScale, scale) - - return this.transitionToPoint(center, { - scale, - ...options, - }) - } - - startPanning(evt: JQuery.MouseDownEvent) { - const e = this.normalizeEvent(evt) - this.clientX = e.clientX - this.clientY = e.clientY - this.trigger('pan:start', { e }) - this.$(document.body).on({ - 'mousemove.panning touchmove.panning': this.pan.bind(this), - 'mouseup.panning touchend.panning': this.stopPanning.bind(this), - 'mouseleave.panning': this.stopPanning.bind(this), - }) - this.$(window).on('mouseup.panning', this.stopPanning.bind(this)) - } - - pan(evt: JQuery.MouseMoveEvent) { - const e = this.normalizeEvent(evt) - const dx = e.clientX - this.clientX - const dy = e.clientY - this.clientY - this.container.scrollTop -= dy - this.container.scrollLeft -= dx - this.clientX = e.clientX - this.clientY = e.clientY - this.trigger('panning', { e }) - } - - stopPanning(e: JQuery.MouseUpEvent) { - this.$(document.body).off('.panning') - this.$(window).off('.panning') - this.trigger('pan:stop', { e }) - } - - clientToLocalPoint(p: Point.PointLike): Point - clientToLocalPoint(x: number, y: number): Point - clientToLocalPoint(a: number | Point.PointLike, b?: number) { - let x = typeof a === 'object' ? a.x : a - let y = typeof a === 'object' ? a.y : (b as number) - - const ctm = this.graph.matrix() - - x += this.container.scrollLeft - this.padding.left - ctm.e - y += this.container.scrollTop - this.padding.top - ctm.f - - return new Point(x / ctm.a, y / ctm.d) - } - - localToBackgroundPoint(p: Point.PointLike): Point - localToBackgroundPoint(x: number, y: number): Point - localToBackgroundPoint(x: number | Point.PointLike, y?: number) { - const p = typeof x === 'object' ? Point.create(x) : new Point(x, y) - const ctm = this.graph.matrix() - const padding = this.padding - return Dom.transformPoint(p, ctm).translate(padding.left, padding.top) - } - - resize(width?: number, height?: number) { - let w = width != null ? width : this.container.offsetWidth - let h = height != null ? height : this.container.offsetHeight - - if (typeof w === 'number') { - w = Math.round(w) - } - if (typeof h === 'number') { - h = Math.round(h) - } - - this.options.width = w - this.options.height = h - this.$container.css({ width: w, height: h }) - this.update() - } - - getClientSize() { - if (this.cachedClientSize) { - return this.cachedClientSize - } - return { - width: this.container.clientWidth, - height: this.container.clientHeight, - } - } - - autoScroll(clientX: number, clientY: number) { - const buffer = 10 - const container = this.container - const rect = container.getBoundingClientRect() - - let dx = 0 - let dy = 0 - if (clientX <= rect.left + buffer) { - dx = -buffer - } - - if (clientY <= rect.top + buffer) { - dy = -buffer - } - - if (clientX >= rect.right - buffer) { - dx = buffer - } - - if (clientY >= rect.bottom - buffer) { - dy = buffer - } - - if (dx !== 0) { - container.scrollLeft += dx - } - - if (dy !== 0) { - container.scrollTop += dy - } - - return { - scrollerX: dx, - scrollerY: dy, - } - } - - protected addPadding( - left?: number, - right?: number, - top?: number, - bottom?: number, - ) { - let padding = this.getPadding() - this.padding = { - left: Math.round(padding.left + (left || 0)), - top: Math.round(padding.top + (top || 0)), - bottom: Math.round(padding.bottom + (bottom || 0)), - right: Math.round(padding.right + (right || 0)), - } - - padding = this.padding - - this.$content.css({ - width: padding.left + this.graph.options.width + padding.right, - height: padding.top + this.graph.options.height + padding.bottom, - }) - - const container = this.graph.container - container.style.left = `${this.padding.left}px` - container.style.top = `${this.padding.top}px` - - return this - } - - protected getPadding() { - const padding = this.options.padding - if (typeof padding === 'function') { - return NumberExt.normalizeSides(FunctionExt.call(padding, this, this)) - } - - return NumberExt.normalizeSides(padding) - } - - /** - * Returns the untransformed size and origin of the current viewport. - */ - getVisibleArea() { - const ctm = this.graph.matrix() - const size = this.getClientSize() - const box = { - x: this.container.scrollLeft || 0, - y: this.container.scrollTop || 0, - width: size.width, - height: size.height, - } - const area = Dom.transformRectangle(box, ctm.inverse()) - area.x -= (this.padding.left || 0) / this.sx - area.y -= (this.padding.top || 0) / this.sy - return area - } - - isCellVisible(cell: Cell, options: { strict?: boolean } = {}) { - const bbox = cell.getBBox() - const area = this.getVisibleArea() - return options.strict - ? area.containsRect(bbox) - : area.isIntersectWithRect(bbox) - } - - isPointVisible(point: Point.PointLike) { - return this.getVisibleArea().containsPoint(point) - } - - /** - * Lock the current viewport by disabling user scrolling. - */ - lock() { - this.$container.css('overflow', 'hidden') - return this - } - - /** - * Enable user scrolling if previously locked. - */ - unlock() { - this.$container.css('overflow', 'scroll') - return this - } - - protected onRemove() { - this.stopListening() - } - - @View.dispose() - dispose() { - this.$(this.graph.container).insertBefore(this.$container) - this.remove() - } -} - -export namespace Scroller { - export interface CommonOptions { - className?: string - width?: number - height?: number - pageWidth?: number - pageHeight?: number - pageVisible?: boolean - pageBreak?: boolean - minVisibleWidth?: number - minVisibleHeight?: number - background?: false | BackgroundManager.Options - autoResize?: boolean - padding?: - | NumberExt.SideOptions - | ((this: Scroller, scroller: Scroller) => NumberExt.SideOptions) - /** - * **Deprecation Notice:** Scroller option `fitTocontentOptions` is deprecated and will be - * moved in next major release. Use `autoResizeOptions` instead. - * - * @deprecated - */ - fitTocontentOptions?: - | TransformManager.FitToContentFullOptions - | (( - this: Scroller, - scroller: Scroller, - ) => TransformManager.FitToContentFullOptions) - autoResizeOptions?: - | TransformManager.FitToContentFullOptions - | (( - this: Scroller, - scroller: Scroller, - ) => TransformManager.FitToContentFullOptions) - } - - export interface Options extends CommonOptions { - graph: Graph - } - - export interface ScrollOptions { - animation?: JQuery.EffectsOptions - } - - export interface CenterOptions extends ScrollOptions { - padding?: NumberExt.SideOptions - } - - export type PositionContentOptions = TransformManager.GetContentAreaOptions & - Scroller.CenterOptions - - export type Direction = - | 'center' - | 'top' - | 'top-right' - | 'top-left' - | 'right' - | 'bottom-right' - | 'bottom' - | 'bottom-left' - | 'left' - - export interface TransitionOptions { - /** - * The zoom level to reach at the end of the transition. - */ - scale?: number - duration?: string - delay?: string - timing?: string - onTransitionEnd?: (this: Scroller, e: TransitionEvent) => void - } - - export interface TransitionToRectOptions extends TransitionOptions { - minScale?: number - maxScale?: number - scaleGrid?: number - visibility?: number - center?: Point.PointLike - } -} - -export namespace Scroller { - export class Background extends BackgroundManager { - protected readonly scroller: Scroller - - protected get elem() { - return this.scroller.background - } - - constructor(scroller: Scroller) { - super(scroller.graph) - - this.scroller = scroller - if (scroller.options.background) { - this.draw(scroller.options.background) - } - } - - protected init() { - this.graph.on('scale', this.update, this) - this.graph.on('translate', this.update, this) - } - - protected updateBackgroundOptions(options?: BackgroundManager.Options) { - this.scroller.options.background = options - } - } -} - -namespace Util { - export const containerClass = 'graph-scroller' - export const panningClass = `${containerClass}-panning` - export const pannableClass = `${containerClass}-pannable` - export const pagedClass = `${containerClass}-paged` - export const contentClass = `${containerClass}-content` - export const backgroundClass = `${containerClass}-background` - export const transitionClassName = 'transition-in-progress' - export const transitionEventName = 'transitionend.graph-scroller-transition' - - export const defaultOptions: Partial = { - padding() { - const size = this.getClientSize() - const minWidth = Math.max(this.options.minVisibleWidth || 0, 1) || 1 - const minHeight = Math.max(this.options.minVisibleHeight || 0, 1) || 1 - const left = Math.max(size.width - minWidth, 0) - const top = Math.max(size.height - minHeight, 0) - return { left, top, right: left, bottom: top } - }, - minVisibleWidth: 50, - minVisibleHeight: 50, - pageVisible: false, - pageBreak: false, - autoResize: true, - } - - export function getOptions(options: Scroller.Options) { - const result = ObjectExt.merge({}, defaultOptions, options) - - if (result.pageWidth == null) { - result.pageWidth = options.graph.options.width - } - if (result.pageHeight == null) { - result.pageHeight = options.graph.options.height - } - - return result as Scroller.Options - } -} diff --git a/packages/x6/src/addon/selection/index.less b/packages/x6/src/addon/selection/index.less deleted file mode 100644 index aa90a7b38dd..00000000000 --- a/packages/x6/src/addon/selection/index.less +++ /dev/null @@ -1,81 +0,0 @@ -@import '../../style/index'; -@import '../common/handle.less'; - -@selection-prefix-cls: ~'@{x6-prefix}-widget-selection'; - -.@{selection-prefix-cls} { - position: absolute; - display: none; - width: 0; - height: 0; - touch-action: none; - - &-rubberband { - display: block; - overflow: visible; - opacity: 0.3; - } - - &-selected { - display: block; - } - - &-box { - cursor: move; - } - - &-inner[data-selection-length='0'], - &-inner[data-selection-length='1'] { - display: none; - } - - &-content { - position: absolute; - top: 100%; - right: -20px; - left: -20px; - margin-top: 30px; - padding: 6px; - line-height: 14px; - text-align: center; - border-radius: 6px; - - &:empty { - display: none; - } - } -} - -// theme -.@{selection-prefix-cls} { - &-rubberband { - background-color: #3498db; - border: 2px solid #2980b9; - } - - &-box { - box-sizing: content-box !important; - margin-top: -4px; - margin-left: -4px; - padding-right: 4px; - padding-bottom: 4px; - border: 2px dashed #feb663; - box-shadow: 2px 2px 5px #d3d3d3; - } - - &-inner { - box-sizing: content-box !important; - margin-top: -8px; - margin-left: -8px; - padding-right: 12px; - padding-bottom: 12px; - border: 2px solid #feb663; - box-shadow: 2px 2px 5px #d3d3d3; - } - - &-content { - color: #fff; - font-size: 10px; - background-color: #6a6b8a; - } -} diff --git a/packages/x6/src/addon/selection/index.ts b/packages/x6/src/addon/selection/index.ts deleted file mode 100644 index c8cbd08df9f..00000000000 --- a/packages/x6/src/addon/selection/index.ts +++ /dev/null @@ -1,1237 +0,0 @@ -import { Util } from '../../global' -import { KeyValue } from '../../types' -import { Rectangle, Angle, Point } from '../../geometry' -import { ObjectExt, StringExt, FunctionExt } from '../../util' -import { Cell } from '../../model/cell' -import { Node } from '../../model/node' -import { Edge } from '../../model/edge' -import { Model } from '../../model/model' -import { Collection } from '../../model/collection' -import { View } from '../../view/view' -import { CellView } from '../../view/cell' -import { NodeView } from '../../view/node' -import { Graph } from '../../graph/graph' -import { Renderer } from '../../graph/renderer' -import { notify } from '../transform/util' -import { Handle } from '../common' - -export class Selection extends View { - public readonly options: Selection.Options - protected readonly collection: Collection - protected $container: JQuery - protected $selectionContainer: JQuery - protected $selectionContent: JQuery - protected boxCount: number - protected boxesUpdated: boolean - - public get graph() { - return this.options.graph - } - - protected get boxClassName() { - return this.prefixClassName(Private.classNames.box) - } - - protected get $boxes() { - return this.$container.children(`.${this.boxClassName}`) - } - - protected get handleOptions() { - return this.options - } - - constructor(options: Selection.Options) { - super() - this.options = ObjectExt.merge({}, Private.defaultOptions, 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 Collection([], { - comparator: Private.depthComparator, - }) - this.options.collection = this.collection - } - - this.boxCount = 0 - - this.createContainer() - this.initHandles() - this.startListening() - } - - protected 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) - } - - protected 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) - } - - protected onRemove() { - this.stopListening() - } - - protected onGraphTransformed() { - this.updateSelectionBoxes({ async: false }) - } - - protected onCellChanged() { - this.updateSelectionBoxes() - } - - protected translating: boolean - - protected onNodePositionChanged({ - node, - options, - }: Collection.EventArgs['node:change:position']) { - const { showNodeSelectionBox, pointerEvents } = this.options - const { ui, selection } = options - let allowTranslating = !this.translating - - /* Scenarios where this method is not called: - * 1. ShowNodeSelection is true or ponterEvents is none - * 2. Avoid circular calls with the selection tag - */ - allowTranslating = - allowTranslating && - (showNodeSelectionBox !== true || pointerEvents === 'none') - allowTranslating = allowTranslating && ui && !selection - - if (allowTranslating) { - 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 - } - } - - protected onModelUpdated({ removed }: Collection.EventArgs['updated']) { - if (removed && removed.length) { - this.unselect(removed) - } - } - - isEmpty() { - return this.length <= 0 - } - - isSelected(cell: Cell | string) { - return this.collection.has(cell) - } - - get length() { - return this.collection.length - } - - get cells() { - return this.collection.toArray() - } - - select(cells: Cell | Cell[], options: Selection.AddOptions = {}) { - options.dryrun = true - const items = this.filter(Array.isArray(cells) ? cells : [cells]) - this.collection.add(items, options) - return this - } - - unselect(cells: Cell | Cell[], options: Selection.RemoveOptions = {}) { - // dryrun to prevent cell be removed from graph - options.dryrun = true - this.collection.remove(Array.isArray(cells) ? cells : [cells], options) - return this - } - - reset(cells?: Cell | Cell[], options: Selection.SetOptions = {}) { - if (cells) { - if (options.batch) { - const filterCells = this.filter(Array.isArray(cells) ? cells : [cells]) - this.collection.reset(filterCells, { ...options, ui: true }) - return this - } - - const prev = this.cells - const next = this.filter(Array.isArray(cells) ? cells : [cells]) - const prevMap: KeyValue = {} - const nextMap: KeyValue = {} - prev.forEach((cell) => (prevMap[cell.id] = cell)) - next.forEach((cell) => (nextMap[cell.id] = cell)) - const added: Cell[] = [] - const removed: Cell[] = [] - 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, { ...options, ui: true }) - } - - if (added.length) { - this.select(added, { ...options, ui: true }) - } - - if (removed.length === 0 && added.length === 0) { - this.updateContainer() - } - - return this - } - - return this.clean(options) - } - - clean(options: Selection.SetOptions = {}) { - if (this.length) { - if (options.batch === false) { - this.unselect(this.cells, options) - } else { - this.collection.reset([], { ...options, ui: true }) - } - } - return this - } - - setFilter(filter?: Selection.Filter) { - this.options.filter = filter - } - - setContent(content?: Selection.Content) { - this.options.content = content - } - - startSelecting(evt: JQuery.MouseDownEvent) { - // 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 = this.$(graphContainer).offset()! - const scrollLeft = graphContainer.scrollLeft - const scrollTop = graphContainer.scrollTop - x = evt.clientX - offset.left + window.pageXOffset + scrollLeft - y = evt.clientY - offset.top + window.pageYOffset + scrollTop - } - - this.$container.css({ - 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, - }) - - this.delegateDocumentEvents(Private.documentEvents, evt.data) - } - - filter(cells: Cell[]) { - const filter = this.options.filter - if (Array.isArray(filter)) { - return cells.filter( - (cell) => !filter.includes(cell) && !filter.includes(cell.shape), - ) - } - - if (typeof filter === 'function') { - return cells.filter((cell) => FunctionExt.call(filter, this.graph, cell)) - } - - return cells - } - - protected stopSelecting(evt: JQuery.MouseUpEvent) { - const graph = this.graph - const eventData = this.getEventData(evt) - const action = eventData.action - switch (action) { - case 'selecting': { - let width = this.$container.width()! - let height = this.$container.height()! - const offset = this.$container.offset()! - const origin = graph.pageToLocal(offset.left, offset.top) - const scale = graph.transform.getScale() - width /= scale.sx - height /= scale.sy - const rect = new 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 as EventData.Translating - 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 - } - } - } - - protected onMouseUp(evt: JQuery.MouseUpEvent) { - const action = this.getEventData(evt).action - if (action) { - this.stopSelecting(evt) - this.undelegateDocumentEvents() - } - } - - protected onSelectionBoxMouseDown(evt: JQuery.MouseDownEvent) { - 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) - } - - protected startTranslating(evt: JQuery.MouseDownEvent) { - 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, - }) - } - - protected getSelectionOffset(client: Point, data: EventData.Translating) { - let dx = client.x - data.clientX - let dy = client.y - data.clientY - const restrict = this.graph.hook.getRestrictArea() - if (restrict) { - const cells = this.collection.toArray() - const totalBBox = - Cell.getCellsBBox(cells, { deep: true }) || 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, - } - } - - protected updateSelectedNodesPosition(offset: { dx: number; dy: number }) { - 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() - this.$boxes.add(this.$selectionContainer).css({ - left: `+=${dx * scale.sx}`, - top: `+=${dy * scale.sy}`, - }) - } - } - } - - protected autoScrollGraph(x: number, y: number) { - const scroller = this.graph.scroller.widget - if (scroller) { - return scroller.autoScroll(x, y) - } - return { scrollerX: 0, scrollerY: 0 } - } - - protected adjustSelection(evt: JQuery.MouseMoveEvent) { - const e = this.normalizeEvent(evt) - const eventData = this.getEventData(e) - const action = eventData.action - switch (action) { - case 'selecting': { - const data = eventData as EventData.Selecting - if (data.moving !== true) { - this.$container.appendTo(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(this.$container.css('left'), 10) - const top = parseInt(this.$container.css('top'), 10) - this.$container.css({ - 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 as EventData.Translating - 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 - } - - protected translateSelectedNodes( - dx: number, - dy: number, - exclude?: Cell, - otherOptions?: KeyValue, - ) { - const map: { [id: string]: boolean } = {} - const excluded: Cell[] = [] - - 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 = { - ...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 - } - }) - } - }) - } - - protected getCellViewsInArea(rect: Rectangle) { - const graph = this.graph - const options = { - strict: this.options.strict, - } - let views: CellView[] = [] - - if (this.options.rubberNode) { - if (this.options.useCellGeometry) { - views = views.concat( - graph.model - .getNodesInArea(rect, options) - .map((node) => graph.renderer.findViewByCell(node)) - .filter((view) => view != null) as CellView[], - ) - } else { - views = views.concat(graph.renderer.findViewsInArea(rect, options)) - } - } - - if (this.options.rubberEdge) { - if (this.options.useCellGeometry) { - views = views.concat( - graph.model - .getEdgesInArea(rect, options) - .map((edge) => graph.renderer.findViewByCell(edge)) - .filter((view) => view != null) as CellView[], - ) - } else { - views = views.concat(graph.renderer.findEdgeViewsInArea(rect, options)) - } - } - - return views - } - - protected notifyBoxEvent< - K extends keyof Selection.BoxEventArgs, - T extends JQuery.TriggeredEvent, - >(name: K, e: T, x: number, y: number) { - const data = this.getEventData(e) - const view = data.activeView - this.trigger(name, { e, view, x, y, cell: view.cell }) - } - - protected getSelectedClassName(cell: Cell) { - return this.prefixClassName(`${cell.isNode() ? 'node' : 'edge'}-selected`) - } - - protected addCellSelectedClassName(cell: Cell) { - const view = this.graph.renderer.findViewByCell(cell) - if (view) { - view.addClass(this.getSelectedClassName(cell)) - } - } - - protected removeCellUnSelectedClassName(cell: Cell) { - const view = this.graph.renderer.findViewByCell(cell) - if (view) { - view.removeClass(this.getSelectedClassName(cell)) - } - } - - protected destroySelectionBox(cell: Cell) { - this.removeCellUnSelectedClassName(cell) - - if (this.canShowSelectionBox(cell)) { - this.$container.find(`[data-cell-id="${cell.id}"]`).remove() - if (this.$boxes.length === 0) { - this.hide() - } - this.boxCount = Math.max(0, this.boxCount - 1) - } - } - - protected destroyAllSelectionBoxes(cells: Cell[]) { - cells.forEach((cell) => this.removeCellUnSelectedClassName(cell)) - - this.hide() - this.$boxes.remove() - this.boxCount = 0 - } - - hide() { - this.$container - .removeClass(this.prefixClassName(Private.classNames.rubberband)) - .removeClass(this.prefixClassName(Private.classNames.selected)) - } - - protected showRubberband() { - this.$container.addClass( - this.prefixClassName(Private.classNames.rubberband), - ) - } - - protected hideRubberband() { - this.$container.removeClass( - this.prefixClassName(Private.classNames.rubberband), - ) - } - - protected showSelected() { - this.$container - .removeAttr('style') - .addClass(this.prefixClassName(Private.classNames.selected)) - } - - protected createContainer() { - this.container = document.createElement('div') - this.$container = this.$(this.container) - this.$container.addClass(this.prefixClassName(Private.classNames.root)) - if (this.options.className) { - this.$container.addClass(this.options.className) - } - - this.$selectionContainer = this.$('
').addClass( - this.prefixClassName(Private.classNames.inner), - ) - - this.$selectionContent = this.$('
').addClass( - this.prefixClassName(Private.classNames.content), - ) - - this.$selectionContainer.append(this.$selectionContent) - this.$selectionContainer.attr( - 'data-selection-length', - this.collection.length, - ) - - this.$container.prepend(this.$selectionContainer) - this.$handleContainer = this.$selectionContainer - } - - protected updateContainerPosition(offset: { dx: number; dy: number }) { - if (offset.dx || offset.dy) { - this.$selectionContainer.css({ - left: `+=${offset.dx}`, - top: `+=${offset.dy}`, - }) - } - } - - protected 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: this.options.useCellGeometry, - }) - 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) - } - }) - - this.$selectionContainer - .css({ - position: 'absolute', - pointerEvents: 'none', - left: origin.x, - top: origin.y, - width: corner.x - origin.x, - height: corner.y - origin.y, - }) - .attr('data-selection-length', this.collection.length) - - const boxContent = this.options.content - if (boxContent) { - if (typeof boxContent === 'function') { - const content = FunctionExt.call( - boxContent, - this.graph, - this, - this.$selectionContent[0], - ) - if (content) { - this.$selectionContent.html(content) - } - } else { - this.$selectionContent.html(boxContent) - } - } - - if (this.collection.length > 0 && !this.container.parentNode) { - this.$container.appendTo(this.graph.container) - } else if (this.collection.length <= 0 && this.container.parentNode) { - this.container.parentNode.removeChild(this.container) - } - } - - protected canShowSelectionBox(cell: Cell) { - return ( - (cell.isNode() && this.options.showNodeSelectionBox === true) || - (cell.isEdge() && this.options.showEdgeSelectionBox === true) - ) - } - - protected createSelectionBox(cell: Cell) { - this.addCellSelectedClassName(cell) - - if (this.canShowSelectionBox(cell)) { - const view = this.graph.renderer.findViewByCell(cell) - if (view) { - const bbox = view.getBBox({ - useCellGeometry: this.options.useCellGeometry, - }) - - const className = this.boxClassName - this.$('
') - .addClass(className) - .addClass(`${className}-${cell.isNode() ? 'node' : 'edge'}`) - .attr('data-cell-id', cell.id) - .css({ - position: 'absolute', - left: bbox.x, - top: bbox.y, - width: bbox.width, - height: bbox.height, - pointerEvents: this.options.pointerEvents || 'auto', - }) - .appendTo(this.container) - this.showSelected() - this.boxCount += 1 - } - } - } - - protected updateSelectionBoxes( - options: Renderer.RequestViewUpdateOptions = {}, - ) { - if (this.collection.length > 0) { - this.boxesUpdated = true - this.graph.renderer.requestViewUpdate(this as any, 1, 2, options) - } - } - - confirmUpdate() { - if (this.boxCount) { - this.hide() - this.$boxes.each((_, elem) => { - const cellId = this.$(elem).remove().attr('data-cell-id') - const cell = this.collection.get(cellId) - if (cell) { - this.createSelectionBox(cell) - } - }) - - this.updateContainer() - } - return 0 - } - - protected getCellViewFromElem(elem: Element) { - 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 - } - - protected onCellRemoved({ cell }: Collection.EventArgs['removed']) { - this.destroySelectionBox(cell) - this.updateContainer() - } - - protected onReseted({ previous, current }: Collection.EventArgs['reseted']) { - this.destroyAllSelectionBoxes(previous) - current.forEach((cell) => { - this.listenCellRemoveEvent(cell) - this.createSelectionBox(cell) - }) - this.updateContainer() - } - - protected onCellAdded({ cell }: Collection.EventArgs['added']) { - // 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 evnet. - this.listenCellRemoveEvent(cell) - this.createSelectionBox(cell) - this.updateContainer() - } - - protected listenCellRemoveEvent(cell: Cell) { - cell.off('removed', this.onCellRemoved, this) - cell.on('removed', this.onCellRemoved, this) - } - - protected onCollectionUpdated({ - added, - removed, - options, - }: Collection.EventArgs['updated']) { - added.forEach((cell) => { - this.trigger('cell:selected', { cell, options }) - this.graph.trigger('cell:selected', { cell, options }) - if (cell.isNode()) { - this.trigger('node:selected', { cell, options, node: cell }) - this.graph.trigger('node:selected', { cell, options, node: cell }) - } else if (cell.isEdge()) { - this.trigger('edge:selected', { cell, options, edge: cell }) - this.graph.trigger('edge:selected', { cell, options, edge: cell }) - } - }) - - removed.forEach((cell) => { - this.trigger('cell:unselected', { cell, options }) - this.graph.trigger('cell:unselected', { cell, options }) - if (cell.isNode()) { - this.trigger('node:unselected', { cell, options, node: cell }) - this.graph.trigger('node:unselected', { cell, options, node: cell }) - } else if (cell.isEdge()) { - this.trigger('edge:unselected', { cell, options, edge: cell }) - this.graph.trigger('edge:unselected', { cell, options, edge: cell }) - } - }) - - const args = { - added, - removed, - options, - selected: this.cells, - } - this.trigger('selection:changed', args) - this.graph.trigger('selection:changed', args) - } - - // #region handle - - protected deleteSelectedCells() { - const cells = this.collection.toArray() - this.clean() - this.graph.model.removeCells(cells, { selection: this.cid }) - } - - protected startRotate({ e }: Handle.EventArgs) { - const cells = this.collection.toArray() - const center = Cell.getCellsBBox(cells)!.getCenter() - const client = this.graph.snapToGrid(e.clientX!, e.clientY!) - const angles = cells.reduce<{ [id: string]: number }>( - (memo, cell: Node) => { - memo[cell.id] = Angle.normalize(cell.getAngle()) - return memo - }, - {}, - ) - - this.setEventData(e, { - center, - angles, - start: client.theta(center), - }) - } - - protected doRotate({ e }: Handle.EventArgs) { - const data = this.getEventData(e) - const grid = this.graph.options.rotating.grid - const gridSize = - typeof grid === 'function' - ? FunctionExt.call(grid, this.graph, null as any) - : grid - const client = this.graph.snapToGrid(e.clientX!, e.clientY!) - const delta = data.start - client.theta(data.center) - - if (!data.rotated) { - data.rotated = true - } - - if (Math.abs(delta) > 0.001) { - this.collection.toArray().forEach((node: Node) => { - const angle = Util.snapToGrid( - data.angles[node.id] + delta, - gridSize || 15, - ) - node.rotate(angle, { - absolute: true, - center: data.center, - selection: this.cid, - }) - }) - this.updateSelectionBoxes() - } - } - - protected stopRotate({ e }: Handle.EventArgs) { - const data = this.getEventData(e) - if (data.rotated) { - data.rotated = false - this.collection.toArray().forEach((node: Node) => { - notify( - 'node:rotated', - e as JQuery.MouseUpEvent, - this.graph.findViewByCell(node) as NodeView, - ) - }) - } - } - - protected startResize({ e }: Handle.EventArgs) { - const gridSize = this.graph.getGridSize() - const cells = this.collection.toArray() - const bbox = Cell.getCellsBBox(cells)! - const bboxes = cells.map((cell) => cell.getBBox()) - const maxWidth = bboxes.reduce((maxWidth, bbox) => { - return bbox.width < maxWidth ? bbox.width : maxWidth - }, Infinity) - const maxHeight = bboxes.reduce((maxHeight, bbox) => { - return bbox.height < maxHeight ? bbox.height : maxHeight - }, Infinity) - - this.setEventData(e, { - bbox, - cells: this.graph.model.getSubGraph(cells), - minWidth: (gridSize * bbox.width) / maxWidth, - minHeight: (gridSize * bbox.height) / maxHeight, - }) - } - - protected doResize({ e, dx, dy }: Handle.EventArgs) { - const data = this.eventData(e) - const bbox = data.bbox - const width = bbox.width - const height = bbox.height - const newWidth = Math.max(width + dx, data.minWidth) - const newHeight = Math.max(height + dy, data.minHeight) - - if (!data.resized) { - data.resized = true - } - - if ( - Math.abs(width - newWidth) > 0.001 || - Math.abs(height - newHeight) > 0.001 - ) { - this.graph.model.resizeCells(newWidth, newHeight, data.cells, { - selection: this.cid, - }) - bbox.width = newWidth - bbox.height = newHeight - this.updateSelectionBoxes() - } - } - - protected stopResize({ e }: Handle.EventArgs) { - const data = this.eventData(e) - if (data.resized) { - data.resized = false - this.collection.toArray().forEach((node: Node) => { - notify( - 'node:resized', - e as JQuery.MouseUpEvent, - this.graph.findViewByCell(node) as NodeView, - ) - }) - } - } - - // #endregion - - @View.dispose() - dispose() { - this.clean() - this.remove() - } -} - -export namespace Selection { - export interface CommonOptions extends Handle.Options { - model?: Model - collection?: Collection - className?: string - strict?: boolean - filter?: Filter - - showEdgeSelectionBox?: boolean - showNodeSelectionBox?: boolean - movable?: boolean - following?: boolean - useCellGeometry?: boolean - content?: Content - - // Can select node or edge when rubberband - rubberNode?: boolean - rubberEdge?: boolean - - // Whether to respond event on the selectionBox - pointerEvents?: 'none' | 'auto' - } - - export interface Options extends CommonOptions { - graph: Graph - } - - export type Content = - | null - | false - | string - | (( - this: Graph, - selection: Selection, - contentElement: HTMLElement, - ) => string) - - export type Filter = - | null - | (string | { id: string })[] - | ((this: Graph, cell: Cell) => boolean) - - export interface SetOptions extends Collection.SetOptions { - batch?: boolean - } - - export interface AddOptions extends Collection.AddOptions {} - - export interface RemoveOptions extends Collection.RemoveOptions {} -} - -export namespace Selection { - interface SelectionBoxEventArgs { - e: T - view: CellView - cell: Cell - x: number - y: number - } - - export interface BoxEventArgs { - 'box:mousedown': SelectionBoxEventArgs - 'box:mousemove': SelectionBoxEventArgs - 'box:mouseup': SelectionBoxEventArgs - } - - export interface SelectionEventArgs { - 'cell:selected': { cell: Cell; options: Model.SetOptions } - 'node:selected': { cell: Cell; node: Node; options: Model.SetOptions } - 'edge:selected': { cell: Cell; edge: Edge; options: Model.SetOptions } - 'cell:unselected': { cell: Cell; options: Model.SetOptions } - 'node:unselected': { cell: Cell; node: Node; options: Model.SetOptions } - 'edge:unselected': { cell: Cell; edge: Edge; options: Model.SetOptions } - 'selection:changed': { - added: Cell[] - removed: Cell[] - selected: Cell[] - options: Model.SetOptions - } - } - - export interface EventArgs extends BoxEventArgs, SelectionEventArgs {} -} - -export interface Selection extends Handle {} - -ObjectExt.applyMixins(Selection, Handle) - -// private -// ------- -namespace Private { - const base = 'widget-selection' - - export const classNames = { - root: base, - inner: `${base}-inner`, - box: `${base}-box`, - content: `${base}-content`, - rubberband: `${base}-rubberband`, - selected: `${base}-selected`, - } - - export const documentEvents = { - mousemove: 'adjustSelection', - touchmove: 'adjustSelection', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - touchcancel: 'onMouseUp', - } - - export const defaultOptions: Partial = { - movable: true, - following: true, - strict: false, - useCellGeometry: false, - content(selection) { - return StringExt.template( - '<%= length %> node<%= length > 1 ? "s":"" %> selected.', - )({ length: selection.length }) - }, - handles: [ - { - name: 'remove', - position: 'nw', - events: { - mousedown: 'deleteSelectedCells', - }, - }, - { - name: 'rotate', - position: 'sw', - events: { - mousedown: 'startRotate', - mousemove: 'doRotate', - mouseup: 'stopRotate', - }, - }, - { - name: 'resize', - position: 'se', - events: { - mousedown: 'startResize', - mousemove: 'doResize', - mouseup: 'stopResize', - }, - }, - ], - } - - export function depthComparator(cell: Cell) { - return cell.getAncestors().length - } -} - -namespace EventData { - export interface Common { - action: 'selecting' | 'translating' - } - - export interface Selecting extends Common { - action: 'selecting' - moving?: boolean - clientX: number - clientY: number - offsetX: number - offsetY: number - scrollerX: number - scrollerY: number - } - - export interface Translating extends Common { - action: 'translating' - clientX: number - clientY: number - originX: number - originY: number - } - - export interface SelectionBox { - activeView: CellView - } - - export interface Rotation { - rotated?: boolean - center: Point.PointLike - start: number - angles: { [id: string]: number } - } - - export interface Resizing { - resized?: boolean - bbox: Rectangle - cells: Cell[] - minWidth: number - minHeight: number - } -} diff --git a/packages/x6/src/addon/snapline/index.less b/packages/x6/src/addon/snapline/index.less deleted file mode 100644 index a77d5ebcdee..00000000000 --- a/packages/x6/src/addon/snapline/index.less +++ /dev/null @@ -1,27 +0,0 @@ -@import '../../style/index'; - -@snapline-prefix-cls: ~'@{x6-prefix}-widget-snapline'; - -.@{snapline-prefix-cls} { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - pointer-events: none; - - &-vertical, - &-horizontal { - position: absolute; - opacity: 1; - pointer-events: none; - } - - &-horizontal { - border-bottom: 1px solid #2ecc71; - } - - &-vertical { - border-right: 1px solid #2ecc71; - } -} diff --git a/packages/x6/src/addon/snapline/index.ts b/packages/x6/src/addon/snapline/index.ts deleted file mode 100644 index b96fa6be0ea..00000000000 --- a/packages/x6/src/addon/snapline/index.ts +++ /dev/null @@ -1,679 +0,0 @@ -import { ArrayExt, FunctionExt } from '../../util' -import { IDisablable } from '../../common' -import { Point, Rectangle, Angle } from '../../geometry' -import { Node } from '../../model/node' -import { Model } from '../../model/model' -import { View } from '../../view/view' -import { CellView } from '../../view/cell' -import { NodeView } from '../../view/node' -import { Graph } from '../../graph' -import { EventArgs } from '../../graph/events' - -export class Snapline extends View implements IDisablable { - public readonly options: Snapline.Options - protected readonly graph: Graph - protected filterShapes: { [type: string]: boolean } - protected filterCells: { [id: string]: boolean } - protected filterFunction: Snapline.FilterFunction | null - protected offset: Point.PointLike - protected timer: number | null - protected $container: JQuery - protected $horizontal: JQuery - protected $vertical: JQuery - - protected get model() { - return this.graph.model - } - - protected get containerClassName() { - return this.prefixClassName('widget-snapline') - } - - protected get verticalClassName() { - return `${this.containerClassName}-vertical` - } - - protected get horizontalClassName() { - return `${this.containerClassName}-horizontal` - } - - constructor(options: Snapline.Options & { graph: Graph }) { - super() - - const { graph, ...others } = options - this.graph = graph - this.options = { tolerance: 10, ...others } - this.render() - this.parseFilter() - if (!this.disabled) { - this.startListening() - } - } - - public get disabled() { - return ( - this.options.enabled !== true || - this.graph.options.snapline.enabled !== true - ) - } - - enable() { - if (this.disabled) { - this.options.enabled = true - this.graph.options.snapline.enabled = true - this.startListening() - } - } - - disable() { - if (!this.disabled) { - this.options.enabled = false - this.graph.options.snapline.enabled = false - this.stopListening() - } - } - - setFilter(filter?: Snapline.Filter) { - this.options.filter = filter - this.parseFilter() - } - - protected render() { - this.container = document.createElement('div') - this.$container = this.$(this.container) - this.$horizontal = this.$(document.createElement('div')).addClass( - this.horizontalClassName, - ) - this.$vertical = this.$(document.createElement('div')).addClass( - this.verticalClassName, - ) - - this.$container - .hide() - .addClass(this.containerClassName) - .append([this.$horizontal, this.$vertical]) - - if (this.options.className) { - this.$container.addClass(this.options.className) - } - } - - protected startListening() { - this.stopListening() - this.graph.on('node:mousedown', this.captureCursorOffset, this) - this.graph.on('node:mousemove', this.snapOnMoving, this) - this.model.on('batch:stop', this.onBatchStop, this) - this.delegateDocumentEvents({ - mouseup: 'hide', - touchend: 'hide', - }) - } - - protected stopListening() { - this.graph.off('node:mousedown', this.captureCursorOffset, this) - this.graph.off('node:mousemove', this.snapOnMoving, this) - this.model.off('batch:stop', this.onBatchStop, this) - this.undelegateDocumentEvents() - } - - protected parseFilter() { - this.filterShapes = {} - this.filterCells = {} - this.filterFunction = null - const filter = this.options.filter - if (Array.isArray(filter)) { - filter.forEach((item) => { - if (typeof item === 'string') { - this.filterShapes[item] = true - } else { - this.filterCells[item.id] = true - } - }) - } else if (typeof filter === 'function') { - this.filterFunction = filter - } - } - - protected onBatchStop({ name, data }: Model.EventArgs['batch:stop']) { - if (name === 'resize') { - this.snapOnResizing(data.cell, data as Node.ResizeOptions) - } - } - - captureCursorOffset({ view, x, y }: EventArgs['node:mousedown']) { - const targetView = view.getDelegatedView() - if (targetView && this.isNodeMovable(targetView)) { - const pos = view.cell.getPosition() - this.offset = { - x: x - pos.x, - y: y - pos.y, - } - } - } - - protected isNodeMovable(view: CellView) { - return view && view.cell.isNode() && view.can('nodeMovable') - } - - protected snapOnResizing(node: Node, options: Node.ResizeOptions) { - if ( - this.options.resizing && - !options.snapped && - options.ui && - options.direction && - options.trueDirection - ) { - const view = this.graph.renderer.findViewByCell(node) as NodeView - if (view && view.cell.isNode()) { - const nodeBbox = node.getBBox() - const nodeBBoxRotated = nodeBbox.bbox(node.getAngle()) - const nodeTopLeft = nodeBBoxRotated.getTopLeft() - const nodeBottomRight = nodeBBoxRotated.getBottomRight() - const angle = Angle.normalize(node.getAngle()) - const tolerance = this.options.tolerance || 0 - let verticalLeft: number | undefined - let verticalTop: number | undefined - let verticalHeight: number | undefined - let horizontalTop: number | undefined - let horizontalLeft: number | undefined - let horizontalWidth: number | undefined - - const snapOrigin = { - vertical: 0, - horizontal: 0, - } - - const direction = options.direction - const trueDirection = options.trueDirection - const relativeDirection = options.relativeDirection - - if (trueDirection.indexOf('right') !== -1) { - snapOrigin.vertical = nodeBottomRight.x - } else { - snapOrigin.vertical = nodeTopLeft.x - } - - if (trueDirection.indexOf('bottom') !== -1) { - snapOrigin.horizontal = nodeBottomRight.y - } else { - snapOrigin.horizontal = nodeTopLeft.y - } - - this.model.getNodes().some((cell) => { - if (this.isIgnored(node, cell)) { - return false - } - - const snapBBox = cell.getBBox().bbox(cell.getAngle()) - const snapTopLeft = snapBBox.getTopLeft() - const snapBottomRight = snapBBox.getBottomRight() - const groups = { - vertical: [snapTopLeft.x, snapBottomRight.x], - horizontal: [snapTopLeft.y, snapBottomRight.y], - } - - const distances = {} as { - vertical: { position: number; distance: number }[] - horizontal: { position: number; distance: number }[] - } - - Object.keys(groups).forEach((k) => { - const key = k as 'vertical' | 'horizontal' - const list = groups[key] - .map((value) => ({ - position: value, - distance: Math.abs(value - snapOrigin[key]), - })) - .filter((item) => item.distance <= tolerance) - - distances[key] = ArrayExt.sortBy(list, (item) => item.distance) - }) - - if (verticalLeft == null && distances.vertical.length > 0) { - verticalLeft = distances.vertical[0].position - verticalTop = Math.min(nodeBBoxRotated.y, snapBBox.y) - verticalHeight = - Math.max(nodeBottomRight.y, snapBottomRight.y) - verticalTop - } - - if (horizontalTop == null && distances.horizontal.length > 0) { - horizontalTop = distances.horizontal[0].position - horizontalLeft = Math.min(nodeBBoxRotated.x, snapBBox.x) - horizontalWidth = - Math.max(nodeBottomRight.x, snapBottomRight.x) - horizontalLeft - } - - return verticalLeft != null && horizontalTop != null - }) - - this.hide() - - let dx = 0 - let dy = 0 - if (horizontalTop != null || verticalLeft != null) { - if (verticalLeft != null) { - dx = - trueDirection.indexOf('right') !== -1 - ? verticalLeft - nodeBottomRight.x - : nodeTopLeft.x - verticalLeft - } - - if (horizontalTop != null) { - dy = - trueDirection.indexOf('bottom') !== -1 - ? horizontalTop - nodeBottomRight.y - : nodeTopLeft.y - horizontalTop - } - } - - let dWidth = 0 - let dHeight = 0 - if (angle % 90 === 0) { - if (angle === 90 || angle === 270) { - dWidth = dy - dHeight = dx - } else { - dWidth = dx - dHeight = dy - } - } else { - const quadrant = - angle >= 0 && angle < 90 - ? 1 - : angle >= 90 && angle < 180 - ? 4 - : angle >= 180 && angle < 270 - ? 3 - : 2 - - if (horizontalTop != null && verticalLeft != null) { - if (dx < dy) { - dy = 0 - horizontalTop = undefined - } else { - dx = 0 - verticalLeft = undefined - } - } - - const rad = Angle.toRad(angle % 90) - if (dx) { - dWidth = quadrant === 3 ? dx / Math.cos(rad) : dx / Math.sin(rad) - } - if (dy) { - dHeight = quadrant === 3 ? dy / Math.cos(rad) : dy / Math.sin(rad) - } - - const quadrant13 = quadrant === 1 || quadrant === 3 - switch (relativeDirection) { - case 'top': - case 'bottom': - dHeight = dy - ? dy / (quadrant13 ? Math.cos(rad) : Math.sin(rad)) - : dx / (quadrant13 ? Math.sin(rad) : Math.cos(rad)) - break - case 'left': - case 'right': - dWidth = dx - ? dx / (quadrant13 ? Math.cos(rad) : Math.sin(rad)) - : dy / (quadrant13 ? Math.sin(rad) : Math.cos(rad)) - break - default: - break - } - } - - switch (relativeDirection) { - case 'top': - case 'bottom': - dWidth = 0 - break - case 'left': - case 'right': - dHeight = 0 - break - default: - break - } - - const gridSize = this.graph.getGridSize() - let newWidth = Math.max(nodeBbox.width + dWidth, gridSize) - let newHeight = Math.max(nodeBbox.height + dHeight, gridSize) - - if (options.minWidth && options.minWidth > gridSize) { - newWidth = Math.max(newWidth, options.minWidth) - } - - if (options.minHeight && options.minHeight > gridSize) { - newHeight = Math.max(newHeight, options.minHeight) - } - - if (options.maxWidth) { - newWidth = Math.min(newWidth, options.maxWidth) - } - - if (options.maxHeight) { - newHeight = Math.min(newHeight, options.maxHeight) - } - - if (options.preserveAspectRatio) { - if (dHeight < dWidth) { - newHeight = newWidth * (nodeBbox.height / nodeBbox.width) - } else { - newWidth = newHeight * (nodeBbox.width / nodeBbox.height) - } - } - - if (newWidth !== nodeBbox.width || newHeight !== nodeBbox.height) { - node.resize(newWidth, newHeight, { - direction, - relativeDirection, - trueDirection, - snapped: true, - snaplines: this.cid, - restrict: this.graph.hook.getRestrictArea(view), - }) - - if (verticalHeight) { - verticalHeight += newHeight - nodeBbox.height - } - - if (horizontalWidth) { - horizontalWidth += newWidth - nodeBbox.width - } - } - - const newRotatedBBox = node.getBBox().bbox(angle) - if ( - verticalLeft && - Math.abs(newRotatedBBox.x - verticalLeft) > 1 && - Math.abs(newRotatedBBox.width + newRotatedBBox.x - verticalLeft) > 1 - ) { - verticalLeft = undefined - } - - if ( - horizontalTop && - Math.abs(newRotatedBBox.y - horizontalTop) > 1 && - Math.abs(newRotatedBBox.height + newRotatedBBox.y - horizontalTop) > 1 - ) { - horizontalTop = undefined - } - - this.update({ - verticalLeft, - verticalTop, - verticalHeight, - horizontalTop, - horizontalLeft, - horizontalWidth, - }) - } - } - } - - snapOnMoving({ view, e, x, y }: EventArgs['node:mousemove']) { - const targetView: NodeView = view.getEventData(e).delegatedView || view - if (!this.isNodeMovable(targetView)) { - return - } - - const node = targetView.cell - const size = node.getSize() - const position = node.getPosition() - const cellBBox = new Rectangle( - x - this.offset.x, - y - this.offset.y, - size.width, - size.height, - ) - const angle = node.getAngle() - const nodeCenter = cellBBox.getCenter() - const nodeBBoxRotated = cellBBox.bbox(angle) - const nodeTopLeft = nodeBBoxRotated.getTopLeft() - const nodeBottomRight = nodeBBoxRotated.getBottomRight() - - const distance = this.options.tolerance || 0 - let verticalLeft: number | undefined - let verticalTop: number | undefined - let verticalHeight: number | undefined - let horizontalTop: number | undefined - let horizontalLeft: number | undefined - let horizontalWidth: number | undefined - let verticalFix = 0 - let horizontalFix = 0 - - this.model.getNodes().some((targetNode) => { - if (this.isIgnored(node, targetNode)) { - return false - } - - const snapBBox = targetNode.getBBox().bbox(targetNode.getAngle()) - const snapCenter = snapBBox.getCenter() - const snapTopLeft = snapBBox.getTopLeft() - const snapBottomRight = snapBBox.getBottomRight() - - if (verticalLeft == null) { - if (Math.abs(snapCenter.x - nodeCenter.x) < distance) { - verticalLeft = snapCenter.x - verticalFix = 0.5 - } else if (Math.abs(snapTopLeft.x - nodeTopLeft.x) < distance) { - verticalLeft = snapTopLeft.x - verticalFix = 0 - } else if (Math.abs(snapTopLeft.x - nodeBottomRight.x) < distance) { - verticalLeft = snapTopLeft.x - verticalFix = 1 - } else if (Math.abs(snapBottomRight.x - nodeBottomRight.x) < distance) { - verticalLeft = snapBottomRight.x - verticalFix = 1 - } else if (Math.abs(snapBottomRight.x - nodeTopLeft.x) < distance) { - verticalLeft = snapBottomRight.x - } - - if (verticalLeft != null) { - verticalTop = Math.min(nodeBBoxRotated.y, snapBBox.y) - verticalHeight = - Math.max(nodeBottomRight.y, snapBottomRight.y) - verticalTop - } - } - - if (horizontalTop == null) { - if (Math.abs(snapCenter.y - nodeCenter.y) < distance) { - horizontalTop = snapCenter.y - horizontalFix = 0.5 - } else if (Math.abs(snapTopLeft.y - nodeTopLeft.y) < distance) { - horizontalTop = snapTopLeft.y - } else if (Math.abs(snapTopLeft.y - nodeBottomRight.y) < distance) { - horizontalTop = snapTopLeft.y - horizontalFix = 1 - } else if (Math.abs(snapBottomRight.y - nodeBottomRight.y) < distance) { - horizontalTop = snapBottomRight.y - horizontalFix = 1 - } else if (Math.abs(snapBottomRight.y - nodeTopLeft.y) < distance) { - horizontalTop = snapBottomRight.y - } - - if (horizontalTop != null) { - horizontalLeft = Math.min(nodeBBoxRotated.x, snapBBox.x) - horizontalWidth = - Math.max(nodeBottomRight.x, snapBottomRight.x) - horizontalLeft - } - } - - return verticalLeft != null && horizontalTop != null - }) - - this.hide() - - if (horizontalTop != null || verticalLeft != null) { - if (horizontalTop != null) { - nodeBBoxRotated.y = - horizontalTop - horizontalFix * nodeBBoxRotated.height - } - - if (verticalLeft != null) { - nodeBBoxRotated.x = verticalLeft - verticalFix * nodeBBoxRotated.width - } - - const newCenter = nodeBBoxRotated.getCenter() - const newX = newCenter.x - cellBBox.width / 2 - const newY = newCenter.y - cellBBox.height / 2 - const dx = newX - position.x - const dy = newY - position.y - - if (dx !== 0 || dy !== 0) { - node.translate(dx, dy, { - snapped: true, - restrict: this.graph.hook.getRestrictArea(targetView), - }) - - if (horizontalWidth) { - horizontalWidth += dx - } - - if (verticalHeight) { - verticalHeight += dy - } - } - - this.update({ - verticalLeft, - verticalTop, - verticalHeight, - horizontalTop, - horizontalLeft, - horizontalWidth, - }) - } - } - - protected isIgnored(snapNode: Node, targetNode: Node) { - return ( - targetNode.id === snapNode.id || - targetNode.isDescendantOf(snapNode) || - this.filterShapes[targetNode.shape] || - this.filterCells[targetNode.id] || - (this.filterFunction && - FunctionExt.call(this.filterFunction, this.graph, targetNode)) - ) - } - - protected update(metadata: { - verticalLeft?: number - verticalTop?: number - verticalHeight?: number - horizontalTop?: number - horizontalLeft?: number - horizontalWidth?: number - }) { - const ctm = this.graph.matrix() - const sx = ctm.a - const sy = ctm.d - const tx = ctm.e - const ty = ctm.f - - const sharp = this.options.sharp - const hasScroller = this.graph.scroller.widget != null - - if (metadata.horizontalTop) { - this.$horizontal - .css({ - top: metadata.horizontalTop * sy + ty, - left: sharp - ? metadata.horizontalLeft! * sx + tx - : hasScroller - ? '-300%' - : 0, - width: sharp - ? metadata.horizontalWidth! * sx - : hasScroller - ? '700%' - : '100%', - }) - .show() - } else { - this.$horizontal.hide() - } - - if (metadata.verticalLeft) { - this.$vertical - .css({ - left: metadata.verticalLeft * sx + tx, - top: sharp - ? metadata.verticalTop! * sy + ty - : hasScroller - ? '-300%' - : 0, - height: sharp - ? metadata.verticalHeight! * sy - : hasScroller - ? '700%' - : '100%', - }) - .show() - } else { - this.$vertical.hide() - } - - this.show() - } - - protected resetTimer() { - if (this.timer) { - clearTimeout(this.timer) - this.timer = null - } - } - - show() { - this.$container.show() - this.resetTimer() - if (this.container.parentNode == null) { - this.graph.container.appendChild(this.container) - } - return this - } - - hide() { - this.$container.hide() - this.resetTimer() - const clean = this.options.clean - const delay = typeof clean === 'number' ? clean : clean !== false ? 3000 : 0 - if (delay > 0) { - this.timer = window.setTimeout(() => { - this.unmount() - }, delay) - } - - return this - } - - protected onRemove() { - this.stopListening() - this.hide() - } - - @View.dispose() - dispose() { - this.remove() - } -} - -export namespace Snapline { - export interface Options { - enabled?: boolean - className?: string - tolerance?: number - sharp?: boolean - /** - * Specify if snap on node resizing or not. - */ - resizing?: boolean - clean?: boolean | number - filter?: Filter - } - - export type Filter = null | (string | { id: string })[] | FilterFunction - - export type FilterFunction = (this: Graph, node: Node) => boolean -} diff --git a/packages/x6/src/addon/stencil/index.less b/packages/x6/src/addon/stencil/index.less deleted file mode 100644 index d88a1978ae1..00000000000 --- a/packages/x6/src/addon/stencil/index.less +++ /dev/null @@ -1,257 +0,0 @@ -@import '../dnd/index.less'; - -@stencil-prefix-cls: ~'@{x6-prefix}-widget-stencil'; - -.@{stencil-prefix-cls} { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - - &::after { - position: absolute; - top: 0; - display: block; - width: 100%; - height: 20px; - padding: 8px 0; - line-height: 20px; - text-align: center; - opacity: 0; - transition: top 0.1s linear, opacity 0.1s linear; - content: ' '; - pointer-events: none; - } - - &-content { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - height: auto; - overflow-x: hidden; - overflow-y: auto; - } - - .@{x6-prefix}-node [magnet]:not([magnet='passive']) { - pointer-events: none; - } - - &-group { - padding: 0; - padding-bottom: 8px; - overflow: hidden; - user-select: none; - - &.collapsed { - height: auto; - padding-bottom: 0; - } - - &-title { - position: relative; - margin-top: 0; - margin-bottom: 0; - padding: 4px; - cursor: pointer; - } - } - - &-title, - &-group > &-group-title { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - user-select: none; - } - - .unmatched { - opacity: 0.3; - } - - .@{x6-prefix}-node.unmatched { - display: none; - } - - &-group.unmatched { - display: none; - } - - &-search-text { - position: relative; - z-index: 1; - box-sizing: border-box; - width: 100%; - height: 30px; - max-height: 30px; - line-height: 30px; - outline: 0; - } - - &.not-found::after { - opacity: 1; - content: attr(data-not-found-text); - } - - &.not-found.searchable::after { - top: 30px; - } - - &.not-found.searchable.collapsable::after { - top: 50px; - } -} - -// theme -.@{stencil-prefix-cls} { - color: #333; - background: #f5f5f5; - - &-content { - position: absolute; - } - - &.collapsable > &-content { - top: 32px; - } - - &.searchable > &-content { - top: 80px; - } - - &.not-found::after { - position: absolute; - } - - &.not-found.searchable.collapsable::after { - top: 80px; - } - - &.not-found.searchable::after { - top: 60px; - } - - &-group { - height: auto; - margin-bottom: 1px; - padding: 0; - transition: none; - - .@{x6-prefix}-graph { - background: transparent; - box-shadow: none; - } - - &.collapsed { - height: auto; - max-height: 31px; - } - } - - &-title, - &-group > &-group-title { - position: relative; - left: 0; - box-sizing: border-box; - width: 100%; - height: 32px; - padding: 0 5px 0 8px; - color: #666; - font-weight: 700; - font-size: 12px; - line-height: 32px; - cursor: default; - transition: all 0.3; - - &:hover { - color: #444; - } - } - - &-title { - background: #e9e9e9; - } - - &-group > &-group-title { - background: #ededed; - } - - &.collapsable > &-title, - &-group.collapsable > &-group-title { - padding-left: 32px; - cursor: pointer; - - &::before { - position: absolute; - top: 6px; - left: 8px; - display: block; - width: 18px; - height: 18px; - margin: 0; - padding: 0; - background-color: transparent; - background-repeat: no-repeat; - background-position: 0 0; - border: none; - content: ' '; - } - } - - &.collapsable > &-title::before, - &-group.collapsable > &-group-title::before { - background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIj48cGF0aCBkPSJNOS4zNzUuNUM0LjY4Ny41Ljg3NSA0LjMxMy44NzUgOWMwIDQuNjg4IDMuODEyIDguNSA4LjUgOC41IDQuNjg3IDAgOC41LTMuODEyIDguNS04LjUgMC00LjY4Ny0zLjgxMy04LjUtOC41LTguNXptMCAxNS44ODZDNS4zMDMgMTYuMzg2IDEuOTkgMTMuMDcyIDEuOTkgOXMzLjMxMi03LjM4NSA3LjM4NS03LjM4NVMxNi43NiA0LjkyOCAxNi43NiA5YzAgNC4wNzItMy4zMTMgNy4zODYtNy4zODUgNy4zODZ6Ii8+PHBhdGggZD0iTTEyLjc1MyA4LjQ0M0g1Ljk5N2EuNTU4LjU1OCAwIDAwMCAxLjExNmg2Ljc1NmEuNTU4LjU1OCAwIDAwMC0xLjExNnoiLz48L2c+PC9zdmc+'); - opacity: 0.4; - transition: all 0.3s; - } - - &.collapsable > &-title:hover::before, - &-group.collapsable > &-group-title:hover::before { - opacity: 0.6; - } - - &.collapsable.collapsed > &-title::before, - &-group.collapsable.collapsed > &-group-title::before { - background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIj48cGF0aCBkPSJNOS4zNzUuNUM0LjY4Ny41Ljg3NSA0LjMxMy44NzUgOWMwIDQuNjg4IDMuODEyIDguNSA4LjUgOC41IDQuNjg3IDAgOC41LTMuODEyIDguNS04LjUgMC00LjY4Ny0zLjgxMy04LjUtOC41LTguNXptMCAxNS44ODZDNS4zMDMgMTYuMzg2IDEuOTkgMTMuMDcyIDEuOTkgOXMzLjMxMi03LjM4NSA3LjM4NS03LjM4NVMxNi43NiA0LjkyOCAxNi43NiA5YzAgNC4wNzItMy4zMTMgNy4zODYtNy4zODUgNy4zODZ6Ii8+PHBhdGggZD0iTTEyLjc1MyA4LjQ0M0g1Ljk5N2EuNTU4LjU1OCAwIDAwMCAxLjExNmg2Ljc1NmEuNTU4LjU1OCAwIDAwMC0xLjExNnoiLz48cGF0aCBkPSJNOC44MTcgNS42MjN2Ni43NTZhLjU1OC41NTggMCAwMDEuMTE2IDBWNS42MjNhLjU1OC41NTggMCAxMC0xLjExNiAweiIvPjwvZz48L3N2Zz4='); - opacity: 0.4; - } - - &.collapsable.collapsed > &-title:hover::before, - &-group.collapsable.collapsed > &-group-title:hover::before { - opacity: 0.6; - } - - input[type='search'] { - -webkit-appearance: textfield; - } - - input[type='search']::-webkit-search-cancel-button, - input[type='search']::-webkit-search-decoration { - -webkit-appearance: none; - } - - &-search-text { - display: block; - width: 90%; - margin: 8px 5%; - padding-left: 8px; - color: #333; - background: #fff; - border: 1px solid #e9e9e9; - border-radius: 12px; - outline: 0; - - &:focus { - outline: 0; - } - } - - &::after { - color: #808080; - font-weight: 600; - font-size: 12px; - background: 0 0; - } -} diff --git a/packages/x6/src/addon/stencil/index.ts b/packages/x6/src/addon/stencil/index.ts deleted file mode 100644 index 2c8f6e8bdbc..00000000000 --- a/packages/x6/src/addon/stencil/index.ts +++ /dev/null @@ -1,546 +0,0 @@ -import { FunctionExt } from '../../util' -import { grid } from '../../layout/grid' -import { Cell } from '../../model/cell' -import { Node } from '../../model/node' -import { Model } from '../../model/model' -import { View } from '../../view/view' -import { Graph } from '../../graph/graph' -import { EventArgs } from '../../graph/events' -import { Dnd } from '../dnd' - -export class Stencil extends View { - public readonly options: Stencil.Options - public readonly dnd: Dnd - protected readonly graphs: { [groupName: string]: Graph } - protected readonly $groups: { [groupName: string]: JQuery } - protected readonly $container: JQuery - protected readonly $content: JQuery - - protected get targetScroller() { - const target = this.options.target - return Graph.isGraph(target) ? target.scroller.widget : target - } - - protected get targetGraph() { - const target = this.options.target - return Graph.isGraph(target) ? target : target.graph - } - - protected get targetModel() { - return this.targetGraph.model - } - - constructor(options: Partial) { - super() - - this.graphs = {} - this.$groups = {} - this.options = { - ...Stencil.defaultOptions, - ...options, - } as Stencil.Options - - this.dnd = new Dnd(this.options) - this.onSearch = FunctionExt.debounce(this.onSearch, 200) - this.container = document.createElement('div') - this.$container = this.$(this.container) - .addClass(this.prefixClassName(ClassNames.base)) - .attr( - 'data-not-found-text', - this.options.notFoundText || 'No matches found', - ) - - this.options.collapsable = - options.collapsable && - options.groups && - options.groups.some((group) => group.collapsable !== false) - - if (this.options.collapsable) { - this.$container.addClass('collapsable') - const collapsed = - options.groups && - options.groups.every( - (group) => group.collapsed || group.collapsable === false, - ) - if (collapsed) { - this.$container.addClass('collapsed') - } - } - - this.$('
') - .addClass(this.prefixClassName(ClassNames.title)) - .html(this.options.title) - .appendTo(this.$container) - - if (options.search) { - this.$container.addClass('searchable').append(this.renderSearch()) - } - - this.$content = this.$('
') - .addClass(this.prefixClassName(ClassNames.content)) - .appendTo(this.$container) - - const globalGraphOptions = options.stencilGraphOptions || {} - - if (options.groups && options.groups.length) { - options.groups.forEach((group) => { - const $group = this.$('
') - .addClass(this.prefixClassName(ClassNames.group)) - .attr('data-name', group.name) - - if ( - (group.collapsable == null && options.collapsable) || - group.collapsable !== false - ) { - $group.addClass('collapsable') - } - - $group.toggleClass('collapsed', group.collapsed === true) - - const $title = this.$('

') - .addClass(this.prefixClassName(ClassNames.groupTitle)) - .html(group.title || group.name) - - const $content = this.$('
').addClass( - this.prefixClassName(ClassNames.groupContent), - ) - - const graphOptionsInGroup = group.graphOptions - const graph = new Graph({ - ...globalGraphOptions, - ...graphOptionsInGroup, - container: document.createElement('div'), - model: globalGraphOptions.model || new Model(), - width: group.graphWidth || options.stencilGraphWidth, - height: group.graphHeight || options.stencilGraphHeight, - interacting: false, - preventDefaultBlankAction: false, - }) - - $content.append(graph.container) - $group.append($title, $content).appendTo(this.$content) - - this.$groups[group.name] = $group - this.graphs[group.name] = graph - }) - } else { - const graph = new Graph({ - ...globalGraphOptions, - container: document.createElement('div'), - model: globalGraphOptions.model || new Model(), - width: options.stencilGraphWidth, - height: options.stencilGraphHeight, - interacting: false, - preventDefaultBlankAction: false, - }) - this.$content.append(graph.container) - this.graphs[Private.defaultGroupName] = graph - } - - this.startListening() - return this - } - - protected renderSearch() { - return this.$('
') - .addClass(this.prefixClassName(ClassNames.search)) - .append( - this.$('') - .attr({ - type: 'search', - placeholder: this.options.placeholder || 'Search', - }) - .addClass(this.prefixClassName(ClassNames.searchText)), - ) - } - - protected startListening() { - const title = this.prefixClassName(ClassNames.title) - const searchText = this.prefixClassName(ClassNames.searchText) - const groupTitle = this.prefixClassName(ClassNames.groupTitle) - - this.delegateEvents({ - [`click .${title}`]: 'onTitleClick', - [`touchstart .${title}`]: 'onTitleClick', - [`click .${groupTitle}`]: 'onGroupTitleClick', - [`touchstart .${groupTitle}`]: 'onGroupTitleClick', - [`input .${searchText}`]: 'onSearch', - [`focusin .${searchText}`]: 'onSearchFocusIn', - [`focusout .${searchText}`]: 'onSearchFocusOut', - }) - - Object.keys(this.graphs).forEach((groupName) => { - const graph = this.graphs[groupName] - graph.on('cell:mousedown', this.onDragStart, this) - }) - } - - protected stopListening() { - this.undelegateEvents() - Object.keys(this.graphs).forEach((groupName) => { - const graph = this.graphs[groupName] - graph.off('cell:mousedown', this.onDragStart, this) - }) - } - - load(groups: { [groupName: string]: (Node | Node.Metadata)[] }): this - load(nodes: (Node | Node.Metadata)[], groupName?: string): this - load( - data: - | { [groupName: string]: (Node | Node.Metadata)[] } - | (Node | Node.Metadata)[], - groupName?: string, - ) { - if (Array.isArray(data)) { - this.loadGroup(data, groupName) - } else if (this.options.groups) { - Object.keys(this.options.groups).forEach((groupName) => { - if (data[groupName]) { - this.loadGroup(data[groupName], groupName) - } - }) - } - return this - } - - protected loadGroup(cells: (Node | Node.Metadata)[], groupName?: string) { - const model = this.getModel(groupName) - if (model) { - const nodes = cells.map((cell) => - Node.isNode(cell) ? cell : Node.create(cell), - ) - model.resetCells(nodes) - } - - const group = this.getGroup(groupName) - let height = this.options.stencilGraphHeight - if (group && group.graphHeight != null) { - height = group.graphHeight - } - - const layout = (group && group.layout) || this.options.layout - if (layout && model) { - FunctionExt.call(layout, this, model, group) - } - - if (!height) { - const graph = this.getGraph(groupName) - graph.fitToContent({ - minWidth: graph.options.width, - gridHeight: 1, - padding: - (group && group.graphPadding) || - this.options.stencilGraphPadding || - 10, - }) - } - - return this - } - - protected onDragStart(args: EventArgs['node:mousedown']) { - const { e, node } = args - this.dnd.start(node, e) - } - - protected filter(keyword: string, filter?: Stencil.Filter) { - const found = Object.keys(this.graphs).reduce((memo, groupName) => { - const graph = this.graphs[groupName] - const name = groupName === Private.defaultGroupName ? null : groupName - const items = graph.model.getNodes().filter((cell) => { - let matched = false - if (typeof filter === 'function') { - matched = FunctionExt.call(filter, this, cell, keyword, name, this) - } else if (typeof filter === 'boolean') { - matched = filter - } else { - matched = this.isCellMatched( - cell, - keyword, - filter, - keyword.toLowerCase() !== keyword, - ) - } - - const view = graph.renderer.findViewByCell(cell) - if (view) { - view.$(view.container).toggleClass('unmatched', !matched) - } - - return matched - }) - - const found = items.length > 0 - const options = this.options - - const model = new Model() - model.resetCells(items) - - if (options.layout) { - FunctionExt.call(options.layout, this, model, this.getGroup(groupName)) - } - - if (this.$groups[groupName]) { - this.$groups[groupName].toggleClass('unmatched', !found) - } - - graph.fitToContent({ - gridWidth: 1, - gridHeight: 1, - padding: options.stencilGraphPadding || 10, - }) - - return memo || found - }, false) - - this.$container.toggleClass('not-found', !found) - } - - protected isCellMatched( - cell: Cell, - keyword: string, - filters: Stencil.Filters | undefined, - ignoreCase: boolean, - ) { - if (keyword && filters) { - return Object.keys(filters).some((shape) => { - if (shape === '*' || cell.shape === shape) { - const filter = filters[shape] - if (typeof filter === 'boolean') { - return filter - } - - const paths = Array.isArray(filter) ? filter : [filter] - return paths.some((path) => { - let val = cell.getPropByPath(path) - if (val != null) { - val = `${val}` - if (!ignoreCase) { - val = val.toLowerCase() - } - return val.indexOf(keyword) >= 0 - } - return false - }) - } - - return false - }) - } - - return true - } - - protected onSearch(evt: JQuery.TriggeredEvent) { - this.filter(evt.target.value as string, this.options.search) - } - - protected onSearchFocusIn() { - this.$container.addClass('is-focused') - } - - protected onSearchFocusOut() { - this.$container.removeClass('is-focused') - } - - protected onTitleClick() { - if (this.options.collapsable) { - this.$container.toggleClass('collapsed') - if (this.$container.hasClass('collapsed')) { - this.collapseGroups() - } else { - this.expandGroups() - } - } - } - - protected onGroupTitleClick(evt: JQuery.TriggeredEvent) { - const $group = this.$(evt.target).closest( - `.${this.prefixClassName(ClassNames.group)}`, - ) - this.toggleGroup($group.attr('data-name') || '') - - const allCollapsed = Object.keys(this.$groups).every((name) => { - const group = this.getGroup(name) - const $group = this.$groups[name] - return ( - (group && group.collapsable === false) || $group.hasClass('collapsed') - ) - }) - - this.$container.toggleClass('collapsed', allCollapsed) - } - - protected getModel(groupName?: string) { - const graph = this.getGraph(groupName) - return graph ? graph.model : null - } - - protected getGraph(groupName?: string) { - return this.graphs[groupName || Private.defaultGroupName] - } - - protected getGroup(groupName?: string) { - const groups = this.options.groups - if (groupName != null && groups && groups.length) { - return groups.find((group) => group.name === groupName) - } - return null - } - - toggleGroup(groupName: string) { - if (this.isGroupCollapsed(groupName)) { - this.expandGroup(groupName) - } else { - this.collapseGroup(groupName) - } - return this - } - - collapseGroup(groupName: string) { - if (this.isGroupCollapsable(groupName)) { - const $group = this.$groups[groupName] - if ($group && !this.isGroupCollapsed(groupName)) { - this.trigger('group:collapse', { name: groupName }) - $group.addClass('collapsed') - } - } - return this - } - - expandGroup(groupName: string) { - if (this.isGroupCollapsable(groupName)) { - const $group = this.$groups[groupName] - if ($group && this.isGroupCollapsed(groupName)) { - this.trigger('group:expand', { name: groupName }) - $group.removeClass('collapsed') - } - } - return this - } - - isGroupCollapsable(groupName: string) { - const $group = this.$groups[groupName] - return $group.hasClass('collapsable') - } - - isGroupCollapsed(groupName: string) { - const $group = this.$groups[groupName] - return $group && $group.hasClass('collapsed') - } - - collapseGroups() { - Object.keys(this.$groups).forEach((groupName) => - this.collapseGroup(groupName), - ) - return this - } - - expandGroups() { - Object.keys(this.$groups).forEach((groupName) => - this.expandGroup(groupName), - ) - return this - } - - resizeGroup(groupName: string, size: { width: number; height: number }) { - const graph = this.graphs[groupName] - if (graph) { - graph.resize(size.width, size.height) - } - return this - } - - onRemove() { - Object.keys(this.graphs).forEach((groupName) => { - const graph = this.graphs[groupName] - graph.view.remove() - delete this.graphs[groupName] - }) - this.dnd.remove() - this.stopListening() - this.undelegateDocumentEvents() - } -} - -export namespace Stencil { - export interface Options extends Dnd.Options { - title: string - groups?: Group[] - search?: Filter - placeholder?: string - notFoundText?: string - collapsable?: boolean - stencilGraphWidth: number - stencilGraphHeight: number - stencilGraphOptions?: Graph.Options - stencilGraphPadding?: number - layout?: (this: Stencil, model: Model, group?: Group | null) => any - layoutOptions?: any - } - - export type Filter = Filters | FilterFn | boolean - export type Filters = { [shape: string]: string | string[] | boolean } - export type FilterFn = ( - this: Stencil, - cell: Node, - keyword: string, - groupName: string | null, - stencil: Stencil, - ) => boolean - - export interface Group { - name: string - title?: string - collapsed?: boolean - collapsable?: boolean - graphWidth?: number - graphHeight?: number - graphPadding?: number - graphOptions?: Graph.Options - layout?: (this: Stencil, model: Model, group?: Group | null) => any - layoutOptions?: any - } - - export const defaultOptions: Partial = { - stencilGraphWidth: 200, - stencilGraphHeight: 800, - title: 'Stencil', - collapsable: false, - placeholder: 'Search', - notFoundText: 'No matches found', - - layout(model, group) { - const options = { - columnWidth: (this.options.stencilGraphWidth as number) / 2 - 10, - columns: 2, - rowHeight: 80, - resizeToFit: false, - dx: 10, - dy: 10, - } - - grid(model, { - ...options, - ...this.options.layoutOptions, - ...(group ? group.layoutOptions : {}), - }) - }, - ...Dnd.defaults, - } -} - -namespace ClassNames { - export const base = 'widget-stencil' - export const title = `${base}-title` - export const search = `${base}-search` - export const searchText = `${search}-text` - export const content = `${base}-content` - export const group = `${base}-group` - export const groupTitle = `${group}-title` - export const groupContent = `${group}-content` -} - -namespace Private { - export const defaultGroupName = '__default__' -} diff --git a/packages/x6/src/addon/transform/index.less b/packages/x6/src/addon/transform/index.less deleted file mode 100644 index b29e2181767..00000000000 --- a/packages/x6/src/addon/transform/index.less +++ /dev/null @@ -1,159 +0,0 @@ -@import '../../style/index'; - -@transform-prefix-cls: ~'@{x6-prefix}-widget-transform'; -@transform-box-padding: 4px; - -.@{transform-prefix-cls} { - position: absolute; - box-sizing: content-box !important; - margin: -@transform-box-padding - 1px 0 0 -@transform-box-padding - 1px; - padding: @transform-box-padding; - border: 1px dashed #000; - border-radius: 5px; - user-select: none; - pointer-events: none; - - & > div { - position: absolute; - box-sizing: border-box; - background-color: #fff; - border: 1px solid #000; - transition: background-color 0.2s; - pointer-events: auto; - -webkit-user-drag: none; - user-drag: none; /* stylelint-disable-line */ - - &:hover { - background-color: #d3d3d3; - } - } - - &-cursor { - &-n { - cursor: n-resize; - } - - &-s { - cursor: s-resize; - } - - &-e { - cursor: e-resize; - } - - &-w { - cursor: w-resize; - } - - &-ne { - cursor: ne-resize; - } - - &-nw { - cursor: nw-resize; - } - - &-se { - cursor: se-resize; - } - - &-sw { - cursor: sw-resize; - } - } - - &-resize { - width: 10px; - height: 10px; - border-radius: 6px; - - &[data-position='top-left'] { - top: -5px; - left: -5px; - } - - &[data-position='top-right'] { - top: -5px; - right: -5px; - } - - &[data-position='bottom-left'] { - bottom: -5px; - left: -5px; - } - - &[data-position='bottom-right'] { - right: -5px; - bottom: -5px; - } - - &[data-position='top'] { - top: -5px; - left: 50%; - margin-left: -5px; - } - - &[data-position='bottom'] { - bottom: -5px; - left: 50%; - margin-left: -5px; - } - - &[data-position='left'] { - top: 50%; - left: -5px; - margin-top: -5px; - } - - &[data-position='right'] { - top: 50%; - right: -5px; - margin-top: -5px; - } - } - - &.prevent-aspect-ratio &-resize[data-position='top'], - &.prevent-aspect-ratio &-resize[data-position='bottom'], - &.prevent-aspect-ratio &-resize[data-position='left'], - &.prevent-aspect-ratio &-resize[data-position='right'] { - display: none; - } - - &.no-orth-resize &-resize[data-position='bottom'], - &.no-orth-resize &-resize[data-position='left'], - &.no-orth-resize &-resize[data-position='right'], - &.no-orth-resize &-resize[data-position='top'] { - display: none; - } - - &.no-resize &-resize { - display: none; - } - - &-rotate { - top: -20px; - left: -20px; - width: 12px; - height: 12px; - border-radius: 6px; - cursor: crosshair; - } - - &.no-rotate &-rotate { - display: none; - } - - &-active { - border-color: transparent; - pointer-events: all; - - > div { - display: none; - } - - & > &-handle { - display: block; - background-color: #808080; - } - } -} diff --git a/packages/x6/src/addon/transform/index.ts b/packages/x6/src/addon/transform/index.ts deleted file mode 100644 index f0d913e28d3..00000000000 --- a/packages/x6/src/addon/transform/index.ts +++ /dev/null @@ -1,611 +0,0 @@ -import { Util } from '../../global' -import { KeyValue } from '../../types' -import { NumberExt } from '../../util' -import { Angle, Point } from '../../geometry' -import { Node } from '../../model/node' -import { NodeView } from '../../view/node' -import { Widget } from '../common' -import { notify } from './util' - -export class Transform extends Widget { - protected handle: Element | null - protected prevShift: number - protected $container: JQuery - - protected get node() { - return this.cell as Node - } - - protected get containerClassName() { - return this.prefixClassName('widget-transform') - } - - protected get resizeClassName() { - return `${this.containerClassName}-resize` - } - - protected get rotateClassName() { - return `${this.containerClassName}-rotate` - } - - protected init(options: Transform.Options) { - this.options = { - ...Private.defaultOptions, - ...options, - } - - this.render() - this.startListening() - } - - protected startListening() { - this.delegateEvents({ - [`mousedown .${this.resizeClassName}`]: 'startResizing', - [`touchstart .${this.resizeClassName}`]: 'startResizing', - [`mousedown .${this.rotateClassName}`]: 'startRotating', - [`touchstart .${this.rotateClassName}`]: 'startRotating', - }) - - this.model.on('*', this.update, this) - this.graph.on('scale', this.update, this) - this.graph.on('translate', this.update, this) - - this.node.on('removed', this.remove, this) - this.model.on('reseted', this.remove, this) - - this.view.on('cell:knob:mousedown', this.onKnobMouseDown, this) - this.view.on('cell:knob:mouseup', this.onKnobMouseUp, this) - - super.startListening() - } - - protected stopListening() { - this.undelegateEvents() - - this.model.off('*', this.update, this) - this.graph.off('scale', this.update, this) - this.graph.off('translate', this.update, this) - - this.node.off('removed', this.remove, this) - this.model.off('reseted', this.remove, this) - - this.view.off('cell:knob:mousedown', this.onKnobMouseDown, this) - this.view.off('cell:knob:mouseup', this.onKnobMouseUp, this) - - super.stopListening() - } - - protected renderHandles() { - this.container = document.createElement('div') - this.$container = this.$(this.container) - - const $knob = this.$('
').prop('draggable', false) - const $rotate = $knob.clone().addClass(this.rotateClassName) - - const $resizes = Private.POSITIONS.map((pos) => { - return $knob - .clone() - .addClass(this.resizeClassName) - .attr('data-position', pos) - }) - this.empty() - this.$container.append($resizes, $rotate) - } - - render() { - this.renderHandles() - this.view.addClass(Private.NODE_CLS) - this.$container - .addClass(this.containerClassName) - .toggleClass( - 'no-orth-resize', - this.options.preserveAspectRatio || !this.options.orthogonalResizing, - ) - .toggleClass('no-resize', !this.options.resizable) - .toggleClass('no-rotate', !this.options.rotatable) - - if (this.options.className) { - this.$container.addClass(this.options.className) - } - - this.graph.container.appendChild(this.container) - - return this.update() - } - - update() { - const ctm = this.graph.matrix() - const bbox = this.node.getBBox() - - bbox.x *= ctm.a - bbox.x += ctm.e - bbox.y *= ctm.d - bbox.y += ctm.f - bbox.width *= ctm.a - bbox.height *= ctm.d - - const angle = Angle.normalize(this.node.getAngle()) - const transform = angle !== 0 ? `rotate(${angle}deg)` : '' - this.$container.css({ - transform, - width: bbox.width, - height: bbox.height, - left: bbox.x, - top: bbox.y, - }) - - this.updateResizerDirections() - - return this - } - - remove() { - this.view.removeClass(Private.NODE_CLS) - return super.remove() - } - - protected onKnobMouseDown() { - this.startHandle() - } - - protected onKnobMouseUp() { - this.stopHandle() - } - - protected updateResizerDirections() { - // Update the directions on the resizer divs while the node being rotated. - // The directions are represented by cardinal points (N,S,E,W). For example - // the div originally pointed to north needs to be changed to point to south - // if the node was rotated by 180 degrees. - const angle = Angle.normalize(this.node.getAngle()) - const shift = Math.floor(angle * (Private.DIRECTIONS.length / 360)) - if (shift !== this.prevShift) { - // Create the current directions array based on the calculated shift. - const directions = Private.DIRECTIONS.slice(shift).concat( - Private.DIRECTIONS.slice(0, shift), - ) - - const className = (dir: string) => - `${this.containerClassName}-cursor-${dir}` - - this.$container - .find(`.${this.resizeClassName}`) - .removeClass(Private.DIRECTIONS.map((dir) => className(dir)).join(' ')) - .each((index, elem) => { - this.$(elem).addClass(className(directions[index])) - }) - this.prevShift = shift - } - } - - protected getTrueDirection(dir: Node.ResizeDirection) { - const angle = Angle.normalize(this.node.getAngle()) - let index = Private.POSITIONS.indexOf(dir) - - index += Math.floor(angle * (Private.POSITIONS.length / 360)) - index %= Private.POSITIONS.length - - return Private.POSITIONS[index] - } - - protected toValidResizeDirection(dir: string): Node.ResizeDirection { - return ( - ( - { - top: 'top-left', - bottom: 'bottom-right', - left: 'bottom-left', - right: 'top-right', - } as KeyValue - )[dir] || dir - ) - } - - protected startResizing(evt: JQuery.MouseDownEvent) { - evt.stopPropagation() - this.model.startBatch('resize', { cid: this.cid }) - const dir = this.$(evt.target).attr('data-position') as Node.ResizeDirection - const view = this.graph.findViewByCell(this.node) as NodeView - this.prepareResizing(evt, dir) - this.startAction(evt) - notify('node:resize:mousedown', evt, view) - } - - protected prepareResizing( - evt: JQuery.TriggeredEvent, - relativeDirection: Node.ResizeDirection, - ) { - const trueDirection = this.getTrueDirection(relativeDirection) - let rx = 0 - let ry = 0 - relativeDirection.split('-').forEach((direction) => { - rx = ({ left: -1, right: 1 } as KeyValue)[direction] || rx - ry = ({ top: -1, bottom: 1 } as KeyValue)[direction] || ry - }) - - const direction = this.toValidResizeDirection(relativeDirection) - const selector = ( - { - 'top-right': 'bottomLeft', - 'top-left': 'bottomRight', - 'bottom-left': 'topRight', - 'bottom-right': 'topLeft', - } as KeyValue - )[direction] - const angle = Angle.normalize(this.node.getAngle()) - - this.setEventData(evt, { - selector, - direction, - trueDirection, - relativeDirection, - angle, - resizeX: rx, - resizeY: ry, - action: 'resizing', - }) - } - - protected startRotating(evt: JQuery.MouseDownEvent) { - evt.stopPropagation() - - this.model.startBatch('rotate', { cid: this.cid }) - - const view = this.graph.findViewByCell(this.node) as NodeView - const center = this.node.getBBox().getCenter() - const e = this.normalizeEvent(evt) - const client = this.graph.snapToGrid(e.clientX, e.clientY) - this.setEventData(evt, { - center, - action: 'rotating', - angle: Angle.normalize(this.node.getAngle()), - start: Point.create(client).theta(center), - }) - this.startAction(evt) - notify('node:rotate:mousedown', evt, view) - } - - protected onMouseMove(evt: JQuery.MouseMoveEvent) { - const view = this.graph.findViewByCell(this.node) as NodeView - let data = this.getEventData(evt) - if (data.action) { - const e = this.normalizeEvent(evt) - let clientX = e.clientX - let clientY = e.clientY - - const scroller = this.graph.scroller.widget - const restrict = this.options.restrictedResizing - - if (restrict === true || typeof restrict === 'number') { - const factor = restrict === true ? 0 : restrict - const fix = scroller ? Math.max(factor, 8) : factor - const rect = this.graph.container.getBoundingClientRect() - clientX = NumberExt.clamp(clientX, rect.left + fix, rect.right - fix) - clientY = NumberExt.clamp(clientY, rect.top + fix, rect.bottom - fix) - } else if (this.options.autoScrollOnResizing && scroller) { - scroller.autoScroll(clientX, clientY) - } - - const pos = this.graph.snapToGrid(clientX, clientY) - const gridSize = this.graph.getGridSize() - const node = this.node - const options = this.options - - if (data.action === 'resizing') { - data = data as EventData.Resizing - if (!data.resized) { - if (view) { - view.addClass('node-resizing') - notify('node:resize', evt, view) - } - data.resized = true - } - - const currentBBox = node.getBBox() - const requestedSize = Point.create(pos) - .rotate(data.angle, currentBBox.getCenter()) - .diff(currentBBox[data.selector]) - - let width = data.resizeX - ? requestedSize.x * data.resizeX - : currentBBox.width - - let height = data.resizeY - ? requestedSize.y * data.resizeY - : currentBBox.height - - const rawWidth = width - const rawHeight = height - - width = Util.snapToGrid(width, gridSize) - height = Util.snapToGrid(height, gridSize) - width = Math.max(width, options.minWidth || gridSize) - height = Math.max(height, options.minHeight || gridSize) - width = Math.min(width, options.maxWidth || Infinity) - height = Math.min(height, options.maxHeight || Infinity) - - if (options.preserveAspectRatio) { - const candidateWidth = - (currentBBox.width * height) / currentBBox.height - const candidateHeight = - (currentBBox.height * width) / currentBBox.width - - if (width < candidateWidth) { - height = candidateHeight - } else { - width = candidateWidth - } - } - - const relativeDirection = data.relativeDirection - if ( - options.allowReverse && - (rawWidth <= -width || rawHeight <= -height) - ) { - let reverted: Node.ResizeDirection - - if (relativeDirection === 'left') { - if (rawWidth <= -width) { - reverted = 'right' - } - } else if (relativeDirection === 'right') { - if (rawWidth <= -width) { - reverted = 'left' - } - } else if (relativeDirection === 'top') { - if (rawHeight <= -height) { - reverted = 'bottom' - } - } else if (relativeDirection === 'bottom') { - if (rawHeight <= -height) { - reverted = 'top' - } - } else if (relativeDirection === 'top-left') { - if (rawWidth <= -width && rawHeight <= -height) { - reverted = 'bottom-right' - } else if (rawWidth <= -width) { - reverted = 'top-right' - } else if (rawHeight <= -height) { - reverted = 'bottom-left' - } - } else if (relativeDirection === 'top-right') { - if (rawWidth <= -width && rawHeight <= -height) { - reverted = 'bottom-left' - } else if (rawWidth <= -width) { - reverted = 'top-left' - } else if (rawHeight <= -height) { - reverted = 'bottom-right' - } - } else if (relativeDirection === 'bottom-left') { - if (rawWidth <= -width && rawHeight <= -height) { - reverted = 'top-right' - } else if (rawWidth <= -width) { - reverted = 'bottom-right' - } else if (rawHeight <= -height) { - reverted = 'top-left' - } - } else if (relativeDirection === 'bottom-right') { - if (rawWidth <= -width && rawHeight <= -height) { - reverted = 'top-left' - } else if (rawWidth <= -width) { - reverted = 'bottom-left' - } else if (rawHeight <= -height) { - reverted = 'top-right' - } - } - - const revertedDir = reverted! - this.stopHandle() - const $handle = this.$container.find( - `.${this.resizeClassName}[data-position="${revertedDir}"]`, - ) - this.startHandle($handle[0]) - this.prepareResizing(evt, revertedDir) - this.onMouseMove(evt) - } - - if (currentBBox.width !== width || currentBBox.height !== height) { - const resizeOptions: Node.ResizeOptions = { - ui: true, - direction: data.direction, - relativeDirection: data.relativeDirection, - trueDirection: data.trueDirection, - minWidth: options.minWidth!, - minHeight: options.minHeight!, - maxWidth: options.maxWidth!, - maxHeight: options.maxHeight!, - preserveAspectRatio: options.preserveAspectRatio === true, - } - node.resize(width, height, resizeOptions) - notify('node:resizing', evt, view) - } - notify('node:resize:mousemove', evt, view) - } else if (data.action === 'rotating') { - data = data as EventData.Rotating - if (!data.rotated) { - if (view) { - view.addClass('node-rotating') - notify('node:rotate', evt, view) - } - data.rotated = true - } - - const currentAngle = node.getAngle() - const theta = data.start - Point.create(pos).theta(data.center) - let target = data.angle + theta - if (options.rotateGrid) { - target = Util.snapToGrid(target, options.rotateGrid) - } - - if (currentAngle !== target) { - node.rotate(target, { absolute: true }) - notify('node:rotating', evt, view) - } - notify('node:rotate:mousemove', evt, view) - } - } - } - - protected onMouseUp(evt: JQuery.MouseUpEvent) { - const view = this.graph.findViewByCell(this.node) as NodeView - const data = this.getEventData(evt) - if (data.action) { - this.stopAction(evt) - this.model.stopBatch(data.action === 'resizing' ? 'resize' : 'rotate', { - cid: this.cid, - }) - - if (data.action === 'resizing') { - notify('node:resize:mouseup', evt, view) - } else if (data.action === 'rotating') { - notify('node:rotate:mouseup', evt, view) - } - } - } - - protected startHandle(handle?: Element | null) { - this.handle = handle || null - this.$container.addClass(`${this.containerClassName}-active`) - if (handle) { - this.$(handle).addClass(`${this.containerClassName}-active-handle`) - - const pos = handle.getAttribute('data-position') as Node.ResizeDirection - if (pos) { - const dir = Private.DIRECTIONS[Private.POSITIONS.indexOf(pos)] - this.$container.addClass(`${this.containerClassName}-cursor-${dir}`) - } - } - } - - protected stopHandle() { - this.$container.removeClass(`${this.containerClassName}-active`) - - if (this.handle) { - this.$(this.handle).removeClass( - `${this.containerClassName}-active-handle`, - ) - - const pos = this.handle.getAttribute( - 'data-position', - ) as Node.ResizeDirection - if (pos) { - const dir = Private.DIRECTIONS[Private.POSITIONS.indexOf(pos)] - this.$container.removeClass(`${this.containerClassName}-cursor-${dir}`) - } - - this.handle = null - } - } - - protected startAction(evt: JQuery.MouseDownEvent) { - this.startHandle(evt.target) - this.graph.view.undelegateEvents() - this.delegateDocumentEvents(Private.documentEvents, evt.data) - } - - protected stopAction(evt: JQuery.MouseUpEvent) { - this.stopHandle() - this.undelegateDocumentEvents() - this.graph.view.delegateEvents() - - const view = this.graph.findViewByCell(this.node) as NodeView - const data = this.getEventData(evt) - - if (view) { - view.removeClass(`node-${data.action}`) - if (data.action === 'resizing' && data.resized) { - notify('node:resized', evt, view) - } else if (data.action === 'rotating' && data.rotated) { - notify('node:rotated', evt, view) - } - } - } -} - -export namespace Transform { - export type Direction = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' - - export interface Options extends Widget.Options { - className?: string - - minWidth?: number - maxWidth?: number - minHeight?: number - maxHeight?: number - resizable?: boolean - - rotatable?: boolean - rotateGrid?: number - orthogonalResizing?: boolean - restrictedResizing?: boolean | number - autoScrollOnResizing?: boolean - - /** - * Set to `true` if you want the resizing to preserve the - * aspect ratio of the node. Default is `false`. - */ - preserveAspectRatio?: boolean - /** - * Reaching the minimum width or height is whether to allow control points to reverse - */ - allowReverse?: boolean - } -} - -namespace Private { - export const NODE_CLS = 'has-widget-transform' - export const DIRECTIONS = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'] - export const POSITIONS: Node.ResizeDirection[] = [ - 'top-left', - 'top', - 'top-right', - 'right', - 'bottom-right', - 'bottom', - 'bottom-left', - 'left', - ] - - export const documentEvents = { - mousemove: 'onMouseMove', - touchmove: 'onMouseMove', - mouseup: 'onMouseUp', - touchend: 'onMouseUp', - } - - export const defaultOptions: Transform.Options = { - minWidth: 0, - minHeight: 0, - maxWidth: Infinity, - maxHeight: Infinity, - rotateGrid: 15, - rotatable: true, - preserveAspectRatio: false, - orthogonalResizing: true, - restrictedResizing: false, - autoScrollOnResizing: true, - allowReverse: true, - } -} - -namespace EventData { - export interface Resizing { - action: 'resizing' - selector: 'bottomLeft' | 'bottomRight' | 'topRight' | 'topLeft' - direction: Node.ResizeDirection - trueDirection: Node.ResizeDirection - relativeDirection: Node.ResizeDirection - resizeX: number - resizeY: number - angle: number - resized?: boolean - } - - export interface Rotating { - action: 'rotating' - center: Point.PointLike - angle: number - start: number - rotated?: boolean - } -} diff --git a/packages/x6/src/addon/transform/util.ts b/packages/x6/src/addon/transform/util.ts deleted file mode 100644 index bbb5f3c5f3b..00000000000 --- a/packages/x6/src/addon/transform/util.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { KeyValue } from '../../types' -import { NodeView } from '../../view/node' - -export function notify( - name: string, - evt: JQuery.TriggeredEvent, - view: NodeView, - args: KeyValue = {}, -) { - if (view) { - const graph = view.graph - const e = graph.view.normalizeEvent(evt) as JQuery.MouseDownEvent - const localPoint = graph.snapToGrid(e.clientX, e.clientY) - - view.notify(name, { - e, - view, - node: view.cell, - cell: view.cell, - x: localPoint.x, - y: localPoint.y, - ...args, - }) - } -} diff --git a/packages/x6/src/common/algorithm/dijkstra.ts b/packages/x6/src/common/algorithm/dijkstra.ts deleted file mode 100644 index ad65e1f997f..00000000000 --- a/packages/x6/src/common/algorithm/dijkstra.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { PriorityQueue } from './priorityqueue' - -export namespace Dijkstra { - export type AdjacencyList = { [key: string]: string[] } - export type Weight = (u: string, v: string) => number - - export function run( - adjacencyList: AdjacencyList, - source: string, - weight: Weight = (u, v) => 1, // eslint-disable-line - ) { - const dist: { [key: string]: number } = {} - const previous: { [key: string]: string } = {} - const scanned: { [key: string]: boolean } = {} - const queue = new PriorityQueue() - - dist[source] = 0 - - Object.keys(adjacencyList).forEach((v) => { - if (v !== source) { - dist[v] = Infinity - } - queue.insert(dist[v], v, v) - }) - - while (!queue.isEmpty()) { - const u = queue.remove()! - scanned[u] = true - - const neighbours = adjacencyList[u] || [] - for (let i = 0; i < neighbours.length; i += 1) { - const v = neighbours[i] - if (!scanned[v]) { - const alt = dist[u] + weight(u, v) - if (alt < dist[v]) { - dist[v] = alt - previous[v] = u - queue.updatePriority(v, alt) - } - } - } - } - - return previous - } -} diff --git a/packages/x6/src/common/algorithm/index.ts b/packages/x6/src/common/algorithm/index.ts deleted file mode 100644 index ab0d9537408..00000000000 --- a/packages/x6/src/common/algorithm/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './priorityqueue' -export * from './dijkstra' diff --git a/packages/x6/src/common/algorithm/priorityqueue.ts b/packages/x6/src/common/algorithm/priorityqueue.ts deleted file mode 100644 index bae1c1e6a37..00000000000 --- a/packages/x6/src/common/algorithm/priorityqueue.ts +++ /dev/null @@ -1,207 +0,0 @@ -/** - * An implementation of the Priority Queue abstract data type. - * - * @see: http://en.wikipedia.org/wiki/Priority_queue - * - * It is like a normal stack or queue, but where each item has assigned a - * priority (a number). Items with higher priority are served before items - * with lower priority. This implementation uses binary heap as an internal - * representation of the queue. The time complexity of all the methods is as - * follows: - * - * - create: `O(n)` - * - insert: `O(log n)` - * - remove: `O(log n)` - * - peek: `O(1)` - * - isEmpty: `O(1)` - * - peekPriority: `O(1)` - */ -export class PriorityQueue { - protected readonly comparator: PriorityQueue.Comparator - protected index: { [key: string]: number } - protected data: PriorityQueue.Data - - constructor(options: PriorityQueue.Options = {}) { - this.comparator = options.comparator || PriorityQueue.defaultComparator - this.index = {} - this.data = options.data || [] - this.heapify() - } - - /** - * Returns `true` if the priority queue is empty, `false` otherwise. - */ - isEmpty() { - return this.data.length === 0 - } - - /** - * Inserts a value with priority to the queue. Optionally pass a unique - * id of this item. Passing unique IDs for each item you insert allows - * you to use the `updatePriority()` operation. - * @param priority - * @param value - * @param id - */ - insert(priority: number, value: T, id?: string) { - const item: PriorityQueue.DataItem = { priority, value } - const index = this.data.length - 1 - if (id) { - item.id = id - this.index[id] = index - } - this.data.push(item) - this.bubbleUp(index) - return this - } - - /** - * Returns the value of an item with the highest priority. - */ - peek() { - return this.data[0] ? this.data[0].value : null - } - - /** - * Returns the highest priority in the queue. - */ - peekPriority() { - return this.data[0] ? this.data[0].priority : null - } - - updatePriority(id: string, priority: number) { - const index = this.index[id] - if (typeof index === 'undefined') { - throw new Error(`Node with id '${id}' was not found in the heap.`) - } - - const data = this.data - const oldPriority = data[index].priority - const comp = this.comparator(priority, oldPriority) - if (comp < 0) { - data[index].priority = priority - this.bubbleUp(index) - } else if (comp > 0) { - data[index].priority = priority - this.bubbleDown(index) - } - } - - /** - * Removes the item with the highest priority from the queue - * - * @returns The value of the removed item. - */ - remove() { - const data = this.data - const peek = data[0] - const last = data.pop()! - delete this.index[data.length] - - if (data.length > 0) { - data[0] = last - if (last.id) { - this.index[last.id] = 0 - } - this.bubbleDown(0) - } - - return peek ? peek.value : null - } - - protected heapify() { - for (let i = 0; i < this.data.length; i += 1) { - this.bubbleUp(i) - } - } - - protected bubbleUp(index: number) { - const data = this.data - let tmp - let parent: number - let current = index - - while (current > 0) { - parent = (current - 1) >>> 1 - if (this.comparator(data[current].priority, data[parent].priority) < 0) { - tmp = data[parent] - data[parent] = data[current] - let id = data[current].id - if (id != null) { - this.index[id] = parent - } - data[current] = tmp - id = data[current].id - if (id != null) { - this.index[id] = current - } - current = parent - } else { - break - } - } - } - - protected bubbleDown(index: number) { - const data = this.data - const last = data.length - 1 - let current = index - - // eslint-disable-next-line - while (true) { - const left = (current << 1) + 1 - const right = left + 1 - let minIndex = current - - if ( - left <= last && - this.comparator(data[left].priority, data[minIndex].priority) < 0 - ) { - minIndex = left - } - if ( - right <= last && - this.comparator(data[right].priority, data[minIndex].priority) < 0 - ) { - minIndex = right - } - - if (minIndex !== current) { - const tmp = data[minIndex] - data[minIndex] = data[current] - let id = data[current].id - if (id != null) { - this.index[id] = minIndex - } - data[current] = tmp - id = data[current].id - if (id != null) { - this.index[id] = current - } - current = minIndex - } else { - break - } - } - } -} - -export namespace PriorityQueue { - export interface Options { - comparator?: Comparator - data?: Data - } - - export type Data = DataItem[] - - export interface DataItem { - priority: number - value: T - id?: string - } - - export type Comparator = (a: number, b: number) => number -} -export namespace PriorityQueue { - export const defaultComparator: Comparator = (a, b) => a - b -} diff --git a/packages/x6/src/common/animation/index.ts b/packages/x6/src/common/animation/index.ts deleted file mode 100644 index 43bceebae1c..00000000000 --- a/packages/x6/src/common/animation/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './timing' -export * from './interp' diff --git a/packages/x6/src/common/animation/interp.ts b/packages/x6/src/common/animation/interp.ts deleted file mode 100644 index b83a0b4e566..00000000000 --- a/packages/x6/src/common/animation/interp.ts +++ /dev/null @@ -1,62 +0,0 @@ -export namespace Interp { - export type Definition = (from: T, to: T) => (time: number) => T -} - -export namespace Interp { - export const number: Definition = (a, b) => { - const d = b - a - return (t: number) => { - return a + d * t - } - } - - export const object: Definition<{ [key: string]: number }> = (a, b) => { - const keys = Object.keys(a) - return (t) => { - const ret: { [key: string]: number } = {} - for (let i = keys.length - 1; i !== -1; i -= 1) { - const key = keys[i] - ret[key] = a[key] + (b[key] - a[key]) * t - } - return ret - } - } - - export const unit: Definition = (a, b) => { - const reg = /(-?[0-9]*.[0-9]*)(px|em|cm|mm|in|pt|pc|%)/ - const ma = reg.exec(a) - const mb = reg.exec(b) - - const pb = mb ? mb[1] : '' - const aa = ma ? +ma[1] : 0 - const bb = mb ? +mb[1] : 0 - - const index = pb.indexOf('.') - const precision = index > 0 ? pb[1].length - index - 1 : 0 - - const d = bb - aa - const u = ma ? ma[2] : '' - - return (t) => { - return (aa + d * t).toFixed(precision) + u - } - } - - export const color: Definition = (a, b) => { - const ca = parseInt(a.slice(1), 16) - const cb = parseInt(b.slice(1), 16) - const ra = ca & 0x0000ff - const rd = (cb & 0x0000ff) - ra - const ga = ca & 0x00ff00 - const gd = (cb & 0x00ff00) - ga - const ba = ca & 0xff0000 - const bd = (cb & 0xff0000) - ba - - return (t) => { - const r = (ra + rd * t) & 0x000000ff - const g = (ga + gd * t) & 0x0000ff00 - const b = (ba + bd * t) & 0x00ff0000 - return `#${((1 << 24) | r | g | b).toString(16).slice(1)}` - } - } -} diff --git a/packages/x6/src/common/animation/timing.ts b/packages/x6/src/common/animation/timing.ts deleted file mode 100644 index 343cca12bbe..00000000000 --- a/packages/x6/src/common/animation/timing.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { FunctionKeys } from 'utility-types' - -export namespace Timing { - export type Definition = (t: number) => number - export type Names = FunctionKeys -} - -export namespace Timing { - export const linear: Definition = (t) => t - export const quad: Definition = (t) => t * t - export const cubic: Definition = (t) => t * t * t - export const inout: Definition = (t) => { - if (t <= 0) { - return 0 - } - - if (t >= 1) { - return 1 - } - - const t2 = t * t - const t3 = t2 * t - return 4 * (t < 0.5 ? t3 : 3 * (t - t2) + t3 - 0.75) - } - - export const exponential: Definition = (t) => { - return Math.pow(2, 10 * (t - 1)) // eslint-disable-line - } - - export const bounce = ((t: number) => { - // eslint-disable-next-line - for (let a = 0, b = 1; 1; a += b, b /= 2) { - if (t >= (7 - 4 * a) / 11) { - const q = (11 - 6 * a - 11 * t) / 4 - return -q * q + b * b - } - } - }) as Definition -} - -export namespace Timing { - export const decorators = { - reverse(f: Definition): Definition { - return (t) => 1 - f(1 - t) - }, - reflect(f: Definition): Definition { - return (t) => 0.5 * (t < 0.5 ? f(2 * t) : 2 - f(2 - 2 * t)) - }, - clamp(f: Definition, n = 0, x = 1): Definition { - return (t) => { - const r = f(t) - return r < n ? n : r > x ? x : r - } - }, - back(s = 1.70158): Definition { - return (t) => t * t * ((s + 1) * t - s) - }, - elastic(x = 1.5): Definition { - return (t) => - Math.pow(2, 10 * (t - 1)) * Math.cos(((20 * Math.PI * x) / 3) * t) // eslint-disable-line - }, - } -} - -export namespace Timing { - // Slight acceleration from zero to full speed - export function easeInSine(t: number) { - return -1 * Math.cos(t * (Math.PI / 2)) + 1 - } - - // Slight deceleration at the end - export function easeOutSine(t: number) { - return Math.sin(t * (Math.PI / 2)) - } - - // Slight acceleration at beginning and slight deceleration at end - export function easeInOutSine(t: number) { - return -0.5 * (Math.cos(Math.PI * t) - 1) - } - - // Accelerating from zero velocity - export function easeInQuad(t: number) { - return t * t - } - - // Decelerating to zero velocity - export function easeOutQuad(t: number) { - return t * (2 - t) - } - - // Acceleration until halfway, then deceleration - export function easeInOutQuad(t: number) { - return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t - } - - // Accelerating from zero velocity - export function easeInCubic(t: number) { - return t * t * t - } - - // Decelerating to zero velocity - export function easeOutCubic(t: number) { - const t1 = t - 1 - return t1 * t1 * t1 + 1 - } - - // Acceleration until halfway, then deceleration - export function easeInOutCubic(t: number) { - return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1 - } - - // Accelerating from zero velocity - export function easeInQuart(t: number) { - return t * t * t * t - } - - // Decelerating to zero velocity - export function easeOutQuart(t: number) { - const t1 = t - 1 - return 1 - t1 * t1 * t1 * t1 - } - - // Acceleration until halfway, then deceleration - export function easeInOutQuart(t: number) { - const t1 = t - 1 - return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * t1 * t1 * t1 * t1 - } - - // Accelerating from zero velocity - export function easeInQuint(t: number) { - return t * t * t * t * t - } - - // Decelerating to zero velocity - export function easeOutQuint(t: number) { - const t1 = t - 1 - return 1 + t1 * t1 * t1 * t1 * t1 - } - - // Acceleration until halfway, then deceleration - export function easeInOutQuint(t: number) { - const t1 = t - 1 - return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * t1 * t1 * t1 * t1 * t1 - } - - // Accelerate exponentially until finish - export function easeInExpo(t: number) { - if (t === 0) { - return 0 - } - - return Math.pow(2, 10 * (t - 1)) // eslint-disable-line - } - - // Initial exponential acceleration slowing to stop - export function easeOutExpo(t: number) { - if (t === 1) { - return 1 - } - - return -Math.pow(2, -10 * t) + 1 // eslint-disable-line - } - - // Exponential acceleration and deceleration - export function easeInOutExpo(t: number) { - if (t === 0 || t === 1) { - return t - } - - const scaledTime = t * 2 - const scaledTime1 = scaledTime - 1 - - if (scaledTime < 1) { - return 0.5 * Math.pow(2, 10 * scaledTime1) // eslint-disable-line - } - - return 0.5 * (-Math.pow(2, -10 * scaledTime1) + 2) // eslint-disable-line - } - - // Increasing velocity until stop - export function easeInCirc(t: number) { - const scaledTime = t / 1 - return -1 * (Math.sqrt(1 - scaledTime * t) - 1) - } - - // Start fast, decreasing velocity until stop - export function easeOutCirc(t: number) { - const t1 = t - 1 - return Math.sqrt(1 - t1 * t1) - } - - // Fast increase in velocity, fast decrease in velocity - export function easeInOutCirc(t: number) { - const scaledTime = t * 2 - const scaledTime1 = scaledTime - 2 - - if (scaledTime < 1) { - return -0.5 * (Math.sqrt(1 - scaledTime * scaledTime) - 1) - } - - return 0.5 * (Math.sqrt(1 - scaledTime1 * scaledTime1) + 1) - } - - // Slow movement backwards then fast snap to finish - export function easeInBack(t: number, magnitude = 1.70158) { - return t * t * ((magnitude + 1) * t - magnitude) - } - - // Fast snap to backwards point then slow resolve to finish - export function easeOutBack(t: number, magnitude = 1.70158) { - const scaledTime = t / 1 - 1 - - return ( - scaledTime * scaledTime * ((magnitude + 1) * scaledTime + magnitude) + 1 - ) - } - - // Slow movement backwards, fast snap to past finish, slow resolve to finish - export function easeInOutBack(t: number, magnitude = 1.70158) { - const scaledTime = t * 2 - const scaledTime2 = scaledTime - 2 - - const s = magnitude * 1.525 - - if (scaledTime < 1) { - return 0.5 * scaledTime * scaledTime * ((s + 1) * scaledTime - s) - } - - return 0.5 * (scaledTime2 * scaledTime2 * ((s + 1) * scaledTime2 + s) + 2) - } - - // Bounces slowly then quickly to finish - export function easeInElastic(t: number, magnitude = 0.7) { - if (t === 0 || t === 1) { - return t - } - - const scaledTime = t / 1 - const scaledTime1 = scaledTime - 1 - - const p = 1 - magnitude - const s = (p / (2 * Math.PI)) * Math.asin(1) - - return -( - Math.pow(2, 10 * scaledTime1) * // eslint-disable-line - Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p) - ) - } - - // Fast acceleration, bounces to zero - export function easeOutElastic(t: number, magnitude = 0.7) { - const p = 1 - magnitude - const scaledTime = t * 2 - - if (t === 0 || t === 1) { - return t - } - - const s = (p / (2 * Math.PI)) * Math.asin(1) - return ( - Math.pow(2, -10 * scaledTime) * // eslint-disable-line - Math.sin(((scaledTime - s) * (2 * Math.PI)) / p) + - 1 - ) - } - - // Slow start and end, two bounces sandwich a fast motion - export function easeInOutElastic(t: number, magnitude = 0.65) { - const p = 1 - magnitude - - if (t === 0 || t === 1) { - return t - } - - const scaledTime = t * 2 - const scaledTime1 = scaledTime - 1 - - const s = (p / (2 * Math.PI)) * Math.asin(1) - - if (scaledTime < 1) { - return ( - -0.5 * - (Math.pow(2, 10 * scaledTime1) * // eslint-disable-line - Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p)) - ) - } - - return ( - Math.pow(2, -10 * scaledTime1) * // eslint-disable-line - Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p) * - 0.5 + - 1 - ) - } - - // Bounce to completion - export function easeOutBounce(t: number) { - const scaledTime = t / 1 - - if (scaledTime < 1 / 2.75) { - return 7.5625 * scaledTime * scaledTime - } - if (scaledTime < 2 / 2.75) { - const scaledTime2 = scaledTime - 1.5 / 2.75 - return 7.5625 * scaledTime2 * scaledTime2 + 0.75 - } - if (scaledTime < 2.5 / 2.75) { - const scaledTime2 = scaledTime - 2.25 / 2.75 - return 7.5625 * scaledTime2 * scaledTime2 + 0.9375 - } - { - const scaledTime2 = scaledTime - 2.625 / 2.75 - return 7.5625 * scaledTime2 * scaledTime2 + 0.984375 - } - } - - // Bounce increasing in velocity until completion - export function easeInBounce(t: number) { - return 1 - easeOutBounce(1 - t) - } - - // Bounce in and bounce out - export function easeInOutBounce(t: number) { - if (t < 0.5) { - return easeInBounce(t * 2) * 0.5 - } - - return easeOutBounce(t * 2 - 1) * 0.5 + 0.5 - } -} diff --git a/packages/x6/src/common/basecoat.ts b/packages/x6/src/common/basecoat.ts deleted file mode 100644 index 5c10657fe67..00000000000 --- a/packages/x6/src/common/basecoat.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ObjectExt } from '../util' -import { Events } from './events' -import { Disposable } from './disposable' - -export class Basecoat extends Events {} - -export interface Basecoat extends Disposable {} - -export namespace Basecoat { - export const dispose = Disposable.dispose -} - -ObjectExt.applyMixins(Basecoat, Disposable) diff --git a/packages/x6/src/common/color.test.ts b/packages/x6/src/common/color.test.ts deleted file mode 100644 index 9daee0c9c57..00000000000 --- a/packages/x6/src/common/color.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Color } from './color' - -describe('Color', () => { - describe('#constructor', () => { - it('shoud create instance from named color', () => { - const black = new Color(Color.named.black) - expect(black.r).toBe(0) - expect(black.g).toBe(0) - expect(black.b).toBe(0) - expect(black.a).toBe(1) - - const white = new Color(Color.named.white) - expect(white.r).toBe(255) - expect(white.g).toBe(255) - expect(white.b).toBe(255) - expect(black.a).toBe(1) - }) - - it('should create instance from hex color', () => { - const black = new Color('#000000') - expect(black.r).toBe(0) - expect(black.g).toBe(0) - expect(black.b).toBe(0) - expect(black.a).toBe(1) - - const white = new Color('#fff') - expect(white.r).toBe(255) - expect(white.g).toBe(255) - expect(white.b).toBe(255) - expect(black.a).toBe(1) - }) - - it('should create instance from rgba array', () => { - const black = new Color([0, 0, 0, 1]) - expect(black.r).toBe(0) - expect(black.g).toBe(0) - expect(black.b).toBe(0) - expect(black.a).toBe(1) - }) - - it('should create instance from rgba values', () => { - const black = new Color(-1, 0, 300, 1) - expect(black.r).toBe(0) - expect(black.g).toBe(0) - expect(black.b).toBe(255) - expect(black.a).toBe(1) - }) - }) - - describe('#randomHex', () => { - it('shoud return valid random hex value', () => { - expect(Color.randomHex()).toMatch(/^#[0-9A-F]{6}/) - }) - }) - - describe('#randomRGBA', () => { - it('shoud generate an rgba color string', () => { - expect(Color.randomRGBA().startsWith('rgba')).toBe(true) - expect(Color.randomRGBA(true).startsWith('rgba')).toBe(true) - }) - }) - - describe('#invert', () => { - it('shoud return invert value of a color value', () => { - expect(Color.invert('#ffffff', false)).toBe('#000000') - expect(Color.invert('#000', false)).toBe('#ffffff') - expect(Color.invert('234567', false)).toBe('dcba98') - }) - - it('decide font color in white or black depending on background color', () => { - expect(Color.invert('#121212', true)).toBe('#ffffff') - expect(Color.invert('#feeade', true)).toBe('#000000') - }) - - it('shoud throw exception with invalid color value', () => { - expect(() => { - Color.invert('#abcd', false) - }).toThrowError('Invalid hex color.') - }) - }) -}) diff --git a/packages/x6/src/common/color.ts b/packages/x6/src/common/color.ts deleted file mode 100644 index 7c72fe15bbd..00000000000 --- a/packages/x6/src/common/color.ts +++ /dev/null @@ -1,514 +0,0 @@ -import { NumberExt } from '../util/number' - -export class Color { - public r: number - public g: number - public b: number - public a: number - - constructor() - constructor(color: string) - constructor(color: Color.RGBA) - constructor(r: number, g: number, b: number, a?: number) - constructor( - color?: - | number - | string - | Color.RGBA - | { - r: number - g: number - b: number - a?: number - }, - g?: number, - b?: number, - a?: number, - ) { - if (color == null) { - return this.set(255, 255, 255, 1) - } - - if (typeof color === 'number') { - return this.set(color, g as number, b as number, a) - } - - if (typeof color === 'string') { - return Color.fromString(color) || this - } - - if (Array.isArray(color)) { - return this.set(color) - } - - this.set(color.r, color.g, color.b, color.a == null ? 1 : color.a) - } - - blend(start: Color, end: Color, weight: number) { - this.set( - start.r + (end.r - start.r) * weight, - start.g + (end.g - start.g) * weight, - start.b + (end.b - start.b) * weight, - start.a + (end.a - start.a) * weight, - ) - } - - lighten(amount: number) { - const rgba = Color.lighten(this.toArray(), amount) - this.r = rgba[0] - this.g = rgba[1] - this.b = rgba[2] - this.a = rgba[3] - } - - darken(amount: number) { - this.lighten(-amount) - } - - set(rgba: Color.RGBA): this - set(r: number, g: number, b: number, a?: number): this - set(arg0: number | Color.RGBA, arg1?: number, arg2?: number, arg3?: number) { - const r = Array.isArray(arg0) ? arg0[0] : (arg0 as number) - const g = Array.isArray(arg0) ? arg0[1] : (arg1 as number) - const b = Array.isArray(arg0) ? arg0[2] : (arg2 as number) - const a = Array.isArray(arg0) ? arg0[3] : (arg3 as number) - this.r = Math.round(NumberExt.clamp(r, 0, 255)) - this.g = Math.round(NumberExt.clamp(g, 0, 255)) - this.b = Math.round(NumberExt.clamp(b, 0, 255)) - this.a = a == null ? 1 : NumberExt.clamp(a, 0, 1) - return this - } - - toHex() { - const hex = ['r', 'g', 'b'].map((key: 'r' | 'g' | 'b') => { - const str = this[key].toString(16) - return str.length < 2 ? `0${str}` : str - }) - return `#${hex.join('')}` - } - - toRGBA(): Color.RGBA { - return this.toArray() - } - - toHSLA(): Color.HSLA { - return Color.rgba2hsla(this.r, this.g, this.b, this.a) - } - - toCSS(ignoreAlpha?: boolean) { - const rgb = `${this.r},${this.g},${this.b},` - return ignoreAlpha ? `rgb(${rgb})` : `rgba(${rgb},${this.a})` - } - - toGrey() { - return Color.makeGrey(Math.round((this.r + this.g + this.b) / 3), this.a) - } - - toArray(): Color.RGBA { - return [this.r, this.g, this.b, this.a] - } - - toString() { - return this.toCSS() - } -} - -export namespace Color { - export type RGBA = [number, number, number, number] - export type HSLA = [number, number, number, number] - - export function fromArray(arr: RGBA) { - return new Color(arr) - } - - export function fromHex(color: string) { - return new Color([...hex2rgb(color), 1]) - } - - export function fromRGBA(color: string) { - const matches = color.toLowerCase().match(/^rgba?\(([\s.,0-9]+)\)/) - if (matches) { - const arr = matches[1].split(/\s*,\s*/).map((v) => parseInt(v, 10)) - return new Color(arr as Color.RGBA) - } - - return null - } - - function hue2rgb(m1: number, m2: number, h: number) { - if (h < 0) { - ++h // eslint-disable-line - } - if (h > 1) { - --h // eslint-disable-line - } - - const h6 = 6 * h - if (h6 < 1) { - return m1 + (m2 - m1) * h6 - } - if (2 * h < 1) { - return m2 - } - if (3 * h < 2) { - return m1 + (m2 - m1) * (2 / 3 - h) * 6 - } - return m1 - } - - export function fromHSLA(color: string) { - const matches = color.toLowerCase().match(/^hsla?\(([\s.,0-9]+)\)/) - if (matches) { - const arr = matches[2].split(/\s*,\s*/) - const h = (((parseFloat(arr[0]) % 360) + 360) % 360) / 360 - const s = parseFloat(arr[1]) / 100 - const l = parseFloat(arr[2]) / 100 - const a = arr[3] == null ? 1 : parseInt(arr[3], 10) - return new Color(hsla2rgba(h, s, l, a)) - } - - return null - } - - export function fromString(color: string) { - if (color.startsWith('#')) { - return fromHex(color) - } - - if (color.startsWith('rgb')) { - return fromRGBA(color) - } - - const preset = (Color.named as any)[color] - if (preset) { - return fromHex(preset) - } - - return fromHSLA(color) - } - - export function makeGrey(g: number, a: number) { - return Color.fromArray([g, g, g, a]) - } - - export function rgba2hsla(rgba: RGBA): HSLA - export function rgba2hsla(r: number, g: number, b: number, a?: number): HSLA - export function rgba2hsla( - arg0: number | RGBA, - arg1?: number, - arg2?: number, - arg3?: number, - ): HSLA { - const r = Array.isArray(arg0) ? arg0[0] : (arg0 as number) - const g = Array.isArray(arg0) ? arg0[1] : (arg1 as number) - const b = Array.isArray(arg0) ? arg0[2] : (arg2 as number) - const a = Array.isArray(arg0) ? arg0[3] : (arg3 as number) - - const max = Math.max(r, g, b) - const min = Math.min(r, g, b) - const l = (max + min) / 2 - - let h = 0 - let s = 0 - - if (min !== max) { - const d = max - min - s = l > 0.5 ? d / (2 - max - min) : d / (max + min) - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0) - break - case g: - h = (b - r) / d + 2 - break - case b: - h = (r - g) / d + 4 - break - default: - break - } - h /= 6 - } - - return [h, s, l, a == null ? 1 : a] - } - - export function hsla2rgba(hsla: HSLA): RGBA - export function hsla2rgba(h: number, s: number, l: number, a?: number): RGBA - export function hsla2rgba( - arg0: number | HSLA, - arg1?: number, - arg2?: number, - arg3?: number, - ): RGBA { - const h = Array.isArray(arg0) ? arg0[0] : (arg0 as number) - const s = Array.isArray(arg0) ? arg0[1] : (arg1 as number) - const l = Array.isArray(arg0) ? arg0[2] : (arg2 as number) - const a = Array.isArray(arg0) ? arg0[3] : (arg3 as number) - - const m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s - const m1 = 2 * l - m2 - return [ - hue2rgb(m1, m2, h + 1 / 3) * 256, - hue2rgb(m1, m2, h) * 256, - hue2rgb(m1, m2, h - 1 / 3) * 256, - a == null ? 1 : a, - ] - } - - export function random(ignoreAlpha?: boolean) { - return new Color( - Math.round(Math.random() * 256), - Math.round(Math.random() * 256), - Math.round(Math.random() * 256), - ignoreAlpha ? undefined : parseFloat(Math.random().toFixed(2)), - ) - } - - export function randomHex() { - const letters = '0123456789ABCDEF' - let color = '#' - for (let i = 0; i < 6; i += 1) { - color += letters[Math.floor(Math.random() * 16)] - } - return color - } - - export function randomRGBA(ignoreAlpha?: boolean) { - return random(ignoreAlpha).toString() - } - - export function invert(rgba: RGBA, bw: boolean): RGBA - export function invert(hex: string, bw: boolean): string - export function invert(color: string | RGBA, bw: boolean) { - if (typeof color === 'string') { - const pound = color[0] === '#' - const [r, g, b] = hex2rgb(color) - if (bw) { - // http://stackoverflow.com/a/3943023/112731 - return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? '#000000' : '#ffffff' - } - - return `${pound ? '#' : ''}${rgb2hex(255 - r, 255 - g, 255 - b)}` - } - - const r = color[0] - const g = color[1] - const b = color[2] - const a = color[3] - - if (bw) { - return r * 0.299 + g * 0.587 + b * 0.114 > 186 - ? [0, 0, 0, a] - : [255, 255, 255, a] - } - - return [255 - r, 255 - g, 255 - b, a] - } - - function hex2rgb(hex: string): [number, number, number] { - const color = hex.indexOf('#') === 0 ? hex : `#${hex}` - let val = Number(`0x${color.substr(1)}`) - if (!(color.length === 4 || color.length === 7) || Number.isNaN(val)) { - throw new Error('Invalid hex color.') - } - - const bits = color.length === 4 ? 4 : 8 - const mask = (1 << bits) - 1 - const bgr = ['b', 'g', 'r'].map(() => { - const c = val & mask - val >>= bits - return bits === 4 ? 17 * c : c - }) - - return [bgr[2], bgr[1], bgr[0]] - } - - function rgb2hex(r: number, g: number, b: number) { - const pad = (hex: string) => (hex.length < 2 ? `0${hex}` : hex) - return `${pad(r.toString(16))}${pad(g.toString(16))}${pad(b.toString(16))}` - } - - export function lighten(rgba: RGBA, amt: number): RGBA - export function lighten(hex: string, amt: number): string - export function lighten(color: RGBA | string, amt: number) { - return lum(color, amt) - } - - export function darken(rgba: RGBA, amt: number): RGBA - export function darken(hex: string, amt: number): string - export function darken(color: RGBA | string, amt: number) { - return lum(color, -amt) - } - - function lum(color: RGBA | string, amt: number): RGBA | string { - if (typeof color === 'string') { - const pound = color[0] === '#' - const num = parseInt(pound ? color.substr(1) : color, 16) - const r = NumberExt.clamp((num >> 16) + amt, 0, 255) - const g = NumberExt.clamp(((num >> 8) & 0x00ff) + amt, 0, 255) - const b = NumberExt.clamp((num & 0x0000ff) + amt, 0, 255) - - return `${pound ? '#' : ''}${(b | (g << 8) | (r << 16)).toString(16)}` - } - - const hex = rgb2hex(color[0], color[1], color[2]) - const arr = hex2rgb(lum(hex, amt) as string) - - return [arr[0], arr[1], arr[2], color[3]] - } -} - -export namespace Color { - export const named = { - aliceblue: '#f0f8ff', - antiquewhite: '#faebd7', - aqua: '#00ffff', - aquamarine: '#7fffd4', - azure: '#f0ffff', - beige: '#f5f5dc', - bisque: '#ffe4c4', - black: '#000000', - blanchedalmond: '#ffebcd', - blue: '#0000ff', - blueviolet: '#8a2be2', - brown: '#a52a2a', - burlywood: '#deb887', - burntsienna: '#ea7e5d', - cadetblue: '#5f9ea0', - chartreuse: '#7fff00', - chocolate: '#d2691e', - coral: '#ff7f50', - cornflowerblue: '#6495ed', - cornsilk: '#fff8dc', - crimson: '#dc143c', - cyan: '#00ffff', - darkblue: '#00008b', - darkcyan: '#008b8b', - darkgoldenrod: '#b8860b', - darkgray: '#a9a9a9', - darkgreen: '#006400', - darkgrey: '#a9a9a9', - darkkhaki: '#bdb76b', - darkmagenta: '#8b008b', - darkolivegreen: '#556b2f', - darkorange: '#ff8c00', - darkorchid: '#9932cc', - darkred: '#8b0000', - darksalmon: '#e9967a', - darkseagreen: '#8fbc8f', - darkslateblue: '#483d8b', - darkslategray: '#2f4f4f', - darkslategrey: '#2f4f4f', - darkturquoise: '#00ced1', - darkviolet: '#9400d3', - deeppink: '#ff1493', - deepskyblue: '#00bfff', - dimgray: '#696969', - dimgrey: '#696969', - dodgerblue: '#1e90ff', - firebrick: '#b22222', - floralwhite: '#fffaf0', - forestgreen: '#228b22', - fuchsia: '#ff00ff', - gainsboro: '#dcdcdc', - ghostwhite: '#f8f8ff', - gold: '#ffd700', - goldenrod: '#daa520', - gray: '#808080', - green: '#008000', - greenyellow: '#adff2f', - grey: '#808080', - honeydew: '#f0fff0', - hotpink: '#ff69b4', - indianred: '#cd5c5c', - indigo: '#4b0082', - ivory: '#fffff0', - khaki: '#f0e68c', - lavender: '#e6e6fa', - lavenderblush: '#fff0f5', - lawngreen: '#7cfc00', - lemonchiffon: '#fffacd', - lightblue: '#add8e6', - lightcoral: '#f08080', - lightcyan: '#e0ffff', - lightgoldenrodyellow: '#fafad2', - lightgray: '#d3d3d3', - lightgreen: '#90ee90', - lightgrey: '#d3d3d3', - lightpink: '#ffb6c1', - lightsalmon: '#ffa07a', - lightseagreen: '#20b2aa', - lightskyblue: '#87cefa', - lightslategray: '#778899', - lightslategrey: '#778899', - lightsteelblue: '#b0c4de', - lightyellow: '#ffffe0', - lime: '#00ff00', - limegreen: '#32cd32', - linen: '#faf0e6', - magenta: '#ff00ff', - maroon: '#800000', - mediumaquamarine: '#66cdaa', - mediumblue: '#0000cd', - mediumorchid: '#ba55d3', - mediumpurple: '#9370db', - mediumseagreen: '#3cb371', - mediumslateblue: '#7b68ee', - mediumspringgreen: '#00fa9a', - mediumturquoise: '#48d1cc', - mediumvioletred: '#c71585', - midnightblue: '#191970', - mintcream: '#f5fffa', - mistyrose: '#ffe4e1', - moccasin: '#ffe4b5', - navajowhite: '#ffdead', - navy: '#000080', - oldlace: '#fdf5e6', - olive: '#808000', - olivedrab: '#6b8e23', - orange: '#ffa500', - orangered: '#ff4500', - orchid: '#da70d6', - palegoldenrod: '#eee8aa', - palegreen: '#98fb98', - paleturquoise: '#afeeee', - palevioletred: '#db7093', - papayawhip: '#ffefd5', - peachpuff: '#ffdab9', - peru: '#cd853f', - pink: '#ffc0cb', - plum: '#dda0dd', - powderblue: '#b0e0e6', - purple: '#800080', - rebeccapurple: '#663399', - red: '#ff0000', - rosybrown: '#bc8f8f', - royalblue: '#4169e1', - saddlebrown: '#8b4513', - salmon: '#fa8072', - sandybrown: '#f4a460', - seagreen: '#2e8b57', - seashell: '#fff5ee', - sienna: '#a0522d', - silver: '#c0c0c0', - skyblue: '#87ceeb', - slateblue: '#6a5acd', - slategray: '#708090', - slategrey: '#708090', - snow: '#fffafa', - springgreen: '#00ff7f', - steelblue: '#4682b4', - tan: '#d2b48c', - teal: '#008080', - thistle: '#d8bfd8', - tomato: '#ff6347', - turquoise: '#40e0d0', - violet: '#ee82ee', - wheat: '#f5deb3', - white: '#ffffff', - whitesmoke: '#f5f5f5', - yellow: '#ffff00', - yellowgreen: '#9acd32', - } -} diff --git a/packages/x6/src/common/dictionary.ts b/packages/x6/src/common/dictionary.ts deleted file mode 100644 index 8e0ceeb5a2a..00000000000 --- a/packages/x6/src/common/dictionary.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Disposable } from './disposable' - -export class Dictionary, V> extends Disposable { - private map: WeakMap - private arr: T[] - - constructor() { - super() - this.clear() - } - - clear() { - this.map = new WeakMap() - this.arr = [] - } - - has(key: T) { - return this.map.has(key) - } - - get(key: T) { - return this.map.get(key) - } - - set(key: T, value: V) { - this.map.set(key, value) - this.arr.push(key) - } - - delete(key: T) { - const index = this.arr.indexOf(key) - if (index >= 0) { - this.arr.splice(index, 1) - } - const ret = this.map.get(key) - this.map.delete(key) - return ret - } - - each(iterator: (value: V, key: T) => void) { - this.arr.forEach((key) => { - const value = this.map.get(key)! - iterator(value, key) - }) - } - - @Disposable.dispose() - dispose() { - this.clear() - } -} diff --git a/packages/x6/src/common/disablable.ts b/packages/x6/src/common/disablable.ts deleted file mode 100644 index 8bb562f7e5e..00000000000 --- a/packages/x6/src/common/disablable.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable no-underscore-dangle */ - -import { Basecoat } from './basecoat' - -export interface IDisablable { - readonly disabled: boolean - enable(): void - disable(): void -} - -export abstract class Disablable - extends Basecoat - implements IDisablable -{ - private _disabled?: boolean - - public get disabled(): boolean { - return this._disabled === true - } - - public enable() { - delete this._disabled - } - - public disable() { - this._disabled = true - } -} diff --git a/packages/x6/src/common/disposable.test.ts b/packages/x6/src/common/disposable.test.ts deleted file mode 100644 index 13ba61d61df..00000000000 --- a/packages/x6/src/common/disposable.test.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { - Disposable, - IDisposable, - DisposableSet, - DisposableDelegate, -} from './disposable' - -class TestDisposable implements IDisposable { - count = 0 - - get disposed(): boolean { - return this.count > 0 - } - - dispose(): void { - this.count += 1 - } -} - -class AOPTest extends Disposable { - a = 1 - - @Disposable.dispose() - dispose() { - this.a = 0 - } -} - -describe('disposable', () => { - describe('Disablable', () => { - it('should be `false` before object is disposed', () => { - const obj = new Disposable() - expect(obj.disposed).toBe(false) - }) - - it('should be `true` after object is disposed', () => { - const obj = new Disposable() - obj.dispose() - expect(obj.disposed).toBe(true) - }) - - // it('should add `unload` listener for ie', () => { - // const tmp = Platform as any - // tmp.IS_IE = true - // const obj = new Disposable() - // expect(obj.disposed).toBe(false) - // tmp.IS_IE = false - // window.dispatchEvent(new Event('unload')) - // expect(obj.disposed).toBe(true) - // }) - - it('shoule work with `aop`', () => { - const obj = new AOPTest() - expect(obj.disposed).toBe(false) - expect(obj.a).toBe(1) - obj.dispose() - expect(obj.disposed).toBe(true) - expect(obj.a).toBe(0) - obj.dispose() - expect(obj.disposed).toBe(true) - expect(obj.a).toBe(0) - }) - }) - - describe('DisposableDelegate', () => { - describe('#constructor', () => { - it('should accept a callback', () => { - const delegate = new DisposableDelegate(() => {}) - expect(delegate instanceof DisposableDelegate).toBeTruthy() - }) - }) - - describe('#disposed', () => { - it('should be `false` before object is disposed', () => { - const delegate = new DisposableDelegate(() => {}) - expect(delegate.disposed).toBe(false) - }) - - it('should be `true` after object is disposed', () => { - const delegate = new DisposableDelegate(() => {}) - delegate.dispose() - expect(delegate.disposed).toBe(true) - }) - }) - - describe('#dispose', () => { - it('should invoke a callback when disposed', () => { - let called = false - const delegate = new DisposableDelegate(() => (called = true)) - expect(called).toBe(false) - delegate.dispose() - expect(called).toBe(true) - }) - - it('should ignore multiple calls to `dispose`', () => { - let count = 0 - const delegate = new DisposableDelegate(() => (count += 1)) - expect(count).toBe(0) - delegate.dispose() - delegate.dispose() - delegate.dispose() - expect(count).toBe(1) - }) - }) - }) - - describe('DisposableSet', () => { - describe('#constructor', () => { - it('should accept no arguments', () => { - const set = new DisposableSet() - expect(set instanceof DisposableSet).toBeTruthy() - }) - }) - - describe('#disposed', () => { - it('should be `false` before object is disposed', () => { - const set = new DisposableSet() - expect(set.disposed).toBe(false) - }) - - it('should be `true` after object is disposed', () => { - const set = new DisposableSet() - set.dispose() - expect(set.disposed).toBe(true) - }) - }) - - describe('#dispose', () => { - it('should dispose all items in the set', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = DisposableSet.from([item1, item2, item3]) - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - set.dispose() - expect(item1.count).toBe(1) - expect(item2.count).toBe(1) - expect(item3.count).toBe(1) - }) - - it('should dipose items in the order they were added', () => { - const values: number[] = [] - const item1 = new DisposableDelegate(() => values.push(0)) - const item2 = new DisposableDelegate(() => values.push(1)) - const item3 = new DisposableDelegate(() => values.push(2)) - const set = DisposableSet.from([item1, item2, item3]) - expect(values).toEqual([]) - set.dispose() - expect(values).toEqual([0, 1, 2]) - }) - - it('should ignore multiple calls to `dispose`', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = DisposableSet.from([item1, item2, item3]) - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - set.dispose() - set.dispose() - set.dispose() - expect(item1.count).toBe(1) - expect(item2.count).toBe(1) - expect(item3.count).toBe(1) - }) - }) - - describe('#add', () => { - it('should add items to the set', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = new DisposableSet() - set.add(item1) - set.add(item2) - set.add(item3) - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - set.dispose() - expect(item1.count).toBe(1) - expect(item2.count).toBe(1) - expect(item3.count).toBe(1) - }) - - it('should maintain insertion order', () => { - const values: number[] = [] - const item1 = new DisposableDelegate(() => values.push(0)) - const item2 = new DisposableDelegate(() => values.push(1)) - const item3 = new DisposableDelegate(() => values.push(2)) - const set = DisposableSet.from([item1]) - set.add(item2) - set.add(item3) - expect(values).toEqual([]) - set.dispose() - expect(values).toEqual([0, 1, 2]) - }) - - it('should ignore duplicate items', () => { - const values: number[] = [] - const item1 = new DisposableDelegate(() => values.push(0)) - const item2 = new DisposableDelegate(() => values.push(1)) - const item3 = new DisposableDelegate(() => values.push(2)) - const set = DisposableSet.from([item1]) - set.add(item2) - set.add(item3) - set.add(item3) - set.add(item2) - set.add(item1) - expect(values).toEqual([]) - set.dispose() - expect(values).toEqual([0, 1, 2]) - }) - }) - - describe('#contains', () => { - it('should remove all items from the set after disposed', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = new DisposableSet() - set.add(item1) - set.add(item2) - set.add(item3) - expect(set.contains(item1)).toBe(true) - expect(set.contains(item2)).toBe(true) - expect(set.contains(item3)).toBe(true) - - set.dispose() - - expect(set.contains(item1)).toBe(false) - expect(set.contains(item2)).toBe(false) - expect(set.contains(item3)).toBe(false) - }) - }) - - describe('#remove', () => { - it('should remove items from the set', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = DisposableSet.from([item1, item2, item3]) - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - set.remove(item2) - set.dispose() - expect(item1.count).toBe(1) - expect(item2.count).toBe(0) - expect(item3.count).toBe(1) - }) - - it('should maintain insertion order', () => { - const values: number[] = [] - const item1 = new DisposableDelegate(() => values.push(0)) - const item2 = new DisposableDelegate(() => values.push(1)) - const item3 = new DisposableDelegate(() => values.push(2)) - const set = DisposableSet.from([item1, item2, item3]) - expect(values).toEqual([]) - set.remove(item1) - set.dispose() - expect(values).toEqual([1, 2]) - }) - - it('should ignore missing items', () => { - const values: number[] = [] - const item1 = new DisposableDelegate(() => values.push(0)) - const item2 = new DisposableDelegate(() => values.push(1)) - const item3 = new DisposableDelegate(() => values.push(2)) - const set = DisposableSet.from([item1, item2]) - expect(values).toEqual([]) - set.remove(item3) - set.dispose() - expect(values).toEqual([0, 1]) - }) - }) - - describe('#clear', () => { - it('should remove all items from the set', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = DisposableSet.from([item1, item2, item3]) - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - set.clear() - set.dispose() - expect(item1.count).toBe(0) - expect(item2.count).toBe(0) - expect(item3.count).toBe(0) - }) - }) - - describe('#from', () => { - it('should accept an iterable of disposable items', () => { - const item1 = new TestDisposable() - const item2 = new TestDisposable() - const item3 = new TestDisposable() - const set = DisposableSet.from([item1, item2, item3]) - expect(set instanceof DisposableSet).toBeTruthy() - }) - }) - }) -}) diff --git a/packages/x6/src/common/disposable.ts b/packages/x6/src/common/disposable.ts deleted file mode 100644 index d0a80a7221c..00000000000 --- a/packages/x6/src/common/disposable.ts +++ /dev/null @@ -1,196 +0,0 @@ -/* eslint-disable no-underscore-dangle */ - -/** - * An object which implements the disposable pattern. - */ -export interface IDisposable { - /** - * Test whether the object has been disposed. - * - * #### Notes - * This property is always safe to access. - */ - readonly disposed: boolean - - /** - * Dispose of the resources held by the object. - * - * #### Notes - * If the object's `dispose` method is called more than once, all - * calls made after the first will be a no-op. - * - * #### Undefined Behavior - * It is undefined behavior to use any functionality of the object - * after it has been disposed unless otherwise explicitly noted. - */ - dispose(): void -} - -export class Disposable implements IDisposable { - // constructor() { - // if (Platform.IS_IE) { - // DomEvent.addListener(window, 'unload', () => { - // this.dispose() - // }) - // } - // } - - // eslint-disable-next-line - private _disposed?: boolean - - get disposed() { - return this._disposed === true - } - - public dispose() { - this._disposed = true - } -} - -export namespace Disposable { - export function dispose() { - return ( - target: any, - methodName: string, - descriptor: PropertyDescriptor, - ) => { - const raw = descriptor.value - const proto = target.__proto__ as IDisposable // eslint-disable-line - descriptor.value = function (this: IDisposable) { - if (this.disposed) { - return - } - raw.call(this) - proto.dispose.call(this) - } - } - } -} - -/** - * A disposable object which delegates to a callback function. - */ -export class DisposableDelegate implements IDisposable { - private callback: (() => void) | null - - /** - * Construct a new disposable delegate. - * - * @param callback - The callback function to invoke on dispose. - */ - constructor(callback: () => void) { - this.callback = callback - } - - /** - * Test whether the delegate has been disposed. - */ - get disposed(): boolean { - return !this.callback - } - - /** - * Dispose of the delegate and invoke the callback function. - */ - dispose(): void { - if (!this.callback) { - return - } - const callback = this.callback - this.callback = null - callback() - } -} - -/** - * An object which manages a collection of disposable items. - */ -export class DisposableSet implements IDisposable { - private isDisposed = false // eslint-disable-line:variable-name - - private items = new Set() - - /** - * Test whether the set has been disposed. - */ - get disposed(): boolean { - return this.isDisposed - } - - /** - * Dispose of the set and the items it contains. - * - * #### Notes - * Items are disposed in the order they are added to the set. - */ - dispose(): void { - if (this.isDisposed) { - return - } - this.isDisposed = true - - this.items.forEach((item) => { - item.dispose() - }) - this.items.clear() - } - - /** - * Test whether the set contains a specific item. - * - * @param item - The item of interest. - * - * @returns `true` if the set contains the item, `false` otherwise. - */ - contains(item: IDisposable): boolean { - return this.items.has(item) - } - - /** - * Add a disposable item to the set. - * - * @param item - The item to add to the set. - * - * #### Notes - * If the item is already contained in the set, this is a no-op. - */ - add(item: IDisposable): void { - this.items.add(item) - } - - /** - * Remove a disposable item from the set. - * - * @param item - The item to remove from the set. - * - * #### Notes - * If the item is not contained in the set, this is a no-op. - */ - remove(item: IDisposable): void { - this.items.delete(item) - } - - /** - * Remove all items from the set. - */ - clear(): void { - this.items.clear() - } -} - -export namespace DisposableSet { - /** - * Create a disposable set from an iterable of items. - * - * @param items - The iterable or array-like object of interest. - * - * @returns A new disposable initialized with the given items. - */ - export function from(items: IDisposable[]): DisposableSet { - const set = new DisposableSet() - items.forEach((item) => { - set.add(item) - }) - return set - } -} diff --git a/packages/x6/src/common/events.test.ts b/packages/x6/src/common/events.test.ts deleted file mode 100644 index ba380f6c582..00000000000 --- a/packages/x6/src/common/events.test.ts +++ /dev/null @@ -1,228 +0,0 @@ -import sinon from 'sinon' -import { Events } from './events' - -describe('events', () => { - it('should trigger with context', () => { - const obj = new Events() - const spy = sinon.spy() - const ctx = {} - obj.on('a', spy, ctx) - - obj.trigger('a') - expect(spy.calledOn(ctx)).toBeTruthy() - }) - - it('should trigger with arguments', () => { - const obj = new Events() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - - obj.on('a', spy1) - obj.on('b', spy2) - - const data = { a: 1 } - obj.trigger('a', 1, 2, 3) - obj.trigger('b', data) - expect(spy1.calledWith(1, 2, 3)).toBeTruthy() - expect(spy2.calledWith(data)).toBeTruthy() - }) - - it('should trigger event multi times', () => { - const obj = new Events() - const spy = sinon.spy() - - obj.on('a', spy) - obj.trigger('a', 1, 2) - expect(spy.callCount).toBe(1) - - obj.trigger('a') - obj.trigger('a') - obj.trigger('b') - expect(spy.callCount).toBe(3) - }) - - it('should trigger once', () => { - const spy1 = sinon.spy() - const spy2 = sinon.spy() - const obj = new Events() - const context = {} - - obj.once('a', spy1, context) - obj.once('b', spy2, context) - - obj.trigger('a', 1) - obj.trigger('b', 1, 2) - obj.trigger('a', 1) - obj.trigger('b', 1) - obj.trigger('c') - - expect(spy1.withArgs(1).calledOnce).toBeTruthy() - expect(spy1.calledOn(context)).toBeTruthy() - expect(spy2.withArgs(1, 2).calledOnce).toBeTruthy() - expect(spy2.calledOn(context)).toBeTruthy() - }) - - it('should returns callback status', () => { - const obj = new Events() - const stub1 = sinon.stub() - const stub2 = sinon.stub() - - obj.on('a', stub1) - obj.on('a', stub2) - - stub1.returns(false) - stub2.returns(true) - - expect(obj.trigger('a')).toBe(false) - - stub1.returns(undefined) - stub2.returns(null) - expect(obj.trigger('a')).toBe(true) - - stub1.returns(true) - stub2.returns(true) - expect(obj.trigger('a')).toBe(true) - }) - - it('should not bind invalid callbacks', () => { - const obj = new Events() - const spy = sinon.spy() - - obj.on('a', null as any) - - obj.trigger('a') - expect(spy.callCount).toBe(0) - }) - - it('should bind a callback with a specific context', () => { - const obj = new Events() - const context = {} - const spy = sinon.spy() - - obj.on('a', spy, context) - - obj.trigger('a') - expect(spy.calledOn(context)) - }) - - it('should unbind event', () => { - const obj = new Events() - const spy = sinon.spy() - - obj.on('a', spy) - obj.trigger('a') - expect(spy.callCount).toBe(1) - - obj.off('a') - obj.off('b') - obj.trigger('a') - expect(spy.callCount).toBe(1) - }) - - it('should unbind specified event callback', () => { - const obj = new Events() - const spyA = sinon.spy() - const spyB = sinon.spy() - - obj.on('a', spyA) - obj.on('a', spyB) - - obj.trigger('a') - expect(spyA.callCount).toBe(1) - expect(spyB.callCount).toBe(1) - - obj.off('a', spyA) - obj.trigger('a') - expect(spyA.callCount).toBe(1) - expect(spyB.callCount).toBe(2) - }) - - it('should unbind a callback in the midst of it trigger', () => { - const obj = new Events() - const spy = sinon.spy() - - function callback() { - spy() - obj.off('a', callback) - } - - obj.on('a', callback) - obj.trigger('a') - obj.trigger('a') - - expect(spy.callCount).toBe(1) - }) - - it('should remove all events', () => { - const obj = new Events() - const spy1 = sinon.spy() - const spy2 = sinon.spy() - - obj.on('x', spy1) - obj.on('y', spy2) - - obj.trigger('x') - obj.trigger('y') - obj.off() - obj.trigger('x') - obj.trigger('y') - - expect(spy1.callCount).toBe(1) - expect(spy2.callCount).toBe(1) - }) - - it('should remove all events for a specific event', () => { - const obj = new Events() - const spyA = sinon.spy() - const spyB = sinon.spy() - - obj.on('x', spyA) - obj.on('y', spyA) - obj.on('x', spyB) - obj.on('y', spyB) - - obj.trigger('x') - obj.off('x') - obj.off('y') - obj.trigger('x') - - expect(spyA.callCount).toBe(1) - expect(spyB.callCount).toBe(1) - }) - - it('should remove all events for a specific context', () => { - const obj = new Events() - const spyA = sinon.spy() - const spyB = sinon.spy() - const context = {} - - obj.on('x', spyA) - obj.on('y', spyA) - obj.on('x', spyB, context) - obj.on('y', spyB, context) - - obj.off(null, null, context) - obj.trigger('x') - obj.trigger('y') - - expect(spyA.callCount).toBe(2) - expect(spyB.callCount).toBe(0) - }) - - it('should remove all events for a specific callback', () => { - const obj = new Events() - const success = sinon.spy() - const fail = sinon.spy() - - obj.on('x', success) - obj.on('y', success) - obj.on('x', fail) - obj.on('y', fail) - obj.off(null, fail) - obj.trigger('x') - obj.trigger('y') - - expect(success.callCount).toBe(2) - expect(fail.callCount).toBe(0) - }) -}) diff --git a/packages/x6/src/common/events.ts b/packages/x6/src/common/events.ts deleted file mode 100644 index 4cf5f8569b7..00000000000 --- a/packages/x6/src/common/events.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { - RequiredKeys, - OptionalKeys, - PickByValue, - OmitByValue, -} from 'utility-types' -import { FunctionExt } from '../util' - -export class Events { - private listeners: { [name: string]: any[] } = {} - - on>( - name: Name, - handler: Events.Handler, - context?: any, - ): this - on>( - name: Name, - handler: Events.Handler, - context?: any, - ): this - on>( - name: Name, - handler: Events.Handler, - context?: any, - ) { - if (handler == null) { - return this - } - - if (!this.listeners[name]) { - this.listeners[name] = [] - } - const cache = this.listeners[name] - cache.push(handler, context) - - return this - } - - once>( - name: Name, - handler: Events.Handler, - context?: any, - ): this - once>( - name: Name, - handler: Events.Handler, - context?: any, - ): this - once>( - name: Name, - handler: Events.Handler, - context?: any, - ) { - const cb = (...args: any) => { - this.off(name, cb as any) - return Private.call([handler, context], args) - } - - return this.on(name, cb as any, this) - } - - off(): this - off(name: null, handler: Events.Handler): this - off(name: null, handler: null, context: any): this - off>( - name: Name, - handler?: Events.Handler, - context?: any, - ): this - off>( - name: Name, - handler?: Events.Handler, - context?: any, - ): this - off( - name?: string | null, - handler?: Events.Handler | null, - context?: any, - ) { - // remove all events. - if (!(name || handler || context)) { - this.listeners = {} - return this - } - - const listeners = this.listeners - const names = name ? [name] : Object.keys(listeners) - - names.forEach((n) => { - const cache = listeners[n] - if (!cache) { - return - } - - // remove all events with specified name. - if (!(handler || context)) { - delete listeners[n] - return - } - - for (let i = cache.length - 2; i >= 0; i -= 2) { - if ( - !( - (handler && cache[i] !== handler) || - (context && cache[i + 1] !== context) - ) - ) { - cache.splice(i, 2) - } - } - }) - - return this - } - - trigger>( - name: Name, - ): FunctionExt.AsyncBoolean - trigger>( - name: Name, - args: EventArgs[Name], - ): FunctionExt.AsyncBoolean - trigger>( - name: Name, - ...args: EventArgs[Name] - ): FunctionExt.AsyncBoolean - trigger>( - name: Name, - args?: EventArgs[Name], - ): FunctionExt.AsyncBoolean - trigger>( - name: Name, - ...args: EventArgs[Name] - ): FunctionExt.AsyncBoolean - trigger>( - name: Name, - ...args: any[] - ): FunctionExt.AsyncBoolean - trigger>( - name: Name, - ...args: any[] - ) { - let returned: FunctionExt.AsyncBoolean = true - if (name !== '*') { - const list = this.listeners[name] - if (list != null) { - returned = Private.call([...list], args) - } - } - - const list = this.listeners['*'] - if (list != null) { - return FunctionExt.toAsyncBoolean([ - returned, - Private.call([...list], [name, ...args]), - ]) - } - - return returned - } - - /** - * Triggers event with specified event name. Unknown names - * will cause a typescript type error. - */ - protected emit>( - name: Name, - ): FunctionExt.AsyncBoolean - protected emit>( - name: Name, - args: EventArgs[Name], - ): FunctionExt.AsyncBoolean - protected emit>( - name: Name, - ...args: EventArgs[Name] - ): FunctionExt.AsyncBoolean - protected emit>( - name: Name, - args?: EventArgs[Name], - ): FunctionExt.AsyncBoolean - protected emit>( - name: Name, - ...args: EventArgs[Name] - ): FunctionExt.AsyncBoolean - protected emit(name: any, ...args: any[]) { - return this.trigger(name, ...args) - } -} - -export namespace Events { - export type Handler = Args extends null | undefined - ? () => any - : Args extends any[] - ? (...args: Args) => any - : (args: Args) => any - - export type EventArgs = { [key: string]: any } - - export type EventNames = Extract - - /** - * Get union type of keys from `M` that value matching `any[]`. - */ - export type NamesWithArrayArgs = RequiredKeys< - PickByValue - > - - export type NotArrayValueMap = OmitByValue - - export type OptionalNormalNames = OptionalKeys< - NotArrayValueMap - > - - export type RequiredNormalNames = RequiredKeys< - NotArrayValueMap - > - - export type OtherNames = EventNames< - PickByValue - > - - export type UnknownNames = Exclude> -} - -namespace Private { - export function call(list: any[], args?: any[]) { - const results: any[] = [] - for (let i = 0; i < list.length; i += 2) { - const handler = list[i] - const context = list[i + 1] - const params = Array.isArray(args) ? args : [args] - const ret = FunctionExt.apply(handler, context, params) - results.push(ret) - } - - return FunctionExt.toAsyncBoolean(results) - } -} diff --git a/packages/x6/src/common/index.ts b/packages/x6/src/common/index.ts deleted file mode 100644 index 93b1e7bf8df..00000000000 --- a/packages/x6/src/common/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from './color' -export * from './events' -export * from './basecoat' -export * from './disposable' -export * from './disablable' -export * from './dictionary' - -export * from './algorithm' -export * from './animation' -export * from './localstorage' diff --git a/packages/x6/src/common/localstorage.ts b/packages/x6/src/common/localstorage.ts deleted file mode 100644 index e0078ef61f7..00000000000 --- a/packages/x6/src/common/localstorage.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { Config } from '../global/config' -import { StringExt, FunctionExt } from '../util' - -export namespace LocalStorage { - const prefix = `${Config.prefixCls}.storage` - - export function insert(collection: string, doc: T, cb: Types.Callback) { - const id = (doc as any).id || StringExt.uniqueId('doc-') - const index = loadIndex(collection) - - index.keys.push(id) - - setItem(docKey(collection, id), doc) - setItem(indexKey(collection), index) - callback(cb, null, { ...doc, id }) - } - - export function find( - collection: string, - query: Types.Query, - cb: Types.Callback, - ) { - const index = loadIndex(collection) - const docs: T[] = [] - - if (query == null) { - index.keys.forEach((id) => { - const doc = getItem(docKey(collection, id)) - if (!doc) { - callback( - cb, - new Error(`No document found for an ID '${id}' from index.`), - ) - } else { - docs.push(doc) - } - }) - - callback(cb, null, docs) - } else if (query.id) { - const doc = getItem(docKey(collection, query.id)) - callback(cb, null, doc ? [doc] : []) - } else { - callback(cb, null, []) - } - } - - export function remove( - collection: string, - query: Types.Query, - cb: Types.Callback, - ) { - const index = loadIndex(collection) - - if (query == null) { - index.keys.forEach((id) => { - localStorage.removeItem(docKey(collection, id)) - }) - - localStorage.removeItem(indexKey(collection)) - callback(cb, null) - } else if (query.id) { - const idx = index.keys.indexOf(query.id) - if (idx >= 0) { - index.keys.splice(idx, 1) - } - localStorage.removeItem(docKey(collection, query.id)) - setItem(indexKey(collection), index) - callback(cb, null) - } - } - - // util - // ---- - function callback(cb: Types.Callback, err: Error | null, ret?: T) { - if (cb) { - FunctionExt.defer(() => { - cb(err, ret) - }) - } - } - - function setItem(key: string, item: T) { - localStorage.setItem(key, JSON.stringify(item)) - } - - function getItem(key: string): T | null { - const item = localStorage.getItem(key) - return item ? JSON.parse(item) : null - } - - function loadIndex(collection: string) { - const index = getItem(indexKey(collection)) - if (index) { - if (index.keys == null) { - index.keys = [] - } - return index - } - return { keys: [] } - } - - function docKey(collection: string, id: string) { - return `${prefix}.${collection}.docs.${id}` - } - - function indexKey(collection: string) { - return `${prefix}.${collection}.index` - } -} - -namespace Types { - export interface Index { - keys: string[] - } - - export type Callback = (err: Error | null, ret?: T) => any - - export type Query = { id: string } | null | undefined -} diff --git a/packages/x6-core/src/common/config.ts b/packages/x6/src/config.ts similarity index 100% rename from packages/x6-core/src/common/config.ts rename to packages/x6/src/config.ts diff --git a/packages/x6/src/geometry/angle.ts b/packages/x6/src/geometry/angle.ts deleted file mode 100644 index fd3a5908e9c..00000000000 --- a/packages/x6/src/geometry/angle.ts +++ /dev/null @@ -1,26 +0,0 @@ -export namespace Angle { - /** - * Converts radian angle to degree angle. - * @param rad The radians to convert. - */ - export function toDeg(rad: number) { - return ((180 * rad) / Math.PI) % 360 - } - - /** - * Converts degree angle to radian angle. - * @param deg The degree angle to convert. - * @param over360 - */ - export const toRad = function (deg: number, over360 = false) { - const d = over360 ? deg : deg % 360 - return (d * Math.PI) / 180 - } - - /** - * Returns the angle in degrees and clamps its value between `0` and `360`. - */ - export function normalize(angle: number) { - return (angle % 360) + (angle < 0 ? 360 : 0) - } -} diff --git a/packages/x6/src/geometry/curve.ts b/packages/x6/src/geometry/curve.ts deleted file mode 100644 index be536801ad9..00000000000 --- a/packages/x6/src/geometry/curve.ts +++ /dev/null @@ -1,940 +0,0 @@ -import { Point } from './point' -import { Line } from './line' -import { Rectangle } from './rectangle' -import { Polyline } from './polyline' -import { Geometry } from './geometry' - -export class Curve extends Geometry { - start: Point - end: Point - controlPoint1: Point - controlPoint2: Point - - PRECISION = 3 - - protected get [Symbol.toStringTag]() { - return Curve.toStringTag - } - - constructor( - start: Point.PointLike | Point.PointData, - controlPoint1: Point.PointLike | Point.PointData, - controlPoint2: Point.PointLike | Point.PointData, - end: Point.PointLike | Point.PointData, - ) { - super() - this.start = Point.create(start) - this.controlPoint1 = Point.create(controlPoint1) - this.controlPoint2 = Point.create(controlPoint2) - this.end = Point.create(end) - } - - bbox() { - const start = this.start - const controlPoint1 = this.controlPoint1 - const controlPoint2 = this.controlPoint2 - const end = this.end - - const x0 = start.x - const y0 = start.y - const x1 = controlPoint1.x - const y1 = controlPoint1.y - const x2 = controlPoint2.x - const y2 = controlPoint2.y - const x3 = end.x - const y3 = end.y - - const points = [] // local extremes - const tvalues = [] // t values of local extremes - const bounds: [number[], number[]] = [[], []] - - let a - let b - let c - let t - let t1 - let t2 - let b2ac - let sqrtb2ac - - for (let i = 0; i < 2; i += 1) { - if (i === 0) { - b = 6 * x0 - 12 * x1 + 6 * x2 - a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3 - c = 3 * x1 - 3 * x0 - } else { - b = 6 * y0 - 12 * y1 + 6 * y2 - a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3 - c = 3 * y1 - 3 * y0 - } - - if (Math.abs(a) < 1e-12) { - if (Math.abs(b) < 1e-12) { - continue - } - - t = -c / b - if (t > 0 && t < 1) tvalues.push(t) - - continue - } - - b2ac = b * b - 4 * c * a - sqrtb2ac = Math.sqrt(b2ac) - - if (b2ac < 0) continue - - t1 = (-b + sqrtb2ac) / (2 * a) - if (t1 > 0 && t1 < 1) tvalues.push(t1) - - t2 = (-b - sqrtb2ac) / (2 * a) - if (t2 > 0 && t2 < 1) tvalues.push(t2) - } - - let x - let y - let mt - let j = tvalues.length - const jlen = j - - while (j) { - j -= 1 - t = tvalues[j] - mt = 1 - t - - x = - mt * mt * mt * x0 + - 3 * mt * mt * t * x1 + - 3 * mt * t * t * x2 + - t * t * t * x3 - bounds[0][j] = x - - y = - mt * mt * mt * y0 + - 3 * mt * mt * t * y1 + - 3 * mt * t * t * y2 + - t * t * t * y3 - - bounds[1][j] = y - points[j] = { X: x, Y: y } - } - - tvalues[jlen] = 0 - tvalues[jlen + 1] = 1 - - points[jlen] = { X: x0, Y: y0 } - points[jlen + 1] = { X: x3, Y: y3 } - - bounds[0][jlen] = x0 - bounds[1][jlen] = y0 - - bounds[0][jlen + 1] = x3 - bounds[1][jlen + 1] = y3 - - tvalues.length = jlen + 2 - bounds[0].length = jlen + 2 - bounds[1].length = jlen + 2 - points.length = jlen + 2 - - const left = Math.min.apply(null, bounds[0]) - const top = Math.min.apply(null, bounds[1]) - const right = Math.max.apply(null, bounds[0]) - const bottom = Math.max.apply(null, bounds[1]) - - return new Rectangle(left, top, right - left, bottom - top) - } - - closestPoint( - p: Point.PointLike | Point.PointData, - options: Curve.Options = {}, - ) { - return this.pointAtT(this.closestPointT(p, options)) - } - - closestPointLength( - p: Point.PointLike | Point.PointData, - options: Curve.Options = {}, - ) { - const opts = this.getOptions(options) - return this.lengthAtT(this.closestPointT(p, opts), opts) - } - - closestPointNormalizedLength( - p: Point.PointLike | Point.PointData, - options: Curve.Options = {}, - ) { - const opts = this.getOptions(options) - const cpLength = this.closestPointLength(p, opts) - if (!cpLength) { - return 0 - } - - const length = this.length(opts) - if (length === 0) { - return 0 - } - - return cpLength / length - } - - closestPointT( - p: Point.PointLike | Point.PointData, - options: Curve.Options = {}, - ) { - const precision = this.getPrecision(options) - const subdivisions = this.getDivisions(options) - const precisionRatio = Math.pow(10, -precision) // eslint-disable-line - - let investigatedSubdivision: Curve | null = null - let investigatedSubdivisionStartT = 0 - let investigatedSubdivisionEndT = 0 - let distFromStart = 0 - let distFromEnd = 0 - let chordLength = 0 - let minSumDist: number | null = null - - const count = subdivisions.length - let piece = count > 0 ? 1 / count : 0 - - subdivisions.forEach((division, i) => { - const startDist = division.start.distance(p) - const endDist = division.end.distance(p) - const sumDist = startDist + endDist - if (minSumDist == null || sumDist < minSumDist) { - investigatedSubdivision = division - investigatedSubdivisionStartT = i * piece - investigatedSubdivisionEndT = (i + 1) * piece - - distFromStart = startDist - distFromEnd = endDist - minSumDist = sumDist - chordLength = division.endpointDistance() - } - }) - - // Recursively divide investigated subdivision, until distance between - // baselinePoint and closest path endpoint is within `10^(-precision)`, - // then return the closest endpoint of that final subdivision. - // eslint-disable-next-line - while (true) { - // check if we have reached at least one required observed precision - // - calculated as: the difference in distances from point to start and end divided by the distance - // - note that this function is not monotonic = it doesn't converge stably but has "teeth" - // - the function decreases while one of the endpoints is fixed but "jumps" whenever we switch - // - this criterion works well for points lying far away from the curve - const startPrecisionRatio = distFromStart - ? Math.abs(distFromStart - distFromEnd!) / distFromStart - : 0 - - const endPrecisionRatio = - distFromEnd != null - ? Math.abs(distFromStart! - distFromEnd) / distFromEnd - : 0 - - const hasRequiredPrecision = - startPrecisionRatio < precisionRatio || - endPrecisionRatio < precisionRatio - - // check if we have reached at least one required minimal distance - // - calculated as: the subdivision chord length multiplied by precisionRatio - // - calculation is relative so it will work for arbitrarily large/small curves and their subdivisions - // - this is a backup criterion that works well for points lying "almost at" the curve - const hasMiniStartDistance = distFromStart - ? distFromStart < chordLength * precisionRatio - : true - const hasMiniEndDistance = distFromEnd - ? distFromEnd < chordLength * precisionRatio - : true - const hasMiniDistance = hasMiniStartDistance || hasMiniEndDistance - - if (hasRequiredPrecision || hasMiniDistance) { - return distFromStart <= distFromEnd - ? investigatedSubdivisionStartT - : investigatedSubdivisionEndT - } - - // otherwise, set up for next iteration - const divided: [Curve, Curve] = investigatedSubdivision!.divide(0.5) - piece /= 2 - - const startDist1 = divided[0].start.distance(p) - const endDist1 = divided[0].end.distance(p) - const sumDist1 = startDist1 + endDist1 - - const startDist2 = divided[1].start.distance(p) - const endDist2 = divided[1].end.distance(p) - const sumDist2 = startDist2 + endDist2 - - if (sumDist1 <= sumDist2) { - investigatedSubdivision = divided[0] - investigatedSubdivisionEndT -= piece - distFromStart = startDist1 - distFromEnd = endDist1 - } else { - investigatedSubdivision = divided[1] - investigatedSubdivisionStartT += piece - distFromStart = startDist2 - distFromEnd = endDist2 - } - } - } - - closestPointTangent( - p: Point.PointLike | Point.PointData, - options: Curve.Options = {}, - ) { - return this.tangentAtT(this.closestPointT(p, options)) - } - - containsPoint( - p: Point.PointLike | Point.PointData, - options: Curve.Options = {}, - ) { - const polyline = this.toPolyline(options) - return polyline.containsPoint(p) - } - - divideAt(ratio: number, options: Curve.Options = {}): [Curve, Curve] { - if (ratio <= 0) { - return this.divideAtT(0) - } - - if (ratio >= 1) { - return this.divideAtT(1) - } - - const t = this.tAt(ratio, options) - return this.divideAtT(t) - } - - divideAtLength(length: number, options: Curve.Options = {}): [Curve, Curve] { - const t = this.tAtLength(length, options) - return this.divideAtT(t) - } - - divide(t: number) { - return this.divideAtT(t) - } - - divideAtT(t: number): [Curve, Curve] { - const start = this.start - const controlPoint1 = this.controlPoint1 - const controlPoint2 = this.controlPoint2 - const end = this.end - - if (t <= 0) { - return [ - new Curve(start, start, start, start), - new Curve(start, controlPoint1, controlPoint2, end), - ] - } - - if (t >= 1) { - return [ - new Curve(start, controlPoint1, controlPoint2, end), - new Curve(end, end, end, end), - ] - } - - const dividerPoints = this.getSkeletonPoints(t) - const startControl1 = dividerPoints.startControlPoint1 - const startControl2 = dividerPoints.startControlPoint2 - const divider = dividerPoints.divider - const dividerControl1 = dividerPoints.dividerControlPoint1 - const dividerControl2 = dividerPoints.dividerControlPoint2 - - return [ - new Curve(start, startControl1, startControl2, divider), - new Curve(divider, dividerControl1, dividerControl2, end), - ] - } - - endpointDistance() { - return this.start.distance(this.end) - } - - getSkeletonPoints(t: number) { - const start = this.start - const control1 = this.controlPoint1 - const control2 = this.controlPoint2 - const end = this.end - - // shortcuts for `t` values that are out of range - if (t <= 0) { - return { - startControlPoint1: start.clone(), - startControlPoint2: start.clone(), - divider: start.clone(), - dividerControlPoint1: control1.clone(), - dividerControlPoint2: control2.clone(), - } - } - - if (t >= 1) { - return { - startControlPoint1: control1.clone(), - startControlPoint2: control2.clone(), - divider: end.clone(), - dividerControlPoint1: end.clone(), - dividerControlPoint2: end.clone(), - } - } - - const midpoint1 = new Line(start, control1).pointAt(t) - const midpoint2 = new Line(control1, control2).pointAt(t) - const midpoint3 = new Line(control2, end).pointAt(t) - - const subControl1 = new Line(midpoint1, midpoint2).pointAt(t) - const subControl2 = new Line(midpoint2, midpoint3).pointAt(t) - - const divideLine = new Line(subControl1, subControl2).pointAt(t) - - return { - startControlPoint1: midpoint1, - startControlPoint2: subControl1, - divider: divideLine, - dividerControlPoint1: subControl2, - dividerControlPoint2: midpoint3, - } - } - - getSubdivisions(options: Curve.Options = {}): Curve[] { - const precision = this.getPrecision(options) - let subdivisions = [ - new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end), - ] - - if (precision === 0) { - return subdivisions - } - - let previousLength = this.endpointDistance() - const precisionRatio = Math.pow(10, -precision) // eslint-disable-line - - // Recursively divide curve at `t = 0.5`, until the difference between - // observed length at subsequent iterations is lower than precision. - let iteration = 0 - // eslint-disable-next-line - while (true) { - iteration += 1 - - const divisions: Curve[] = [] - subdivisions.forEach((c) => { - // dividing at t = 0.5 (not at middle length!) - const divided = c.divide(0.5) - divisions.push(divided[0], divided[1]) - }) - - // measure new length - const length = divisions.reduce( - (memo, c) => memo + c.endpointDistance(), - 0, - ) - - // check if we have reached required observed precision - // sine-like curves may have the same observed length in iteration 0 and 1 - skip iteration 1 - // not a problem for further iterations because cubic curves cannot have more than two local extrema - // (i.e. cubic curves cannot intersect the baseline more than once) - // therefore two subsequent iterations cannot produce sampling with equal length - const ratio = length !== 0 ? (length - previousLength) / length : 0 - if (iteration > 1 && ratio < precisionRatio) { - return divisions - } - - subdivisions = divisions - previousLength = length - } - } - - length(options: Curve.Options = {}) { - const divisions = this.getDivisions(options) - return divisions.reduce((memo, c) => { - return memo + c.endpointDistance() - }, 0) - } - - lengthAtT(t: number, options: Curve.Options = {}) { - if (t <= 0) { - return 0 - } - - const precision = - options.precision === undefined ? this.PRECISION : options.precision - const subCurve = this.divide(t)[0] - return subCurve.length({ precision }) - } - - pointAt(ratio: number, options: Curve.Options = {}) { - if (ratio <= 0) { - return this.start.clone() - } - - if (ratio >= 1) { - return this.end.clone() - } - - const t = this.tAt(ratio, options) - return this.pointAtT(t) - } - - pointAtLength(length: number, options: Curve.Options = {}) { - const t = this.tAtLength(length, options) - return this.pointAtT(t) - } - - pointAtT(t: number) { - if (t <= 0) { - return this.start.clone() - } - - if (t >= 1) { - return this.end.clone() - } - - return this.getSkeletonPoints(t).divider - } - - isDifferentiable() { - const start = this.start - const control1 = this.controlPoint1 - const control2 = this.controlPoint2 - const end = this.end - - return !( - start.equals(control1) && - control1.equals(control2) && - control2.equals(end) - ) - } - - tangentAt(ratio: number, options: Curve.Options = {}) { - if (!this.isDifferentiable()) return null - - if (ratio < 0) { - ratio = 0 // eslint-disable-line - } else if (ratio > 1) { - ratio = 1 // eslint-disable-line - } - - const t = this.tAt(ratio, options) - return this.tangentAtT(t) - } - - tangentAtLength(length: number, options: Curve.Options = {}) { - if (!this.isDifferentiable()) { - return null - } - - const t = this.tAtLength(length, options) - return this.tangentAtT(t) - } - - tangentAtT(t: number) { - if (!this.isDifferentiable()) { - return null - } - - if (t < 0) { - t = 0 // eslint-disable-line - } - - if (t > 1) { - t = 1 // eslint-disable-line - } - - const skeletonPoints = this.getSkeletonPoints(t) - const p1 = skeletonPoints.startControlPoint2 - const p2 = skeletonPoints.dividerControlPoint1 - - const tangentStart = skeletonPoints.divider - const tangentLine = new Line(p1, p2) - // move so that tangent line starts at the point requested - tangentLine.translate(tangentStart.x - p1.x, tangentStart.y - p1.y) - return tangentLine - } - - protected getPrecision(options: Curve.Options = {}) { - return options.precision == null ? this.PRECISION : options.precision - } - - protected getDivisions(options: Curve.Options = {}) { - if (options.subdivisions != null) { - return options.subdivisions - } - - const precision = this.getPrecision(options) - return this.getSubdivisions({ precision }) - } - - protected getOptions(options: Curve.Options = {}): Curve.Options { - const precision = this.getPrecision(options) - const subdivisions = this.getDivisions(options) - return { precision, subdivisions } - } - - protected tAt(ratio: number, options: Curve.Options = {}) { - if (ratio <= 0) { - return 0 - } - if (ratio >= 1) { - return 1 - } - - const opts = this.getOptions(options) - const total = this.length(opts) - const length = total * ratio - return this.tAtLength(length, opts) - } - - protected tAtLength(length: number, options: Curve.Options = {}) { - let fromStart = true - if (length < 0) { - fromStart = false - length = -length // eslint-disable-line - } - - const precision = this.getPrecision(options) - const subdivisions = this.getDivisions(options) - const opts = { precision, subdivisions } - - let investigatedSubdivision: Curve | null = null - let investigatedSubdivisionStartT: number - let investigatedSubdivisionEndT: number - let baselinePointDistFromStart = 0 - let baselinePointDistFromEnd = 0 - let memo = 0 - - const count = subdivisions.length - let piece = count > 0 ? 1 / count : 0 - - for (let i = 0; i < count; i += 1) { - const index = fromStart ? i : count - 1 - i - const division = subdivisions[i] - const dist = division.endpointDistance() - - if (length <= memo + dist) { - investigatedSubdivision = division - investigatedSubdivisionStartT = index * piece - investigatedSubdivisionEndT = (index + 1) * piece - - baselinePointDistFromStart = fromStart - ? length - memo - : dist + memo - length - baselinePointDistFromEnd = fromStart - ? dist + memo - length - : length - memo - - break - } - - memo += dist - } - - if (investigatedSubdivision == null) { - return fromStart ? 1 : 0 - } - - // note that precision affects what length is recorded - // (imprecise measurements underestimate length by up to 10^(-precision) of the precise length) - // e.g. at precision 1, the length may be underestimated by up to 10% and cause this function to return 1 - - const total = this.length(opts) - const precisionRatio = Math.pow(10, -precision) // eslint-disable-line - - // recursively divide investigated subdivision: - // until distance between baselinePoint and closest path endpoint is within 10^(-precision) - // then return the closest endpoint of that final subdivision - // eslint-disable-next-line - while (true) { - let ratio - - ratio = total !== 0 ? baselinePointDistFromStart / total : 0 - if (ratio < precisionRatio) { - return investigatedSubdivisionStartT! - } - - ratio = total !== 0 ? baselinePointDistFromEnd / total : 0 - if (ratio < precisionRatio) { - return investigatedSubdivisionEndT! - } - - // otherwise, set up for next iteration - let newBaselinePointDistFromStart - let newBaselinePointDistFromEnd - - const divided: [Curve, Curve] = investigatedSubdivision.divide(0.5) - piece /= 2 - - const baseline1Length = divided[0].endpointDistance() - const baseline2Length = divided[1].endpointDistance() - - if (baselinePointDistFromStart <= baseline1Length) { - investigatedSubdivision = divided[0] - investigatedSubdivisionEndT! -= piece - - newBaselinePointDistFromStart = baselinePointDistFromStart - newBaselinePointDistFromEnd = - baseline1Length - newBaselinePointDistFromStart - } else { - investigatedSubdivision = divided[1] - investigatedSubdivisionStartT! += piece - - newBaselinePointDistFromStart = - baselinePointDistFromStart - baseline1Length - newBaselinePointDistFromEnd = - baseline2Length - newBaselinePointDistFromStart - } - - baselinePointDistFromStart = newBaselinePointDistFromStart - baselinePointDistFromEnd = newBaselinePointDistFromEnd - } - } - - toPoints(options: Curve.Options = {}) { - const subdivisions = this.getDivisions(options) - const points = [subdivisions[0].start.clone()] - subdivisions.forEach((c) => points.push(c.end.clone())) - return points - } - - toPolyline(options: Curve.Options = {}) { - return new Polyline(this.toPoints(options)) - } - - scale(sx: number, sy: number, origin?: Point.PointLike | Point.PointData) { - this.start.scale(sx, sy, origin) - this.controlPoint1.scale(sx, sy, origin) - this.controlPoint2.scale(sx, sy, origin) - this.end.scale(sx, sy, origin) - return this - } - - rotate(angle: number, origin?: Point.PointLike | Point.PointData) { - this.start.rotate(angle, origin) - this.controlPoint1.rotate(angle, origin) - this.controlPoint2.rotate(angle, origin) - this.end.rotate(angle, origin) - return this - } - - translate(tx: number, ty: number): this - translate(p: Point.PointLike | Point.PointData): this - translate(tx: number | Point.PointLike | Point.PointData, ty?: number) { - if (typeof tx === 'number') { - this.start.translate(tx, ty as number) - this.controlPoint1.translate(tx, ty as number) - this.controlPoint2.translate(tx, ty as number) - this.end.translate(tx, ty as number) - } else { - this.start.translate(tx) - this.controlPoint1.translate(tx) - this.controlPoint2.translate(tx) - this.end.translate(tx) - } - - return this - } - - equals(c: Curve) { - return ( - c != null && - this.start.equals(c.start) && - this.controlPoint1.equals(c.controlPoint1) && - this.controlPoint2.equals(c.controlPoint2) && - this.end.equals(c.end) - ) - } - - clone() { - return new Curve( - this.start, - this.controlPoint1, - this.controlPoint2, - this.end, - ) - } - - toJSON() { - return { - start: this.start.toJSON(), - controlPoint1: this.controlPoint1.toJSON(), - controlPoint2: this.controlPoint2.toJSON(), - end: this.end.toJSON(), - } - } - - serialize() { - return [ - this.start.serialize(), - this.controlPoint1.serialize(), - this.controlPoint2.serialize(), - this.end.serialize(), - ].join(' ') - } -} - -export namespace Curve { - export const toStringTag = `X6.Geometry.${Curve.name}` - - export function isCurve(instance: any): instance is Curve { - if (instance == null) { - return false - } - - if (instance instanceof Curve) { - return true - } - - const tag = instance[Symbol.toStringTag] - const curve = instance as Curve - - try { - if ( - (tag == null || tag === toStringTag) && - Point.isPoint(curve.start) && - Point.isPoint(curve.controlPoint1) && - Point.isPoint(curve.controlPoint2) && - Point.isPoint(curve.end) && - typeof curve.toPoints === 'function' && - typeof curve.toPolyline === 'function' - ) { - return true - } - } catch (e) { - return false - } - - return false - } -} - -export namespace Curve { - export interface Options { - precision?: number - subdivisions?: Curve[] - } -} -export namespace Curve { - function getFirstControlPoints(rhs: number[]) { - const n = rhs.length - const x = [] // `x` is a solution vector. - const tmp = [] - let b = 2.0 - - x[0] = rhs[0] / b - - // Decomposition and forward substitution. - for (let i = 1; i < n; i += 1) { - tmp[i] = 1 / b - b = (i < n - 1 ? 4.0 : 3.5) - tmp[i] - x[i] = (rhs[i] - x[i - 1]) / b - } - - for (let i = 1; i < n; i += 1) { - // Backsubstitution. - x[n - i - 1] -= tmp[n - i] * x[n - i] - } - - return x - } - - function getCurveControlPoints( - points: (Point.PointLike | Point.PointData)[], - ) { - const knots = points.map((p) => Point.clone(p)) - const firstControlPoints = [] - const secondControlPoints = [] - const n = knots.length - 1 - - // Special case: Bezier curve should be a straight line. - if (n === 1) { - // 3P1 = 2P0 + P3 - firstControlPoints[0] = new Point( - (2 * knots[0].x + knots[1].x) / 3, - (2 * knots[0].y + knots[1].y) / 3, - ) - - // P2 = 2P1 – P0 - secondControlPoints[0] = new Point( - 2 * firstControlPoints[0].x - knots[0].x, - 2 * firstControlPoints[0].y - knots[0].y, - ) - - return [firstControlPoints, secondControlPoints] - } - - // Calculate first Bezier control points. - // Right hand side vector. - const rhs = [] - - // Set right hand side X values. - for (let i = 1; i < n - 1; i += 1) { - rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x - } - - rhs[0] = knots[0].x + 2 * knots[1].x - rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0 - - // Get first control points X-values. - const x = getFirstControlPoints(rhs) - - // Set right hand side Y values. - for (let i = 1; i < n - 1; i += 1) { - rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y - } - - rhs[0] = knots[0].y + 2 * knots[1].y - rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0 - - // Get first control points Y-values. - const y = getFirstControlPoints(rhs) - - // Fill output arrays. - for (let i = 0; i < n; i += 1) { - // First control point. - firstControlPoints.push(new Point(x[i], y[i])) - - // Second control point. - if (i < n - 1) { - secondControlPoints.push( - new Point( - 2 * knots[i + 1].x - x[i + 1], - 2 * knots[i + 1].y - y[i + 1], - ), - ) - } else { - secondControlPoints.push( - new Point((knots[n].x + x[n - 1]) / 2, (knots[n].y + y[n - 1]) / 2), - ) - } - } - - return [firstControlPoints, secondControlPoints] - } - - export function throughPoints(points: (Point.PointLike | Point.PointData)[]) { - if (points == null || (Array.isArray(points) && points.length < 2)) { - throw new Error('At least 2 points are required') - } - - const controlPoints = getCurveControlPoints(points) - - const curves = [] - for (let i = 0, ii = controlPoints[0].length; i < ii; i += 1) { - const controlPoint1 = new Point( - controlPoints[0][i].x, - controlPoints[0][i].y, - ) - const controlPoint2 = new Point( - controlPoints[1][i].x, - controlPoints[1][i].y, - ) - - curves.push( - new Curve(points[i], controlPoint1, controlPoint2, points[i + 1]), - ) - } - - return curves - } -} diff --git a/packages/x6/src/geometry/ellipse.test.ts b/packages/x6/src/geometry/ellipse.test.ts deleted file mode 100644 index 94e2ef96f6e..00000000000 --- a/packages/x6/src/geometry/ellipse.test.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { Ellipse } from './ellipse' -import { Line } from './line' -import { Point } from './point' -import { Rectangle } from './rectangle' - -describe('ellipse', () => { - describe('#constructor', () => { - it('should create an ellipse instance', () => { - expect(new Ellipse()).toBeInstanceOf(Ellipse) - expect(new Ellipse(1)).toBeInstanceOf(Ellipse) - expect(new Ellipse(1, 2)).toBeInstanceOf(Ellipse) - expect(new Ellipse(1, 2, 3)).toBeInstanceOf(Ellipse) - expect(new Ellipse(1, 2, 3, 4)).toBeInstanceOf(Ellipse) - expect(new Ellipse(1, 2, 3, 4).x).toEqual(1) - expect(new Ellipse(1, 2, 3, 4).y).toEqual(2) - expect(new Ellipse(1, 2, 3, 4).a).toEqual(3) - expect(new Ellipse(1, 2, 3, 4).b).toEqual(4) - expect(new Ellipse().equals(new Ellipse(0, 0, 0, 0))) - }) - - it('should return the center point', () => { - const ellipse = new Ellipse(1, 2, 3, 4) - expect(ellipse.getCenter().equals({ x: 1, y: 2 })) - expect(ellipse.center.equals({ x: 1, y: 2 })) - }) - }) - - describe('#Ellipse.create', () => { - it('should create an ellipse from number', () => { - const ellipse = Ellipse.create(1, 2, 3, 4) - expect(ellipse.x).toEqual(1) - expect(ellipse.y).toEqual(2) - expect(ellipse.a).toEqual(3) - expect(ellipse.b).toEqual(4) - }) - - it('should create an ellipse from Ellipse instance', () => { - const ellipse = Ellipse.create(new Ellipse(1, 2, 3, 4)) - expect(ellipse.x).toEqual(1) - expect(ellipse.y).toEqual(2) - expect(ellipse.a).toEqual(3) - expect(ellipse.b).toEqual(4) - }) - - it('should create an ellipse from EllipseLike', () => { - const ellipse = Ellipse.create({ x: 1, y: 2, a: 3, b: 4 }) - expect(ellipse.x).toEqual(1) - expect(ellipse.y).toEqual(2) - expect(ellipse.a).toEqual(3) - expect(ellipse.b).toEqual(4) - }) - - it('should create an ellipse from EllipseData', () => { - const ellipse = Ellipse.create([1, 2, 3, 4]) - expect(ellipse.x).toEqual(1) - expect(ellipse.y).toEqual(2) - expect(ellipse.a).toEqual(3) - expect(ellipse.b).toEqual(4) - }) - }) - - describe('#Ellipse.fromRect', () => { - it('should create an ellipse from a rectangle instance', () => { - const ellipse = Ellipse.fromRect(new Rectangle(1, 2, 3, 4)) - expect(ellipse.x).toEqual(2.5) - expect(ellipse.y).toEqual(4) - expect(ellipse.a).toEqual(1.5) - expect(ellipse.b).toEqual(2) - }) - }) - - describe('#bbox', () => { - it('should return the bbox', () => { - expect(new Ellipse(1, 2, 3, 4).bbox().toJSON()).toEqual({ - x: -2, - y: -2, - width: 6, - height: 8, - }) - }) - }) - - describe('#inflate', () => { - it('should inflate with the given `amount`', () => { - expect(new Ellipse(1, 2, 3, 4).inflate(2).toJSON()).toEqual({ - x: 1, - y: 2, - a: 7, - b: 8, - }) - }) - - it('should inflate with the given `dx` and `dy`', () => { - expect(new Ellipse(1, 2, 3, 4).inflate(2, 3).toJSON()).toEqual({ - x: 1, - y: 2, - a: 7, - b: 10, - }) - }) - }) - - describe('#normalizedDistance', () => { - it('should return a normalized distance', () => { - const ellipse = new Ellipse(0, 0, 3, 4) - expect(ellipse.normalizedDistance(1, 1) < 1).toBeTrue() - expect(ellipse.normalizedDistance({ x: 5, y: 5 }) > 1).toBeTrue() - expect(ellipse.normalizedDistance([0, 4]) === 1).toBeTrue() - }) - }) - - describe('#containsPoint', () => { - const ellipse = new Ellipse(0, 0, 3, 4) - - it('shoule return true when ellipse contains the given point', () => { - expect(ellipse.containsPoint(1, 1)).toBeTrue() - }) - - it('shoule return true when the given point is on the boundary of the ellipse', () => { - expect(ellipse.containsPoint([0, 4])).toBeTrue() - }) - - it('shoule return true when ellipse not contains the given point', () => { - expect(ellipse.containsPoint({ x: 5, y: 5 })).toBeFalse() - }) - }) - - describe('#intersectsWithLine', () => { - const ellipse = new Ellipse(0, 0, 3, 4) - - it('should return the intersections with line', () => { - expect( - Point.equalPoints(ellipse.intersectsWithLine(new Line(0, -5, 0, 5))!, [ - { x: 0, y: -4 }, - { x: 0, y: 4 }, - ]), - ).toBeTrue() - - expect( - Point.equalPoints(ellipse.intersectsWithLine(new Line(0, 0, 0, 5))!, [ - { x: 0, y: 4 }, - ]), - ).toBeTrue() - - expect( - Point.equalPoints(ellipse.intersectsWithLine(new Line(3, 0, 3, 4))!, [ - { x: 3, y: 0 }, - ]), - ).toBeTrue() - }) - - it('should return null when not intersections', () => { - expect(ellipse.intersectsWithLine(new Line(0, 0, 1, 1))).toBeNull() - expect(ellipse.intersectsWithLine(new Line(3, 5, 3, 6))).toBeNull() - expect(ellipse.intersectsWithLine(new Line(-6, -6, -6, 100))).toBeNull() - expect(ellipse.intersectsWithLine(new Line(6, 6, 100, 100))).toBeNull() - }) - }) - - describe('#intersectsWithLineFromCenterToPoint', () => { - const ellipse = new Ellipse(0, 0, 3, 4) - - it('should return the intersection point when the given point is outside of the ellipse', () => { - expect( - ellipse - .intersectsWithLineFromCenterToPoint({ x: 0, y: 6 }) - .round() - .toJSON(), - ).toEqual({ x: 0, y: 4 }) - - expect( - ellipse - .intersectsWithLineFromCenterToPoint({ x: 6, y: 0 }) - .round() - .toJSON(), - ).toEqual({ x: 3, y: 0 }) - - expect( - ellipse - .intersectsWithLineFromCenterToPoint({ x: 0, y: 6 }, 90) - .round() - .toJSON(), - ).toEqual({ x: 0, y: 3 }) - }) - - it('should return the intersection point when the given point is inside of the ellipse', () => { - expect( - ellipse - .intersectsWithLineFromCenterToPoint({ x: 0, y: 0 }, 90) - .round() - .toJSON(), - ).toEqual({ x: -0, y: -3 }) - - expect( - ellipse - .intersectsWithLineFromCenterToPoint({ x: 0, y: 2 }) - .round() - .toJSON(), - ).toEqual({ x: 0, y: 4 }) - - expect( - ellipse - .intersectsWithLineFromCenterToPoint({ x: 0, y: 2 }, 90) - .round() - .toJSON(), - ).toEqual({ x: 0, y: 3 }) - - expect( - ellipse - .intersectsWithLineFromCenterToPoint({ x: 2, y: 0 }, 90) - .round() - .toJSON(), - ).toEqual({ x: 4, y: -0 }) - }) - }) - - describe('#tangentTheta', () => { - it('should return the tangent theta', () => { - const ellipse = new Ellipse(0, 0, 3, 4) - expect(ellipse.tangentTheta({ x: 3, y: 0 })).toEqual(270) - expect(ellipse.tangentTheta({ x: -3, y: 0 })).toEqual(90) - expect(ellipse.tangentTheta({ x: 0, y: 4 })).toEqual(180) - expect(ellipse.tangentTheta({ x: 0, y: -4 })).toEqual(-0) - }) - }) - - describe('#scale', () => { - it('should scale the ellipse with the given `sx` and `sy`', () => { - const ellipse = new Ellipse(1, 2, 3, 4).scale(3, 4) - expect(ellipse.x).toEqual(1) - expect(ellipse.y).toEqual(2) - expect(ellipse.a).toEqual(9) - expect(ellipse.b).toEqual(16) - }) - }) - - describe('#translate', () => { - it('should translate the ellipse with the given `dx` and `dy`', () => { - const ellipse = new Ellipse(1, 2, 3, 4).translate(3, 4) - expect(ellipse.x).toEqual(4) - expect(ellipse.y).toEqual(6) - expect(ellipse.a).toEqual(3) - expect(ellipse.b).toEqual(4) - }) - }) - - describe('#clone', () => { - it('should return the cloned ellipse', () => { - const ellipse = new Ellipse(1, 2, 3, 4).clone() - expect(ellipse.x).toEqual(1) - expect(ellipse.y).toEqual(2) - expect(ellipse.a).toEqual(3) - expect(ellipse.b).toEqual(4) - }) - }) - - describe('#serialize', () => { - it('should return the serialized string', () => { - expect(new Ellipse(1, 2, 3, 4).serialize()).toEqual('1 2 3 4') - }) - }) -}) diff --git a/packages/x6/src/geometry/ellipse.ts b/packages/x6/src/geometry/ellipse.ts deleted file mode 100644 index 1afc4d42432..00000000000 --- a/packages/x6/src/geometry/ellipse.ts +++ /dev/null @@ -1,350 +0,0 @@ -import { Point } from './point' -import { Line } from './line' -import { Rectangle } from './rectangle' -import { Geometry } from './geometry' - -export class Ellipse extends Geometry implements Ellipse.EllipseLike { - public x: number - public y: number - public a: number - public b: number - - protected get [Symbol.toStringTag]() { - return Ellipse.toStringTag - } - - get center() { - return new Point(this.x, this.y) - } - - constructor(x?: number, y?: number, a?: number, b?: number) { - super() - this.x = x == null ? 0 : x - this.y = y == null ? 0 : y - this.a = a == null ? 0 : a - this.b = b == null ? 0 : b - } - - /** - * Returns a rectangle that is the bounding box of the ellipse. - */ - bbox() { - return Rectangle.fromEllipse(this) - } - - /** - * Returns a point that is the center of the ellipse. - */ - getCenter() { - return this.center - } - - /** - * Returns ellipse inflated in axis-x by `2 * amount` and in axis-y by - * `2 * amount`. - */ - inflate(amount: number): this - /** - * Returns ellipse inflated in axis-x by `2 * dx` and in axis-y by `2 * dy`. - */ - inflate(dx: number, dy: number): this - inflate(dx: number, dy?: number): this { - const w = dx - const h = dy != null ? dy : dx - this.a += 2 * w - this.b += 2 * h - - return this - } - - /** - * Returns a normalized distance from the ellipse center to point `p`. - * Returns `n < 1` for points inside the ellipse, `n = 1` for points - * lying on the ellipse boundary and `n > 1` for points outside the ellipse. - */ - normalizedDistance(x: number, y: number): number - normalizedDistance(p: Point.PointLike | Point.PointData): number - normalizedDistance( - x: number | Point.PointLike | Point.PointData, - y?: number, - ) { - const ref = Point.create(x, y) - const dx = ref.x - this.x - const dy = ref.y - this.y - const a = this.a - const b = this.b - - return (dx * dx) / (a * a) + (dy * dy) / (b * b) - } - - /** - * Returns `true` if the point `p` is inside the ellipse (inclusive). - * Returns `false` otherwise. - */ - containsPoint(x: number, y: number): boolean - containsPoint(p: Point.PointLike | Point.PointData): boolean - containsPoint(x: number | Point.PointLike | Point.PointData, y?: number) { - return this.normalizedDistance(x as number, y as number) <= 1 - } - - /** - * Returns an array of the intersection points of the ellipse and the line. - * Returns `null` if no intersection exists. - */ - intersectsWithLine(line: Line) { - const intersections = [] - const rx = this.a - const ry = this.b - const a1 = line.start - const a2 = line.end - const dir = line.vector() - const diff = a1.diff(new Point(this.x, this.y)) - const mDir = new Point(dir.x / (rx * rx), dir.y / (ry * ry)) - const mDiff = new Point(diff.x / (rx * rx), diff.y / (ry * ry)) - - const a = dir.dot(mDir) - const b = dir.dot(mDiff) - const c = diff.dot(mDiff) - 1.0 - const d = b * b - a * c - - if (d < 0) { - return null - } - - if (d > 0) { - const root = Math.sqrt(d) - const ta = (-b - root) / a - const tb = (-b + root) / a - - if ((ta < 0 || ta > 1) && (tb < 0 || tb > 1)) { - // outside - return null - } - - if (ta >= 0 && ta <= 1) { - intersections.push(a1.lerp(a2, ta)) - } - - if (tb >= 0 && tb <= 1) { - intersections.push(a1.lerp(a2, tb)) - } - } else { - const t = -b / a - if (t >= 0 && t <= 1) { - intersections.push(a1.lerp(a2, t)) - } else { - // outside - return null - } - } - - return intersections - } - - /** - * Returns the point on the boundary of the ellipse that is the - * intersection of the ellipse with a line starting in the center - * of the ellipse ending in the point `p`. - * - * If angle is specified, the intersection will take into account - * the rotation of the ellipse by angle degrees around its center. - */ - intersectsWithLineFromCenterToPoint( - p: Point.PointLike | Point.PointData, - angle = 0, - ) { - const ref = Point.clone(p) - if (angle) { - ref.rotate(angle, this.getCenter()) - } - - const dx = ref.x - this.x - const dy = ref.y - this.y - let result - - if (dx === 0) { - result = this.bbox().getNearestPointToPoint(ref) - if (angle) { - return result.rotate(-angle, this.getCenter()) - } - return result - } - - const m = dy / dx - const mSquared = m * m - const aSquared = this.a * this.a - const bSquared = this.b * this.b - - let x = Math.sqrt(1 / (1 / aSquared + mSquared / bSquared)) - x = dx < 0 ? -x : x - - const y = m * x - result = new Point(this.x + x, this.y + y) - - if (angle) { - return result.rotate(-angle, this.getCenter()) - } - - return result - } - - /** - * Returns the angle between the x-axis and the tangent from a point. It is - * valid for points lying on the ellipse boundary only. - */ - tangentTheta(p: Point.PointLike | Point.PointData) { - const ref = Point.clone(p) - const x0 = ref.x - const y0 = ref.y - const a = this.a - const b = this.b - const center = this.bbox().center - const cx = center.x - const cy = center.y - const refPointDelta = 30 - - const q1 = x0 > center.x + a / 2 - const q3 = x0 < center.x - a / 2 - - let x - let y - - if (q1 || q3) { - y = x0 > center.x ? y0 - refPointDelta : y0 + refPointDelta - x = - (a * a) / (x0 - cx) - - (a * a * (y0 - cy) * (y - cy)) / (b * b * (x0 - cx)) + - cx - } else { - x = y0 > center.y ? x0 + refPointDelta : x0 - refPointDelta - y = - (b * b) / (y0 - cy) - - (b * b * (x0 - cx) * (x - cx)) / (a * a * (y0 - cy)) + - cy - } - - return new Point(x, y).theta(ref) - } - - scale(sx: number, sy: number) { - this.a *= sx - this.b *= sy - return this - } - - rotate(angle: number, origin?: Point.PointLike | Point.PointData) { - const rect = Rectangle.fromEllipse(this) - rect.rotate(angle, origin) - const ellipse = Ellipse.fromRect(rect) - this.a = ellipse.a - this.b = ellipse.b - this.x = ellipse.x - this.y = ellipse.y - return this - } - - translate(dx: number, dy: number): this - translate(p: Point.PointLike | Point.PointData): this - translate(dx: number | Point.PointLike | Point.PointData, dy?: number): this { - const p = Point.create(dx, dy) - this.x += p.x - this.y += p.y - return this - } - - equals(ellipse: Ellipse) { - return ( - ellipse != null && - ellipse.x === this.x && - ellipse.y === this.y && - ellipse.a === this.a && - ellipse.b === this.b - ) - } - - clone() { - return new Ellipse(this.x, this.y, this.a, this.b) - } - - toJSON() { - return { x: this.x, y: this.y, a: this.a, b: this.b } - } - - serialize() { - return `${this.x} ${this.y} ${this.a} ${this.b}` - } -} - -export namespace Ellipse { - export const toStringTag = `X6.Geometry.${Ellipse.name}` - - export function isEllipse(instance: any): instance is Ellipse { - if (instance == null) { - return false - } - - if (instance instanceof Ellipse) { - return true - } - - const tag = instance[Symbol.toStringTag] - const ellipse = instance as Ellipse - - if ( - (tag == null || tag === toStringTag) && - typeof ellipse.x === 'number' && - typeof ellipse.y === 'number' && - typeof ellipse.a === 'number' && - typeof ellipse.b === 'number' && - typeof ellipse.inflate === 'function' && - typeof ellipse.normalizedDistance === 'function' - ) { - return true - } - - return false - } -} - -export namespace Ellipse { - export interface EllipseLike extends Point.PointLike { - x: number - y: number - a: number - b: number - } - - export type EllipseData = [number, number, number, number] -} - -export namespace Ellipse { - export function create( - x?: number | Ellipse | EllipseLike | EllipseData, - y?: number, - a?: number, - b?: number, - ): Ellipse { - if (x == null || typeof x === 'number') { - return new Ellipse(x, y, a, b) - } - - return parse(x) - } - - export function parse(e: Ellipse | EllipseLike | EllipseData) { - if (Ellipse.isEllipse(e)) { - return e.clone() - } - - if (Array.isArray(e)) { - return new Ellipse(e[0], e[1], e[2], e[3]) - } - - return new Ellipse(e.x, e.y, e.a, e.b) - } - - export function fromRect(rect: Rectangle) { - const center = rect.center - return new Ellipse(center.x, center.y, rect.width / 2, rect.height / 2) - } -} diff --git a/packages/x6/src/geometry/geometry.ts b/packages/x6/src/geometry/geometry.ts deleted file mode 100644 index 0c2f59e8732..00000000000 --- a/packages/x6/src/geometry/geometry.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { JSONObject, JSONArray } from '../util' -import { Point } from './point' - -export abstract class Geometry { - abstract scale( - sx: number, - sy: number, - origin?: Point.PointLike | Point.PointData, - ): this - - abstract rotate( - angle: number, - origin?: Point.PointLike | Point.PointData, - ): this - - abstract translate(tx: number, ty: number): this - - abstract translate(p: Point.PointLike | Point.PointData): this - - abstract equals(g: any): boolean - - abstract clone(): Geometry - - abstract toJSON(): JSONObject | JSONArray - - abstract serialize(): string - - valueOf() { - return this.toJSON() - } - - toString() { - return JSON.stringify(this.toJSON()) - } -} diff --git a/packages/x6/src/geometry/index.ts b/packages/x6/src/geometry/index.ts deleted file mode 100644 index 35143cd3a35..00000000000 --- a/packages/x6/src/geometry/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './angle' -export * from './point' -export * from './line' -export * from './ellipse' -export * from './rectangle' -export * from './path' -export * from './curve' -export * from './polyline' diff --git a/packages/x6/src/geometry/line.ts b/packages/x6/src/geometry/line.ts deleted file mode 100644 index 79c2333035c..00000000000 --- a/packages/x6/src/geometry/line.ts +++ /dev/null @@ -1,552 +0,0 @@ -import { Path } from './path' -import { Point } from './point' -import { Ellipse } from './ellipse' -import { Geometry } from './geometry' -import { Polyline } from './polyline' -import { Rectangle } from './rectangle' - -export class Line extends Geometry { - public start: Point - public end: Point - - protected get [Symbol.toStringTag]() { - return Line.toStringTag - } - - get center() { - return new Point( - (this.start.x + this.end.x) / 2, - (this.start.y + this.end.y) / 2, - ) - } - - constructor(x1: number, y1: number, x2: number, y2: number) - constructor( - p1: Point.PointLike | Point.PointData, - p2: Point.PointLike | Point.PointData, - ) - constructor( - x1: number | Point.PointLike | Point.PointData, - y1: number | Point.PointLike | Point.PointData, - x2?: number, - y2?: number, - ) { - super() - if (typeof x1 === 'number' && typeof y1 === 'number') { - this.start = new Point(x1, y1) - this.end = new Point(x2, y2) - } else { - this.start = Point.create(x1) - this.end = Point.create(y1) - } - } - - getCenter() { - return this.center - } - - /** - * Rounds the line to the given `precision`. - */ - round(precision = 0) { - this.start.round(precision) - this.end.round(precision) - return this - } - - translate(tx: number, ty: number): this - translate(p: Point.PointLike | Point.PointData): this - translate(tx: number | Point.PointLike | Point.PointData, ty?: number) { - if (typeof tx === 'number') { - this.start.translate(tx, ty as number) - this.end.translate(tx, ty as number) - } else { - this.start.translate(tx) - this.end.translate(tx) - } - - return this - } - - /** - * Rotate the line by `angle` around `origin`. - */ - rotate(angle: number, origin?: Point.PointLike | Point.PointData) { - this.start.rotate(angle, origin) - this.end.rotate(angle, origin) - return this - } - - /** - * Scale the line by `sx` and `sy` about the given `origin`. If origin is not - * specified, the line is scaled around `0,0`. - */ - scale(sx: number, sy: number, origin?: Point.PointLike | Point.PointData) { - this.start.scale(sx, sy, origin) - this.end.scale(sx, sy, origin) - return this - } - - /** - * Returns the length of the line. - */ - length() { - return Math.sqrt(this.squaredLength()) - } - - /** - * Useful for distance comparisons in which real length is not necessary - * (saves one `Math.sqrt()` operation). - */ - squaredLength() { - const dx = this.start.x - this.end.x - const dy = this.start.y - this.end.y - return dx * dx + dy * dy - } - - /** - * Scale the line so that it has the requested length. The start point of - * the line is preserved. - */ - setLength(length: number) { - const total = this.length() - if (!total) { - return this - } - - const scale = length / total - return this.scale(scale, scale, this.start) - } - - parallel(distance: number) { - const line = this.clone() - if (!line.isDifferentiable()) { - return line - } - - const { start, end } = line - const eRef = start.clone().rotate(270, end) - const sRef = end.clone().rotate(90, start) - start.move(sRef, distance) - end.move(eRef, distance) - return line - } - - /** - * Returns the vector of the line with length equal to length of the line. - */ - vector() { - return new Point(this.end.x - this.start.x, this.end.y - this.start.y) - } - - /** - * Returns the angle of incline of the line. - * - * The function returns `NaN` if the start and end endpoints of the line - * both lie at the same coordinates(it is impossible to determine the angle - * of incline of a line that appears to be a point). The - * `line.isDifferentiable()` function may be used in advance to determine - * whether the angle of incline can be computed for a given line. - */ - angle() { - const horizontal = new Point(this.start.x + 1, this.start.y) - return this.start.angleBetween(this.end, horizontal) - } - - /** - * Returns a rectangle that is the bounding box of the line. - */ - bbox() { - const left = Math.min(this.start.x, this.end.x) - const top = Math.min(this.start.y, this.end.y) - const right = Math.max(this.start.x, this.end.x) - const bottom = Math.max(this.start.y, this.end.y) - - return new Rectangle(left, top, right - left, bottom - top) - } - - /** - * Returns the bearing (cardinal direction) of the line. - * - * The return value is one of the following strings: - * 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW' and 'N'. - * - * The function returns 'N' if the two endpoints of the line are coincident. - */ - bearing() { - return this.start.bearing(this.end) - } - - /** - * Returns the point on the line that lies closest to point `p`. - */ - closestPoint(p: Point.PointLike | Point.PointData) { - return this.pointAt(this.closestPointNormalizedLength(p)) - } - - /** - * Returns the length of the line up to the point that lies closest to point `p`. - */ - closestPointLength(p: Point.PointLike | Point.PointData) { - return this.closestPointNormalizedLength(p) * this.length() - } - - /** - * Returns a line that is tangent to the line at the point that lies closest - * to point `p`. - */ - closestPointTangent(p: Point.PointLike | Point.PointData) { - return this.tangentAt(this.closestPointNormalizedLength(p)) - } - - /** - * Returns the normalized length (distance from the start of the line / total - * line length) of the line up to the point that lies closest to point. - */ - closestPointNormalizedLength(p: Point.PointLike | Point.PointData) { - const product = this.vector().dot(new Line(this.start, p).vector()) - const normalized = Math.min(1, Math.max(0, product / this.squaredLength())) - - // normalized returns `NaN` if this line has zero length - if (Number.isNaN(normalized)) { - return 0 - } - - return normalized - } - - /** - * Returns a point on the line that lies `rate` (normalized length) away from - * the beginning of the line. - */ - pointAt(ratio: number) { - const start = this.start - const end = this.end - - if (ratio <= 0) { - return start.clone() - } - - if (ratio >= 1) { - return end.clone() - } - - return start.lerp(end, ratio) - } - - /** - * Returns a point on the line that lies length away from the beginning of - * the line. - */ - pointAtLength(length: number) { - const start = this.start - const end = this.end - - let fromStart = true - - if (length < 0) { - fromStart = false // start calculation from end point - length = -length // eslint-disable-line - } - - const total = this.length() - if (length >= total) { - return fromStart ? end.clone() : start.clone() - } - - const rate = (fromStart ? length : total - length) / total - return this.pointAt(rate) - } - - /** - * Divides the line into two lines at the point that lies `rate` (normalized - * length) away from the beginning of the line. - */ - divideAt(ratio: number) { - const dividerPoint = this.pointAt(ratio) - return [ - new Line(this.start, dividerPoint), - new Line(dividerPoint, this.end), - ] - } - - /** - * Divides the line into two lines at the point that lies length away from - * the beginning of the line. - */ - divideAtLength(length: number) { - const dividerPoint = this.pointAtLength(length) - return [ - new Line(this.start, dividerPoint), - new Line(dividerPoint, this.end), - ] - } - - /** - * Returns `true` if the point `p` lies on the line. Return `false` otherwise. - */ - containsPoint(p: Point.PointLike | Point.PointData) { - const start = this.start - const end = this.end - - // cross product of 0 indicates that this line and - // the vector to `p` are collinear. - if (start.cross(p, end) !== 0) { - return false - } - - const length = this.length() - if (new Line(start, p).length() > length) { - return false - } - - if (new Line(p, end).length() > length) { - return false - } - - return true - } - - /** - * Returns an array of the intersection points of the line with another - * geometry shape. - */ - intersect(shape: Line | Rectangle | Polyline | Ellipse): Point[] | null - intersect(shape: Path, options?: Path.Options): Point[] | null - intersect( - shape: Line | Rectangle | Polyline | Ellipse | Path, - options?: Path.Options, - ): Point[] | null { - const ret = shape.intersectsWithLine(this, options) - if (ret) { - return Array.isArray(ret) ? ret : [ret] - } - - return null - } - - /** - * Returns the intersection point of the line with another line. Returns - * `null` if no intersection exists. - */ - intersectsWithLine(line: Line) { - const pt1Dir = new Point( - this.end.x - this.start.x, - this.end.y - this.start.y, - ) - const pt2Dir = new Point( - line.end.x - line.start.x, - line.end.y - line.start.y, - ) - const det = pt1Dir.x * pt2Dir.y - pt1Dir.y * pt2Dir.x - const deltaPt = new Point( - line.start.x - this.start.x, - line.start.y - this.start.y, - ) - const alpha = deltaPt.x * pt2Dir.y - deltaPt.y * pt2Dir.x - const beta = deltaPt.x * pt1Dir.y - deltaPt.y * pt1Dir.x - - if (det === 0 || alpha * det < 0 || beta * det < 0) { - return null - } - - if (det > 0) { - if (alpha > det || beta > det) { - return null - } - } else if (alpha < det || beta < det) { - return null - } - - return new Point( - this.start.x + (alpha * pt1Dir.x) / det, - this.start.y + (alpha * pt1Dir.y) / det, - ) - } - - /** - * Returns `true` if a tangent line can be found for the line. - * - * Tangents cannot be found if both of the line endpoints are coincident - * (the line appears to be a point). - */ - isDifferentiable() { - return !this.start.equals(this.end) - } - - /** - * Returns the perpendicular distance between the line and point. The - * distance is positive if the point lies to the right of the line, negative - * if the point lies to the left of the line, and `0` if the point lies on - * the line. - */ - pointOffset(p: Point.PointLike | Point.PointData) { - const ref = Point.clone(p) - const start = this.start - const end = this.end - const determinant = - (end.x - start.x) * (ref.y - start.y) - - (end.y - start.y) * (ref.x - start.x) - - return determinant / this.length() - } - - /** - * Returns the squared distance between the line and the point. - */ - pointSquaredDistance(x: number, y: number): number - pointSquaredDistance(p: Point.PointLike | Point.PointData): number - pointSquaredDistance( - x: number | Point.PointLike | Point.PointData, - y?: number, - ) { - const p = Point.create(x, y) - return this.closestPoint(p).squaredDistance(p) - } - - /** - * Returns the distance between the line and the point. - */ - pointDistance(x: number, y: number): number - pointDistance(p: Point.PointLike | Point.PointData): number - pointDistance(x: number | Point.PointLike | Point.PointData, y?: number) { - const p = Point.create(x, y) - return this.closestPoint(p).distance(p) - } - - /** - * Returns a line tangent to the line at point that lies `rate` (normalized - * length) away from the beginning of the line. - */ - tangentAt(ratio: number) { - if (!this.isDifferentiable()) { - return null - } - - const start = this.start - const end = this.end - - const tangentStart = this.pointAt(ratio) - const tangentLine = new Line(start, end) - tangentLine.translate(tangentStart.x - start.x, tangentStart.y - start.y) - - return tangentLine - } - - /** - * Returns a line tangent to the line at point that lies `length` away from - * the beginning of the line. - */ - tangentAtLength(length: number) { - if (!this.isDifferentiable()) { - return null - } - - const start = this.start - const end = this.end - - const tangentStart = this.pointAtLength(length) - const tangentLine = new Line(start, end) - tangentLine.translate(tangentStart.x - start.x, tangentStart.y - start.y) - - return tangentLine - } - - /** - * Returns which direction the line would have to rotate in order to direct - * itself at a point. - * - * Returns 1 if the given point on the right side of the segment, 0 if its - * on the segment, and -1 if the point is on the left side of the segment. - * - * @see https://softwareengineering.stackexchange.com/questions/165776/what-do-ptlinedist-and-relativeccw-do - */ - relativeCcw(x: number, y: number): -1 | 0 | 1 - relativeCcw(p: Point.PointLike | Point.PointData): -1 | 0 | 1 - relativeCcw(x: number | Point.PointLike | Point.PointData, y?: number) { - const ref = Point.create(x, y) - - let dx1 = ref.x - this.start.x - let dy1 = ref.y - this.start.y - const dx2 = this.end.x - this.start.x - const dy2 = this.end.y - this.start.y - - let ccw = dx1 * dy2 - dy1 * dx2 - if (ccw === 0) { - ccw = dx1 * dx2 + dy1 * dy2 - if (ccw > 0.0) { - dx1 -= dx2 - dy1 -= dy2 - ccw = dx1 * dx2 + dy1 * dy2 - if (ccw < 0.0) { - ccw = 0.0 - } - } - } - - return ccw < 0.0 ? -1 : ccw > 0.0 ? 1 : 0 - } - - /** - * Return `true` if the line equals the other line. - */ - equals(l: Line) { - return ( - l != null && - this.start.x === l.start.x && - this.start.y === l.start.y && - this.end.x === l.end.x && - this.end.y === l.end.y - ) - } - - /** - * Returns another line which is a clone of the line. - */ - clone() { - return new Line(this.start, this.end) - } - - toJSON() { - return { start: this.start.toJSON(), end: this.end.toJSON() } - } - - serialize() { - return [this.start.serialize(), this.end.serialize()].join(' ') - } -} - -export namespace Line { - export const toStringTag = `X6.Geometry.${Line.name}` - - export function isLine(instance: any): instance is Line { - if (instance == null) { - return false - } - - if (instance instanceof Line) { - return true - } - - const tag = instance[Symbol.toStringTag] - const line = instance as Line - - try { - if ( - (tag == null || tag === toStringTag) && - Point.isPoint(line.start) && - Point.isPoint(line.end) && - typeof line.vector === 'function' && - typeof line.bearing === 'function' && - typeof line.parallel === 'function' && - typeof line.intersect === 'function' - ) { - return true - } - } catch (e) { - return false - } - - return false - } -} diff --git a/packages/x6/src/geometry/path/close.ts b/packages/x6/src/geometry/path/close.ts deleted file mode 100644 index 6baff2f651e..00000000000 --- a/packages/x6/src/geometry/path/close.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { Line } from '../line' -import { Point } from '../point' -import { LineTo } from './lineto' -import { Segment } from './segment' - -export class Close extends Segment { - get end() { - if (!this.subpathStartSegment) { - throw new Error( - 'Missing subpath start segment. (This segment needs a subpath ' + - 'start segment (e.g. MoveTo), or segment has not yet been added' + - ' to a path.)', - ) - } - - return this.subpathStartSegment.end - } - - get type() { - return 'Z' - } - - get line() { - return new Line(this.start, this.end) - } - - bbox() { - return this.line.bbox() - } - - closestPoint(p: Point.PointLike | Point.PointData) { - return this.line.closestPoint(p) - } - - closestPointLength(p: Point.PointLike | Point.PointData) { - return this.line.closestPointLength(p) - } - - closestPointNormalizedLength(p: Point.PointLike | Point.PointData) { - return this.line.closestPointNormalizedLength(p) - } - - closestPointTangent(p: Point.PointLike | Point.PointData) { - return this.line.closestPointTangent(p) - } - - length() { - return this.line.length() - } - - divideAt(ratio: number): [Segment, Segment] { - const divided = this.line.divideAt(ratio) - return [ - // do not actually cut into the segment, first divided part can stay as Z - divided[1].isDifferentiable() ? new LineTo(divided[0]) : this.clone(), - new LineTo(divided[1]), - ] - } - - divideAtLength(length: number): [Segment, Segment] { - const divided = this.line.divideAtLength(length) - return [ - divided[1].isDifferentiable() ? new LineTo(divided[0]) : this.clone(), - new LineTo(divided[1]), - ] - } - - getSubdivisions() { - return [] - } - - pointAt(ratio: number) { - return this.line.pointAt(ratio) - } - - pointAtLength(length: number) { - return this.line.pointAtLength(length) - } - - tangentAt(ratio: number) { - return this.line.tangentAt(ratio) - } - - tangentAtLength(length: number) { - return this.line.tangentAtLength(length) - } - - isDifferentiable() { - if (!this.previousSegment || !this.subpathStartSegment) { - return false - } - - return !this.start.equals(this.end) - } - - scale() { - return this - } - - rotate() { - return this - } - - translate() { - return this - } - - equals(s: Segment) { - return ( - this.type === s.type && - this.start.equals(s.start) && - this.end.equals(s.end) - ) - } - - clone() { - return new Close() - } - - toJSON() { - return { - type: this.type, - start: this.start.toJSON(), - end: this.end.toJSON(), - } - } - - serialize() { - return this.type - } -} - -export namespace Close { - export function create(): Close { - return new Close() - } -} diff --git a/packages/x6/src/geometry/path/curveto.ts b/packages/x6/src/geometry/path/curveto.ts deleted file mode 100644 index c5294e9c6f3..00000000000 --- a/packages/x6/src/geometry/path/curveto.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { Curve } from '../curve' -import { Point } from '../point' -import { Segment } from './segment' - -export class CurveTo extends Segment { - controlPoint1: Point - controlPoint2: Point - - constructor(curve: Curve) - constructor( - x1: number, - y1: number, - x2: number, - y2: number, - x: number, - y: number, - ) - constructor( - p1: Point.PointLike | Point.PointData, - p2: Point.PointLike | Point.PointData, - p3: Point.PointLike | Point.PointData, - ) - constructor( - arg0: number | Curve | (Point.PointLike | Point.PointData), - arg1?: number | (Point.PointLike | Point.PointData), - arg2?: number | (Point.PointLike | Point.PointData), - arg3?: number, - arg4?: number, - arg5?: number, - ) { - super() - - if (Curve.isCurve(arg0)) { - this.controlPoint1 = arg0.controlPoint1.clone().round(2) - this.controlPoint2 = arg0.controlPoint2.clone().round(2) - this.endPoint = arg0.end.clone().round(2) - } else if (typeof arg0 === 'number') { - this.controlPoint1 = new Point(arg0, arg1 as number).round(2) - this.controlPoint2 = new Point(arg2 as number, arg3).round(2) - this.endPoint = new Point(arg4, arg5).round(2) - } else { - this.controlPoint1 = Point.create(arg0).round(2) - this.controlPoint2 = Point.create(arg1).round(2) - this.endPoint = Point.create(arg2).round(2) - } - } - - get type() { - return 'C' - } - - get curve() { - return new Curve( - this.start, - this.controlPoint1, - this.controlPoint2, - this.end, - ) - } - - bbox() { - return this.curve.bbox() - } - - closestPoint(p: Point.PointLike | Point.PointData) { - return this.curve.closestPoint(p) - } - - closestPointLength(p: Point.PointLike | Point.PointData) { - return this.curve.closestPointLength(p) - } - - closestPointNormalizedLength(p: Point.PointLike | Point.PointData) { - return this.curve.closestPointNormalizedLength(p) - } - - closestPointTangent(p: Point.PointLike | Point.PointData) { - return this.curve.closestPointTangent(p) - } - - length() { - return this.curve.length() - } - - divideAt(ratio: number, options: Segment.Options = {}): [Segment, Segment] { - // TODO: fix options - const divided = this.curve.divideAt(ratio, options as any) - return [new CurveTo(divided[0]), new CurveTo(divided[1])] - } - - divideAtLength( - length: number, - options: Segment.Options = {}, - ): [Segment, Segment] { - // TODO: fix options - const divided = this.curve.divideAtLength(length, options as any) - return [new CurveTo(divided[0]), new CurveTo(divided[1])] - } - - divideAtT(t: number): [Segment, Segment] { - const divided = this.curve.divideAtT(t) - return [new CurveTo(divided[0]), new CurveTo(divided[1])] - } - - getSubdivisions() { - return [] - } - - pointAt(ratio: number) { - return this.curve.pointAt(ratio) - } - - pointAtLength(length: number) { - return this.curve.pointAtLength(length) - } - - tangentAt(ratio: number) { - return this.curve.tangentAt(ratio) - } - - tangentAtLength(length: number) { - return this.curve.tangentAtLength(length) - } - - isDifferentiable() { - if (!this.previousSegment) { - return false - } - - const start = this.start - const control1 = this.controlPoint1 - const control2 = this.controlPoint2 - const end = this.end - - return !( - start.equals(control1) && - control1.equals(control2) && - control2.equals(end) - ) - } - - scale(sx: number, sy: number, origin?: Point.PointLike | Point.PointData) { - this.controlPoint1.scale(sx, sy, origin) - this.controlPoint2.scale(sx, sy, origin) - this.end.scale(sx, sy, origin) - return this - } - - rotate(angle: number, origin?: Point.PointLike | Point.PointData) { - this.controlPoint1.rotate(angle, origin) - this.controlPoint2.rotate(angle, origin) - this.end.rotate(angle, origin) - return this - } - - translate(tx: number, ty: number): this - translate(p: Point.PointLike | Point.PointData): this - translate(tx: number | Point.PointLike | Point.PointData, ty?: number): this { - if (typeof tx === 'number') { - this.controlPoint1.translate(tx, ty as number) - this.controlPoint2.translate(tx, ty as number) - this.end.translate(tx, ty as number) - } else { - this.controlPoint1.translate(tx) - this.controlPoint2.translate(tx) - this.end.translate(tx) - } - - return this - } - - equals(s: Segment) { - return ( - this.start.equals(s.start) && - this.end.equals(s.end) && - this.controlPoint1.equals((s as CurveTo).controlPoint1) && - this.controlPoint2.equals((s as CurveTo).controlPoint2) - ) - } - - clone() { - return new CurveTo(this.controlPoint1, this.controlPoint2, this.end) - } - - toJSON() { - return { - type: this.type, - start: this.start.toJSON(), - controlPoint1: this.controlPoint1.toJSON(), - controlPoint2: this.controlPoint2.toJSON(), - end: this.end.toJSON(), - } - } - - serialize() { - const c1 = this.controlPoint1 - const c2 = this.controlPoint2 - const end = this.end - return [this.type, c1.x, c1.y, c2.x, c2.y, end.x, end.y].join(' ') - } -} - -export namespace CurveTo { - export function create( - x1: number, - y1: number, - x2: number, - y2: number, - x: number, - y: number, - ): CurveTo - export function create( - x1: number, - y1: number, - x2: number, - y2: number, - x: number, - y: number, - ...coords: number[] - ): CurveTo[] - export function create( - c1: Point.PointLike, - c2: Point.PointLike, - p: Point.PointLike, - ): CurveTo - export function create( - c1: Point.PointLike, - c2: Point.PointLike, - p: Point.PointLike, - ...points: Point.PointLike[] - ): CurveTo[] - export function create(...args: any[]): CurveTo | CurveTo[] { - const len = args.length - const arg0 = args[0] - - // curve provided - if (Curve.isCurve(arg0)) { - return new CurveTo(arg0) - } - - // points provided - if (Point.isPointLike(arg0)) { - if (len === 3) { - return new CurveTo(args[0], args[1], args[2]) - } - - // this is a poly-bezier segment - const segments: CurveTo[] = [] - for (let i = 0; i < len; i += 3) { - segments.push(new CurveTo(args[i], args[i + 1], args[i + 2])) - } - return segments - } - - // coordinates provided - if (len === 6) { - return new CurveTo(args[0], args[1], args[2], args[3], args[4], args[5]) - } - - // this is a poly-bezier segment - const segments: CurveTo[] = [] - for (let i = 0; i < len; i += 6) { - segments.push( - new CurveTo( - args[i], - args[i + 1], - args[i + 2], - args[i + 3], - args[i + 4], - args[i + 5], - ), - ) - } - return segments - } -} diff --git a/packages/x6/src/geometry/path/index.ts b/packages/x6/src/geometry/path/index.ts deleted file mode 100644 index b0f57bbff7d..00000000000 --- a/packages/x6/src/geometry/path/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './path' -export * from './segment' diff --git a/packages/x6/src/geometry/path/lineto.ts b/packages/x6/src/geometry/path/lineto.ts deleted file mode 100644 index b56754e4c2f..00000000000 --- a/packages/x6/src/geometry/path/lineto.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { Line } from '../line' -import { Point } from '../point' -import { Segment } from './segment' - -export class LineTo extends Segment { - constructor(line: Line) - constructor(x: number, y: number) - constructor(p: Point.PointLike | Point.PointData) - constructor( - x: number | Line | (Point.PointLike | Point.PointData), - y?: number, - ) { - super() - - if (Line.isLine(x)) { - this.endPoint = x.end.clone().round(2) - } else { - this.endPoint = Point.create(x, y).round(2) - } - } - - get type() { - return 'L' - } - - get line() { - return new Line(this.start, this.end) - } - - bbox() { - return this.line.bbox() - } - - closestPoint(p: Point.PointLike | Point.PointData) { - return this.line.closestPoint(p) - } - - closestPointLength(p: Point.PointLike | Point.PointData) { - return this.line.closestPointLength(p) - } - - closestPointNormalizedLength(p: Point.PointLike | Point.PointData) { - return this.line.closestPointNormalizedLength(p) - } - - closestPointTangent(p: Point.PointLike | Point.PointData) { - return this.line.closestPointTangent(p) - } - - length() { - return this.line.length() - } - - divideAt(ratio: number): [Segment, Segment] { - const divided = this.line.divideAt(ratio) - return [new LineTo(divided[0]), new LineTo(divided[1])] - } - - divideAtLength(length: number): [Segment, Segment] { - const divided = this.line.divideAtLength(length) - return [new LineTo(divided[0]), new LineTo(divided[1])] - } - - getSubdivisions() { - return [] - } - - pointAt(ratio: number) { - return this.line.pointAt(ratio) - } - - pointAtLength(length: number) { - return this.line.pointAtLength(length) - } - - tangentAt(ratio: number) { - return this.line.tangentAt(ratio) - } - - tangentAtLength(length: number) { - return this.line.tangentAtLength(length) - } - - isDifferentiable() { - if (this.previousSegment == null) { - return false - } - - return !this.start.equals(this.end) - } - - clone() { - return new LineTo(this.end) - } - - scale(sx: number, sy: number, origin?: Point.PointLike | Point.PointData) { - this.end.scale(sx, sy, origin) - return this - } - - rotate(angle: number, origin?: Point.PointLike | Point.PointData) { - this.end.rotate(angle, origin) - return this - } - - translate(tx: number, ty: number): this - translate(p: Point.PointLike | Point.PointData): this - translate(tx: number | Point.PointLike | Point.PointData, ty?: number): this { - if (typeof tx === 'number') { - this.end.translate(tx, ty as number) - } else { - this.end.translate(tx) - } - return this - } - - equals(s: Segment) { - return ( - this.type === s.type && - this.start.equals(s.start) && - this.end.equals(s.end) - ) - } - - toJSON() { - return { - type: this.type, - start: this.start.toJSON(), - end: this.end.toJSON(), - } - } - - serialize() { - const end = this.end - return `${this.type} ${end.x} ${end.y}` - } -} - -export namespace LineTo { - export function create(line: Line): LineTo - export function create(point: Point.PointLike): LineTo - export function create(x: number, y: number): LineTo - export function create( - point: Point.PointLike, - ...points: Point.PointLike[] - ): LineTo[] - export function create(x: number, y: number, ...coords: number[]): LineTo[] - export function create(...args: any[]): LineTo | LineTo[] { - const len = args.length - const arg0 = args[0] - - // line provided - if (Line.isLine(arg0)) { - return new LineTo(arg0) - } - - // points provided - if (Point.isPointLike(arg0)) { - if (len === 1) { - return new LineTo(arg0) - } - - // poly-line segment - return args.map((arg) => new LineTo(arg as Point.PointLike)) - } - - // coordinates provided - if (len === 2) { - return new LineTo(+args[0], +args[1]) - } - - // poly-line segment - const segments: LineTo[] = [] - for (let i = 0; i < len; i += 2) { - const x = +args[i] - const y = +args[i + 1] - segments.push(new LineTo(x, y)) - } - return segments - } -} diff --git a/packages/x6/src/geometry/path/moveto.ts b/packages/x6/src/geometry/path/moveto.ts deleted file mode 100644 index a9db35ebe55..00000000000 --- a/packages/x6/src/geometry/path/moveto.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { Line } from '../line' -import { Curve } from '../curve' -import { Point } from '../point' -import { LineTo } from './lineto' -import { Segment } from './segment' - -export class MoveTo extends Segment { - constructor(line: Line) - constructor(curve: Curve) - constructor(x: number, y: number) - constructor(p: Point.PointLike | Point.PointData) - constructor( - x: number | Curve | Line | (Point.PointLike | Point.PointData), - y?: number, - ) { - super() - - this.isVisible = false - this.isSubpathStart = true - - if (Line.isLine(x) || Curve.isCurve(x)) { - this.endPoint = x.end.clone().round(2) - } else { - this.endPoint = Point.create(x, y).round(2) - } - } - - get start(): Point { - throw new Error( - 'Illegal access. Moveto segments should not need a start property.', - ) - } - - get type() { - return 'M' - } - - bbox() { - return null - } - - closestPoint() { - return this.end.clone() - } - - closestPointLength() { - return 0 - } - - closestPointNormalizedLength() { - return 0 - } - - closestPointT() { - return 1 - } - - closestPointTangent() { - return null - } - - length() { - return 0 - } - - lengthAtT() { - return 0 - } - - divideAt(): [Segment, Segment] { - return [this.clone(), this.clone()] - } - - divideAtLength(): [Segment, Segment] { - return [this.clone(), this.clone()] - } - - getSubdivisions() { - return [] - } - - pointAt() { - return this.end.clone() - } - - pointAtLength() { - return this.end.clone() - } - - pointAtT() { - return this.end.clone() - } - - tangentAt() { - return null - } - - tangentAtLength() { - return null - } - - tangentAtT() { - return null - } - - isDifferentiable() { - return false - } - - scale(sx: number, sy: number, origin?: Point.PointLike | Point.PointData) { - this.end.scale(sx, sy, origin) - return this - } - - rotate(angle: number, origin?: Point.PointLike | Point.PointData) { - this.end.rotate(angle, origin) - return this - } - - translate(tx: number, ty: number): this - translate(p: Point.PointLike | Point.PointData): this - translate(tx: number | Point.PointLike | Point.PointData, ty?: number) { - if (typeof tx === 'number') { - this.end.translate(tx, ty as number) - } else { - this.end.translate(tx) - } - return this - } - - clone() { - return new MoveTo(this.end) - } - - equals(s: Segment) { - return this.type === s.type && this.end.equals(s.end) - } - - toJSON() { - return { - type: this.type, - end: this.end.toJSON(), - } - } - - serialize() { - const end = this.end - return `${this.type} ${end.x} ${end.y}` - } -} - -export namespace MoveTo { - export function create(line: Line): MoveTo - export function create(curve: Curve): MoveTo - export function create(point: Point.PointLike): MoveTo - export function create(x: number, y: number): MoveTo - export function create( - point: Point.PointLike, - ...points: Point.PointLike[] - ): Segment[] - export function create(x: number, y: number, ...coords: number[]): Segment[] - export function create(...args: any[]): MoveTo | Segment[] { - const len = args.length - const arg0 = args[0] - - // line provided - if (Line.isLine(arg0)) { - return new MoveTo(arg0) - } - - // curve provided - if (Curve.isCurve(arg0)) { - return new MoveTo(arg0) - } - - // points provided - if (Point.isPointLike(arg0)) { - if (len === 1) { - return new MoveTo(arg0) - } - - // this is a moveto-with-subsequent-poly-line segment - const segments: Segment[] = [] - // points come one by one - for (let i = 0; i < len; i += 1) { - if (i === 0) { - segments.push(new MoveTo(args[i])) - } else { - segments.push(new LineTo(args[i])) - } - } - return segments - } - - // coordinates provided - if (len === 2) { - return new MoveTo(+args[0], +args[1]) - } - - // this is a moveto-with-subsequent-poly-line segment - const segments: Segment[] = [] - for (let i = 0; i < len; i += 2) { - const x = +args[i] - const y = +args[i + 1] - if (i === 0) { - segments.push(new MoveTo(x, y)) - } else { - segments.push(new LineTo(x, y)) - } - } - return segments - } -} diff --git a/packages/x6/src/geometry/path/normalize.test.ts b/packages/x6/src/geometry/path/normalize.test.ts deleted file mode 100644 index d47f347f1c0..00000000000 --- a/packages/x6/src/geometry/path/normalize.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Path } from './path' -import { normalizePathData } from './normalize' - -describe('Path', () => { - describe('#normalizePathData', () => { - const paths = [ - ['M 10 10 H 20', 'M 10 10 L 20 10'], - ['M 10 10 V 20', 'M 10 10 L 10 20'], - [ - 'M 10 20 C 10 10 25 10 25 20 S 40 30 40 20', - 'M 10 20 C 10 10 25 10 25 20 C 25 30 40 30 40 20', - ], - ['M 20 20 Q 40 0 60 20', 'M 20 20 C 33.33 6.67 46.67 6.67 60 20'], - [ - 'M 20 20 Q 40 0 60 20 T 100 20', - 'M 20 20 C 33.33 6.67 46.67 6.67 60 20 C 73.33 33.33 86.67 33.33 100 20', - ], - ['M 30 15 A 15 15 0 0 0 15 30', 'M 30 15 C 21.72 15 15 21.72 15 30'], - ['m 10 10', 'M 10 10'], - ['M 10 10 m 10 10', 'M 10 10 M 20 20'], - ['M 10 10 l 10 10', 'M 10 10 L 20 20'], - ['M 10 10 c 0 10 10 10 10 0', 'M 10 10 C 10 20 20 20 20 10'], - ['M 10 10 z', 'M 10 10 Z'], - ['M 10 10 20 20', 'M 10 10 L 20 20'], - ['M 10 10 L 20 20 30 30', 'M 10 10 L 20 20 L 30 30'], - [ - 'M 10 10 C 10 20 20 20 20 10 20 0 30 0 30 10', - 'M 10 10 C 10 20 20 20 20 10 C 20 0 30 0 30 10', - ], - - // edge cases - ['L 10 10', 'M 0 0 L 10 10'], - ['C 10 20 20 20 20 10', 'M 0 0 C 10 20 20 20 20 10'], - ['Z', 'M 0 0 Z'], - ['M 10 10 Z L 20 20', 'M 10 10 Z L 20 20'], - ['M 10 10 Z C 10 20 20 20 20 10', 'M 10 10 Z C 10 20 20 20 20 10'], - ['M 10 10 Z Z', 'M 10 10 Z Z'], - ['', 'M 0 0'], // empty string - ['X', 'M 0 0'], // invalid command - ['M', 'M 0 0'], // no arguments for a command that needs them - ['M 10', 'M 0 0'], // too few arguments - ['M 10 10 20', 'M 10 10'], // too many arguments - ['X M 10 10', 'M 10 10'], // mixing invalid and valid commands - - // invalid commands interspersed with valid commands - ['X M 10 10 X L 20 20', 'M 10 10 L 20 20'], - ['A 0 3 0 0 1 10 15', 'M 0 0 L 10 15'], // 0 x radius - ['A 3 0 0 0 1 10 15', 'M 0 0 L 10 15'], // 0 y radius - ['A 0 0 0 0 1 10 15', 'M 0 0 L 10 15'], // 0 x and y radii - - // Make sure this does not throw an error because of - // recursion in a2c() exceeding the maximum stack size - ['M 0 0 A 1 1 0 1 0 -1 -1'], - ['M 14.4 29.52 a .72 .72 0 1 0 -.72 -.72 A .72 .72 0 0 0 14.4 29.52Z'], - ] - - it('should normalize path data', () => { - paths.forEach((path) => { - if (path[1]) { - expect(normalizePathData(path[0])).toEqual(path[1]) - } else { - normalizePathData(path[0]) - } - }) - }) - - it('should parsed by Path', () => { - const path1 = 'M 10 10' - const normalizedPath1 = normalizePathData(path1) - const reconstructedPath1 = Path.parse(path1).serialize() - expect(normalizedPath1).toEqual(reconstructedPath1) - - const path2 = 'M 100 100 C 100 100 0 150 100 200 Z' - const normalizedPath2 = normalizePathData(path2) - const reconstructedPath2 = Path.parse(path2).serialize() - expect(normalizedPath2).toEqual(reconstructedPath2) - - const path3 = - 'M285.8,83V52.7h8.3v31c0,3.2-1,5.8-3,7.7c-2,1.9-4.4,2.8-7.2,2.8c-2.9,0-5.6-1.2-8.1-3.5l3.8-6.1c1.1,1.3,2.3,1.9,3.7,1.9c0.7,0,1.3-0.3,1.8-0.9C285.5,85,285.8,84.2,285.8,83z' - const normalizedPath3 = normalizePathData(path3) - const reconstructedPath3 = Path.parse(path3).serialize() - expect(normalizedPath3).toEqual(reconstructedPath3) - }) - }) -}) diff --git a/packages/x6/src/geometry/path/normalize.ts b/packages/x6/src/geometry/path/normalize.ts deleted file mode 100644 index 04dd2e074b6..00000000000 --- a/packages/x6/src/geometry/path/normalize.ts +++ /dev/null @@ -1,501 +0,0 @@ -import { round } from '../util' - -type Segment = [string, ...number[]] - -function rotate(x: number, y: number, rad: number) { - return { - x: x * Math.cos(rad) - y * Math.sin(rad), - y: x * Math.sin(rad) + y * Math.cos(rad), - } -} - -function q2c( - x1: number, - y1: number, - ax: number, - ay: number, - x2: number, - y2: number, -) { - const v13 = 1 / 3 - const v23 = 2 / 3 - return [ - v13 * x1 + v23 * ax, - v13 * y1 + v23 * ay, - v13 * x2 + v23 * ax, - v13 * y2 + v23 * ay, - x2, - y2, - ] -} - -function a2c( - x1: number, - y1: number, - rx: number, - ry: number, - angle: number, - largeArcFlag: number, - sweepFlag: number, - x2: number, - y2: number, - recursive?: [number, number, number, number], -): any[] { - // for more information of where this math came from visit: - // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes - const v120 = (Math.PI * 120) / 180 - const rad = (Math.PI / 180) * (+angle || 0) - let res = [] - let xy - let f1 - let f2 - let cx - let cy - - if (!recursive) { - xy = rotate(x1, y1, -rad) - x1 = xy.x // eslint-disable-line - y1 = xy.y // eslint-disable-line - - xy = rotate(x2, y2, -rad) - x2 = xy.x // eslint-disable-line - y2 = xy.y // eslint-disable-line - - const x = (x1 - x2) / 2 - const y = (y1 - y2) / 2 - let h = (x * x) / (rx * rx) + (y * y) / (ry * ry) - - if (h > 1) { - h = Math.sqrt(h) - rx = h * rx // eslint-disable-line - ry = h * ry // eslint-disable-line - } - - const rx2 = rx * rx - const ry2 = ry * ry - - const k = - (largeArcFlag === sweepFlag ? -1 : 1) * - Math.sqrt( - Math.abs( - (rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x), - ), - ) - - cx = (k * rx * y) / ry + (x1 + x2) / 2 - cy = (k * -ry * x) / rx + (y1 + y2) / 2 - - f1 = Math.asin((y1 - cy) / ry) - f2 = Math.asin((y2 - cy) / ry) - - f1 = x1 < cx ? Math.PI - f1 : f1 - f2 = x2 < cx ? Math.PI - f2 : f2 - - if (f1 < 0) { - f1 = Math.PI * 2 + f1 - } - - if (f2 < 0) { - f2 = Math.PI * 2 + f2 - } - - if (sweepFlag && f1 > f2) { - f1 -= Math.PI * 2 - } - - if (!sweepFlag && f2 > f1) { - f2 -= Math.PI * 2 - } - } else { - f1 = recursive[0] - f2 = recursive[1] - cx = recursive[2] - cy = recursive[3] - } - - let df = f2 - f1 - if (Math.abs(df) > v120) { - const f2old = f2 - const x2old = x2 - const y2old = y2 - f2 = f1 + v120 * (sweepFlag && f2 > f1 ? 1 : -1) - x2 = cx + rx * Math.cos(f2) // eslint-disable-line - y2 = cy + ry * Math.sin(f2) // eslint-disable-line - res = a2c(x2, y2, rx, ry, angle, 0, sweepFlag, x2old, y2old, [ - f2, - f2old, - cx, - cy, - ]) - } - - df = f2 - f1 - - const c1 = Math.cos(f1) - const s1 = Math.sin(f1) - const c2 = Math.cos(f2) - const s2 = Math.sin(f2) - const t = Math.tan(df / 4) - const hx = (4 / 3) * (rx * t) - const hy = (4 / 3) * (ry * t) - const m1 = [x1, y1] - const m2 = [x1 + hx * s1, y1 - hy * c1] - const m3 = [x2 + hx * s2, y2 - hy * c2] - const m4 = [x2, y2] - - m2[0] = 2 * m1[0] - m2[0] - m2[1] = 2 * m1[1] - m2[1] - - if (recursive) { - return [m2, m3, m4].concat(res) - } - - { - res = [m2, m3, m4].concat(res).join().split(',') - - const newres = [] - const ii = res.length - for (let i = 0; i < ii; i += 1) { - newres[i] = - i % 2 - ? rotate(+res[i - 1], +res[i], rad).y - : rotate(+res[i], +res[i + 1], rad).x - } - return newres - } -} - -function parse(pathData: string) { - if (!pathData) { - return null - } - - const spaces = - '\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029' - - // https://regexper.com/#%28%5Ba-z%5D%29%5B%5Cs%2C%5D*%28%28-%3F%5Cd*%5C.%3F%5C%5Cd*%28%3F%3Ae%5B%5C-%2B%5D%3F%5Cd%2B%29%3F%5B%5Cs%5D*%2C%3F%5B%5Cs%5D*%29%2B%29 - const segmentReg = new RegExp( - `([a-z])[${spaces},]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[${spaces}]*,?[${spaces}]*)+)`, // eslint-disable-line - 'ig', - ) - - // https://regexper.com/#%28-%3F%5Cd*%5C.%3F%5Cd*%28%3F%3Ae%5B%5C-%2B%5D%3F%5Cd%2B%29%3F%29%5B%5Cs%5D*%2C%3F%5B%5Cs%5D* - const commandParamReg = new RegExp( - // eslint-disable-next-line - `(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[${spaces}]*,?[${spaces}]*`, - 'ig', - ) - - const paramsCount = { - a: 7, - c: 6, - h: 1, - l: 2, - m: 2, - q: 4, - s: 4, - t: 2, - v: 1, - z: 0, - } - - const segmetns: Segment[] = [] - - pathData.replace(segmentReg, (input: string, cmd: string, args: string) => { - const params: number[] = [] - let command = cmd.toLowerCase() - - args.replace(commandParamReg, (a: string, b: string) => { - if (b) { - params.push(+b) - } - return a - }) - - if (command === 'm' && params.length > 2) { - segmetns.push([cmd, ...params.splice(0, 2)]) - command = 'l' - cmd = cmd === 'm' ? 'l' : 'L' // eslint-disable-line - } - - const count = paramsCount[command as keyof typeof paramsCount] - while (params.length >= count) { - segmetns.push([cmd, ...params.splice(0, count)]) - if (!count) { - break - } - } - - return input - }) - - return segmetns -} - -function abs(pathString: string) { - const pathArray = parse(pathString) - - // if invalid string, return 'M 0 0' - if (!pathArray || !pathArray.length) { - return [['M', 0, 0]] - } - - let x = 0 - let y = 0 - let mx = 0 - let my = 0 - const segments = [] - - for (let i = 0, ii = pathArray.length; i < ii; i += 1) { - const r: any = [] - - segments.push(r) - - const segment = pathArray[i] - const command = segment[0] - if (command !== command.toUpperCase()) { - r[0] = command.toUpperCase() - - switch (r[0]) { - case 'A': - r[1] = segment[1] - r[2] = segment[2] - r[3] = segment[3] - r[4] = segment[4] - r[5] = segment[5] - r[6] = +segment[6] + x - r[7] = +segment[7] + y - break - - case 'V': - r[1] = +segment[1] + y - break - - case 'H': - r[1] = +segment[1] + x - break - - case 'M': - mx = +segment[1] + x - my = +segment[2] + y - - for (let j = 1, jj = segment.length; j < jj; j += 1) { - r[j] = +segment[j] + (j % 2 ? x : y) - } - break - - default: - for (let j = 1, jj = segment.length; j < jj; j += 1) { - r[j] = +segment[j] + (j % 2 ? x : y) - } - break - } - } else { - for (let j = 0, jj = segment.length; j < jj; j += 1) { - r[j] = segment[j] - } - } - - switch (r[0]) { - case 'Z': - x = +mx - y = +my - break - - case 'H': - x = r[1] - break - - case 'V': - y = r[1] - break - - case 'M': - mx = r[r.length - 2] - my = r[r.length - 1] - x = r[r.length - 2] - y = r[r.length - 1] - break - - default: - x = r[r.length - 2] - y = r[r.length - 1] - break - } - } - - return segments -} - -function normalize(path: string) { - const pathArray = abs(path) - const attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null } - - function processPath(path: any[], d: any, pcom: string) { - let nx - let ny - - if (!path) { - return ['C', d.x, d.y, d.x, d.y, d.x, d.y] - } - - if (!(path[0] in { T: 1, Q: 1 })) { - d.qx = null - d.qy = null - } - - switch (path[0]) { - case 'M': - d.X = path[1] - d.Y = path[2] - break - - case 'A': - if (parseFloat(path[1]) === 0 || parseFloat(path[2]) === 0) { - // https://www.w3.org/TR/SVG/paths.html#ArcOutOfRangeParameters - // "If either rx or ry is 0, then this arc is treated as a - // straight line segment (a "lineto") joining the endpoints." - return ['L', path[6], path[7]] - } - - return ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1)))) - - case 'S': - if (pcom === 'C' || pcom === 'S') { - // In 'S' case we have to take into account, if the previous command is C/S. - nx = d.x * 2 - d.bx // And reflect the previous - ny = d.y * 2 - d.by // command's control point relative to the current point. - } else { - // or some else or nothing - nx = d.x - ny = d.y - } - return ['C', nx, ny].concat(path.slice(1)) - - case 'T': - if (pcom === 'Q' || pcom === 'T') { - // In 'T' case we have to take into account, if the previous command is Q/T. - d.qx = d.x * 2 - d.qx // And make a reflection similar - d.qy = d.y * 2 - d.qy // to case 'S'. - } else { - // or something else or nothing - d.qx = d.x - d.qy = d.y - } - return ['C'].concat( - q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]) as any[], - ) - - case 'Q': - d.qx = path[1] - d.qy = path[2] - return ['C'].concat( - q2c(d.x, d.y, path[1], path[2], path[3], path[4]) as any[], - ) - - case 'H': - return ['L'].concat(path[1], d.y) - - case 'V': - return ['L'].concat(d.x, path[1]) - - case 'L': - break - - case 'Z': - break - - default: - break - } - - return path - } - - function fixArc(pp: any[], i: number) { - if (pp[i].length > 7) { - pp[i].shift() - const pi = pp[i] - - while (pi.length) { - // if created multiple 'C's, their original seg is saved - commands[i] = 'A' - i += 1 // eslint-disable-line - pp.splice(i, 0, ['C'].concat(pi.splice(0, 6))) - } - - pp.splice(i, 1) - ii = pathArray.length - } - } - - const commands = [] // path commands of original path p - let prevCommand = '' // holder for previous path command of original path - - let ii = pathArray.length - for (let i = 0; i < ii; i += 1) { - let command = '' // temporary holder for original path command - - if (pathArray[i]) { - command = pathArray[i][0] // save current path command - } - - if (command !== 'C') { - // C is not saved yet, because it may be result of conversion - commands[i] = command // Save current path command - if (i > 0) { - prevCommand = commands[i - 1] // Get previous path command pcom - } - } - - // Previous path command is inputted to processPath - pathArray[i] = processPath(pathArray[i], attrs, prevCommand) - - if (commands[i] !== 'A' && command === 'C') { - commands[i] = 'C' // 'A' is the only command - } - - // which may produce multiple 'C's - // so we have to make sure that 'C' is also 'C' in original path - - fixArc(pathArray, i) // fixArc adds also the right amount of 'A's to pcoms - - const seg = pathArray[i] - const seglen = seg.length - - attrs.x = seg[seglen - 2] - attrs.y = seg[seglen - 1] - - attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x - attrs.by = parseFloat(seg[seglen - 3]) || attrs.y - } - - // make sure normalized path data string starts with an M segment - if (!pathArray[0][0] || pathArray[0][0] !== 'M') { - pathArray.unshift(['M', 0, 0]) - } - - return pathArray -} - -/** - * Converts provided SVG path data string into a normalized path data string. - * - * The normalization uses a restricted subset of path commands; all segments - * are translated into lineto, curveto, moveto, and closepath segments. - * - * Relative path commands are changed into their absolute counterparts, - * and chaining of coordinates is disallowed. - * - * The function will always return a valid path data string; if an input - * string cannot be normalized, 'M 0 0' is returned. - */ -export function normalizePathData(pathData: string) { - return normalize(pathData) - .map((segment: Segment) => - segment.map((item) => (typeof item === 'string' ? item : round(item, 2))), - ) - .join(',') - .split(',') - .join(' ') -} diff --git a/packages/x6/src/geometry/path/path.ts b/packages/x6/src/geometry/path/path.ts deleted file mode 100644 index 802e9cc94b9..00000000000 --- a/packages/x6/src/geometry/path/path.ts +++ /dev/null @@ -1,1414 +0,0 @@ -import { clamp, squaredLength } from '../util' -import { Line } from '../line' -import { Point } from '../point' -import { Curve } from '../curve' -import { Polyline } from '../polyline' -import { Rectangle } from '../rectangle' -import { Geometry } from '../geometry' -import { Close } from './close' -import { LineTo } from './lineto' -import { MoveTo } from './moveto' -import { CurveTo } from './curveto' -import { Segment } from './segment' -import { normalizePathData } from './normalize' -import * as Util from './util' - -export class Path extends Geometry { - protected readonly PRECISION: number = 3 - public segments: Segment[] - - protected get [Symbol.toStringTag]() { - return Path.toStringTag - } - - constructor() - constructor(line: Line) - constructor(curve: Curve) - constructor(polyline: Polyline) - constructor(segment: Segment) - constructor(segments: Segment[]) - constructor(lines: Line[]) - constructor(curves: Curve[]) - constructor( - args?: Line | Curve | Polyline | Segment | Segment[] | Line[] | Curve[], - ) { - super() - this.segments = [] - if (Array.isArray(args)) { - if (Line.isLine(args[0]) || Curve.isCurve(args[0])) { - let previousObj: Line | Curve | null = null - const arr = args as Line[] | Curve[] - arr.forEach((o: Line | Curve, i: number) => { - if (i === 0) { - this.appendSegment(Path.createSegment('M', o.start)) - } - if (previousObj != null && !previousObj.end.equals(o.start)) { - this.appendSegment(Path.createSegment('M', o.start)) - } - - if (Line.isLine(o)) { - this.appendSegment(Path.createSegment('L', o.end)) - } else if (Curve.isCurve(o)) { - this.appendSegment( - Path.createSegment('C', o.controlPoint1, o.controlPoint2, o.end), - ) - } - - previousObj = o - }) - } else { - const arr = args as Segment[] - arr.forEach((s) => { - if (s.isSegment) { - this.appendSegment(s) - } - }) - } - } else if (args != null) { - if (Line.isLine(args)) { - this.appendSegment(Path.createSegment('M', args.start)) - this.appendSegment(Path.createSegment('L', args.end)) - } else if (Curve.isCurve(args)) { - this.appendSegment(Path.createSegment('M', args.start)) - this.appendSegment( - Path.createSegment( - 'C', - args.controlPoint1, - args.controlPoint2, - args.end, - ), - ) - } else if (Polyline.isPolyline(args)) { - if (args.points && args.points.length) { - args.points.forEach((point, index) => { - const segment = - index === 0 - ? Path.createSegment('M', point) - : Path.createSegment('L', point) - this.appendSegment(segment) - }) - } - } else if (args.isSegment) { - this.appendSegment(args) - } - } - } - - get start() { - const segments = this.segments - const count = segments.length - if (count === 0) { - return null - } - - for (let i = 0; i < count; i += 1) { - const segment = segments[i] - if (segment.isVisible) { - return segment.start - } - } - - // if no visible segment, return last segment end point - return segments[count - 1].end - } - - get end() { - const segments = this.segments - const count = segments.length - if (count === 0) { - return null - } - - for (let i = count - 1; i >= 0; i -= 1) { - const segment = segments[i] - if (segment.isVisible) { - return segment.end - } - } - - // if no visible segment, return last segment end point - return segments[count - 1].end - } - - moveTo(x: number, y: number): this - moveTo(point: Point.PointLike): this - moveTo(line: Line): this - moveTo(curve: Curve): this - moveTo(point: Point.PointLike, ...points: Point.PointLike[]): this - moveTo(x: number, y: number, ...coords: number[]): this - moveTo(...args: any[]) { - return this.appendSegment(MoveTo.create.call(null, ...args)) - } - - lineTo(x: number, y: number): this - lineTo(point: Point.PointLike): this - lineTo(line: Line): this - lineTo(x: number, y: number, ...coords: number[]): this - lineTo(point: Point.PointLike, ...points: Point.PointLike[]): this - lineTo(...args: any[]) { - return this.appendSegment(LineTo.create.call(null, ...args)) - } - - curveTo( - x0: number, - y0: number, - x1: number, - y1: number, - x2: number, - y2: number, - ): this - curveTo( - x0: number, - y0: number, - x1: number, - y1: number, - x2: number, - y2: number, - ...coords: number[] - ): this - curveTo(p1: Point.PointLike, p2: Point.PointLike, p3: Point.PointLike): this - curveTo( - p1: Point.PointLike, - p2: Point.PointLike, - p3: Point.PointLike, - ...points: Point.PointLike[] - ): this - curveTo(...args: any[]) { - return this.appendSegment(CurveTo.create.call(null, ...args)) - } - - arcTo( - rx: number, - ry: number, - xAxisRotation: number, - largeArcFlag: 0 | 1, - sweepFlag: 0 | 1, - endX: number, - endY: number, - ): this - arcTo( - rx: number, - ry: number, - xAxisRotation: number, - largeArcFlag: 0 | 1, - sweepFlag: 0 | 1, - endPoint: Point.PointLike, - ): this - arcTo( - rx: number, - ry: number, - xAxisRotation: number, - largeArcFlag: 0 | 1, - sweepFlag: 0 | 1, - endX: number | Point.PointLike, - endY?: number, - ) { - const start = this.end || new Point() - const points = - typeof endX === 'number' - ? Util.arcToCurves( - start.x, - start.y, - rx, - ry, - xAxisRotation, - largeArcFlag, - sweepFlag, - endX, - endY as number, - ) - : Util.arcToCurves( - start.x, - start.y, - rx, - ry, - xAxisRotation, - largeArcFlag, - sweepFlag, - endX.x, - endX.y, - ) - - if (points != null) { - for (let i = 0, ii = points.length; i < ii; i += 6) { - this.curveTo( - points[i], - points[i + 1], - points[i + 2], - points[i + 3], - points[i + 4], - points[i + 5], - ) - } - } - return this - } - - quadTo(controlPoint: Point.PointLike, endPoint: Point.PointLike): this - quadTo( - controlPointX: number, - controlPointY: number, - endPointX: number, - endPointY: number, - ): this - quadTo( - x1: number | Point.PointLike, - y1: number | Point.PointLike, - x?: number, - y?: number, - ) { - const start = this.end || new Point() - const data = ['M', start.x, start.y] - if (typeof x1 === 'number') { - data.push('Q', x1, y1 as number, x as number, y as number) - } else { - const p = y1 as Point.PointLike - data.push(`Q`, x1.x, x1.y, p.x, p.y) - } - const path = Path.parse(data.join(' ')) - this.appendSegment(path.segments.slice(1)) - return this - } - - close() { - return this.appendSegment(Close.create()) - } - - drawPoints( - points: (Point.PointLike | Point.PointData)[], - options: Util.DrawPointsOptions = {}, - ) { - const raw = Util.drawPoints(points, options) - const sub = Path.parse(raw) - if (sub && sub.segments) { - this.appendSegment(sub.segments) - } - } - - bbox() { - const segments = this.segments - const count = segments.length - if (count === 0) { - return null - } - - let bbox - for (let i = 0; i < count; i += 1) { - const segment = segments[i] - if (segment.isVisible) { - const segmentBBox = segment.bbox() - if (segmentBBox != null) { - bbox = bbox ? bbox.union(segmentBBox) : segmentBBox - } - } - } - - if (bbox != null) { - return bbox - } - - // if the path has only invisible elements, return end point of last segment - const lastSegment = segments[count - 1] - return new Rectangle(lastSegment.end.x, lastSegment.end.y, 0, 0) - } - - appendSegment(seg: Segment | Segment[]) { - const count = this.segments.length - let previousSegment = count !== 0 ? this.segments[count - 1] : null - let currentSegment - const nextSegment = null - - if (Array.isArray(seg)) { - for (let i = 0, ii = seg.length; i < ii; i += 1) { - const segment = seg[i] - currentSegment = this.prepareSegment( - segment, - previousSegment, - nextSegment, - ) - this.segments.push(currentSegment) - previousSegment = currentSegment - } - } else if (seg != null && seg.isSegment) { - currentSegment = this.prepareSegment(seg, previousSegment, nextSegment) - this.segments.push(currentSegment) - } - return this - } - - insertSegment(index: number, seg: Segment | Segment[]) { - const count = this.segments.length - if (index < 0) { - index = count + index + 1 // eslint-disable-line - } - - if (index > count || index < 0) { - throw new Error('Index out of range.') - } - - let currentSegment - let previousSegment = null - let nextSegment = null - - if (count !== 0) { - if (index >= 1) { - previousSegment = this.segments[index - 1] - nextSegment = previousSegment.nextSegment - } else { - previousSegment = null - nextSegment = this.segments[0] - } - } - - if (!Array.isArray(seg)) { - currentSegment = this.prepareSegment(seg, previousSegment, nextSegment) - this.segments.splice(index, 0, currentSegment) - } else { - for (let i = 0, ii = seg.length; i < ii; i += 1) { - const segment = seg[i] - currentSegment = this.prepareSegment( - segment, - previousSegment, - nextSegment, - ) - this.segments.splice(index + i, 0, currentSegment) - previousSegment = currentSegment - } - } - return this - } - - removeSegment(index: number) { - const idx = this.fixIndex(index) - const removedSegment = this.segments.splice(idx, 1)[0] - const previousSegment = removedSegment.previousSegment - const nextSegment = removedSegment.nextSegment - - // link the previous and next segments together (if present) - if (previousSegment) { - previousSegment.nextSegment = nextSegment - } - - if (nextSegment) { - nextSegment.previousSegment = previousSegment - } - - if (removedSegment.isSubpathStart && nextSegment) { - this.updateSubpathStartSegment(nextSegment) - } - return removedSegment - } - - replaceSegment(index: number, seg: Segment | Segment[]) { - const idx = this.fixIndex(index) - - let currentSegment - const replacedSegment = this.segments[idx] - let previousSegment = replacedSegment.previousSegment - const nextSegment = replacedSegment.nextSegment - - let updateSubpathStart = replacedSegment.isSubpathStart - - if (!Array.isArray(seg)) { - currentSegment = this.prepareSegment(seg, previousSegment, nextSegment) - this.segments.splice(idx, 1, currentSegment) - if (updateSubpathStart && currentSegment.isSubpathStart) { - // already updated by `prepareSegment` - updateSubpathStart = false - } - } else { - this.segments.splice(index, 1) - - for (let i = 0, ii = seg.length; i < ii; i += 1) { - const segment = seg[i] - currentSegment = this.prepareSegment( - segment, - previousSegment, - nextSegment, - ) - this.segments.splice(index + i, 0, currentSegment) - previousSegment = currentSegment - - if (updateSubpathStart && currentSegment.isSubpathStart) { - updateSubpathStart = false - } - } - } - - if (updateSubpathStart && nextSegment) { - this.updateSubpathStartSegment(nextSegment) - } - } - - getSegment(index: number) { - const idx = this.fixIndex(index) - return this.segments[idx] - } - - protected fixIndex(index: number) { - const length = this.segments.length - - if (length === 0) { - throw new Error('Path has no segments.') - } - - let i = index - while (i < 0) { - i = length + i - } - - if (i >= length || i < 0) { - throw new Error('Index out of range.') - } - - return i - } - - segmentAt(ratio: number, options: Path.Options = {}) { - const index = this.segmentIndexAt(ratio, options) - if (!index) { - return null - } - - return this.getSegment(index) - } - - segmentAtLength(length: number, options: Path.Options = {}) { - const index = this.segmentIndexAtLength(length, options) - if (!index) return null - - return this.getSegment(index) - } - - segmentIndexAt(ratio: number, options: Path.Options = {}) { - if (this.segments.length === 0) { - return null - } - - const rate = clamp(ratio, 0, 1) - const opt = this.getOptions(options) - const len = this.length(opt) - const length = len * rate - return this.segmentIndexAtLength(length, opt) - } - - segmentIndexAtLength(length: number, options: Path.Options = {}) { - const count = this.segments.length - if (count === 0) { - return null - } - - let fromStart = true - if (length < 0) { - fromStart = false - length = -length // eslint-disable-line - } - - const precision = this.getPrecision(options) - const segmentSubdivisions = this.getSubdivisions(options) - - let memo = 0 - let lastVisibleIndex = null - - for (let i = 0; i < count; i += 1) { - const index = fromStart ? i : count - 1 - i - - const segment = this.segments[index] - const subdivisions = segmentSubdivisions[index] - const len = segment.length({ precision, subdivisions }) - - if (segment.isVisible) { - if (length <= memo + len) { - return index - } - lastVisibleIndex = index - } - - memo += len - } - - // If length requested is higher than the length of the path, return - // last visible segment index. If no visible segment, return null. - return lastVisibleIndex - } - - getSegmentSubdivisions(options: Path.Options = {}): Segment[][] { - const precision = this.getPrecision(options) - const segmentSubdivisions = [] - for (let i = 0, ii = this.segments.length; i < ii; i += 1) { - const segment = this.segments[i] - const subdivisions = segment.getSubdivisions({ precision }) - segmentSubdivisions.push(subdivisions) - } - - return segmentSubdivisions - } - - protected updateSubpathStartSegment(segment: Segment) { - let previous = segment.previousSegment - let current: Segment | null = segment - - while (current && !current.isSubpathStart) { - // assign previous segment's subpath start segment to this segment - if (previous != null) { - current.subpathStartSegment = previous.subpathStartSegment - } else { - current.subpathStartSegment = null - } - - previous = current - current = current.nextSegment - } - } - - protected prepareSegment( - segment: Segment, - previousSegment: Segment | null, - nextSegment: Segment | null, - ) { - segment.previousSegment = previousSegment - segment.nextSegment = nextSegment - - if (previousSegment != null) { - previousSegment.nextSegment = segment - } - - if (nextSegment != null) { - nextSegment.previousSegment = segment - } - - let updateSubpathStart: Segment | null = segment - if (segment.isSubpathStart) { - // move to - segment.subpathStartSegment = segment - updateSubpathStart = nextSegment - } - - // assign previous segment's subpath start (or self if it is a subpath start) to subsequent segments - if (updateSubpathStart != null) { - this.updateSubpathStartSegment(updateSubpathStart) - } - - return segment - } - - closestPoint(p: Point.PointLike, options: Path.Options = {}) { - const t = this.closestPointT(p, options) - if (!t) { - return null - } - - return this.pointAtT(t) - } - - closestPointLength(p: Point.PointLike, options: Path.Options = {}) { - const opts = this.getOptions(options) - const t = this.closestPointT(p, opts) - if (!t) { - return 0 - } - - return this.lengthAtT(t, opts) - } - - closestPointNormalizedLength(p: Point.PointLike, options: Path.Options = {}) { - const opts = this.getOptions(options) - const cpLength = this.closestPointLength(p, opts) - if (cpLength === 0) { - return 0 - } - - const length = this.length(opts) - if (length === 0) { - return 0 - } - - return cpLength / length - } - - closestPointT(p: Point.PointLike, options: Path.Options = {}) { - if (this.segments.length === 0) { - return null - } - - const precision = this.getPrecision(options) - const segmentSubdivisions = this.getSubdivisions(options) - - let closestPointT - let minSquaredDistance = Infinity - for (let i = 0, ii = this.segments.length; i < ii; i += 1) { - const segment = this.segments[i] - const subdivisions = segmentSubdivisions[i] - - if (segment.isVisible) { - const segmentClosestPointT = segment.closestPointT(p, { - precision, - subdivisions, - }) - const segmentClosestPoint = segment.pointAtT(segmentClosestPointT) - const squaredDistance = squaredLength(segmentClosestPoint, p) - - if (squaredDistance < minSquaredDistance) { - closestPointT = { segmentIndex: i, value: segmentClosestPointT } - minSquaredDistance = squaredDistance - } - } - } - - if (closestPointT) { - return closestPointT - } - - return { segmentIndex: this.segments.length - 1, value: 1 } - } - - closestPointTangent(p: Point.PointLike, options: Path.Options = {}) { - if (this.segments.length === 0) { - return null - } - - const precision = this.getPrecision(options) - const segmentSubdivisions = this.getSubdivisions(options) - - let closestPointTangent - let minSquaredDistance = Infinity - for (let i = 0, ii = this.segments.length; i < ii; i += 1) { - const segment = this.segments[i] - const subdivisions = segmentSubdivisions[i] - - if (segment.isDifferentiable()) { - const segmentClosestPointT = segment.closestPointT(p, { - precision, - subdivisions, - }) - const segmentClosestPoint = segment.pointAtT(segmentClosestPointT) - const squaredDistance = squaredLength(segmentClosestPoint, p) - - if (squaredDistance < minSquaredDistance) { - closestPointTangent = segment.tangentAtT(segmentClosestPointT) - minSquaredDistance = squaredDistance - } - } - } - - if (closestPointTangent) { - return closestPointTangent - } - - return null - } - - containsPoint(p: Point.PointLike, options: Path.Options = {}) { - const polylines = this.toPolylines(options) - if (!polylines) { - return false - } - - let numIntersections = 0 - for (let i = 0, ii = polylines.length; i < ii; i += 1) { - const polyline = polylines[i] - if (polyline.containsPoint(p)) { - numIntersections += 1 - } - } - - // returns `true` for odd numbers of intersections (even-odd algorithm) - return numIntersections % 2 === 1 - } - - pointAt(ratio: number, options: Path.Options = {}) { - if (this.segments.length === 0) { - return null - } - - if (ratio <= 0) { - return this.start!.clone() - } - - if (ratio >= 1) { - return this.end!.clone() - } - - const opts = this.getOptions(options) - const pathLength = this.length(opts) - const length = pathLength * ratio - - return this.pointAtLength(length, opts) - } - - pointAtLength(length: number, options: Path.Options = {}) { - if (this.segments.length === 0) { - return null - } - - if (length === 0) { - return this.start!.clone() - } - - let fromStart = true - if (length < 0) { - fromStart = false - length = -length // eslint-disable-line - } - - const precision = this.getPrecision(options) - const segmentSubdivisions = this.getSubdivisions(options) - - let lastVisibleSegment - let memo = 0 - - for (let i = 0, ii = this.segments.length; i < ii; i += 1) { - const index = fromStart ? i : ii - 1 - i - - const segment = this.segments[index] - const subdivisions = segmentSubdivisions[index] - const d = segment.length({ - precision, - subdivisions, - }) - - if (segment.isVisible) { - if (length <= memo + d) { - return segment.pointAtLength((fromStart ? 1 : -1) * (length - memo), { - precision, - subdivisions, - }) - } - - lastVisibleSegment = segment - } - - memo += d - } - - // if length requested is higher than the length of the path, - // return last visible segment endpoint - if (lastVisibleSegment) { - return fromStart ? lastVisibleSegment.end : lastVisibleSegment.start - } - - // if no visible segment, return last segment end point - const lastSegment = this.segments[this.segments.length - 1] - return lastSegment.end.clone() - } - - pointAtT(t: { segmentIndex: number; value: number }) { - const segments = this.segments - const numSegments = segments.length - if (numSegments === 0) return null // if segments is an empty array - - const segmentIndex = t.segmentIndex - if (segmentIndex < 0) return segments[0].pointAtT(0) - if (segmentIndex >= numSegments) { - return segments[numSegments - 1].pointAtT(1) - } - - const tValue = clamp(t.value, 0, 1) - return segments[segmentIndex].pointAtT(tValue) - } - - divideAt(ratio: number, options: Path.Options = {}) { - if (this.segments.length === 0) { - return null - } - - const rate = clamp(ratio, 0, 1) - const opts = this.getOptions(options) - const len = this.length(opts) - const length = len * rate - return this.divideAtLength(length, opts) - } - - divideAtLength(length: number, options: Path.Options = {}) { - if (this.segments.length === 0) { - return null - } - - let fromStart = true - if (length < 0) { - fromStart = false - length = -length // eslint-disable-line - } - - const precision = this.getPrecision(options) - const segmentSubdivisions = this.getSubdivisions(options) - - let memo = 0 - let divided - let dividedSegmentIndex - let lastValidSegment - let lastValidSegmentIndex - let t - - for (let i = 0, ii = this.segments.length; i < ii; i += 1) { - const index = fromStart ? i : ii - 1 - i - const segment = this.getSegment(index) - const subdivisions = segmentSubdivisions[index] - const opts = { precision, subdivisions } - const len = segment.length(opts) - - if (segment.isDifferentiable()) { - lastValidSegment = segment - lastValidSegmentIndex = index - - if (length <= memo + len) { - dividedSegmentIndex = index - divided = segment.divideAtLength( - (fromStart ? 1 : -1) * (length - memo), - opts, - ) - break - } - } - - memo += len - } - - if (!lastValidSegment) { - return null - } - - if (!divided) { - dividedSegmentIndex = lastValidSegmentIndex - t = fromStart ? 1 : 0 - divided = lastValidSegment.divideAtT(t) - } - - // create a copy of this path and replace the identified segment with its two divided parts: - - const pathCopy = this.clone() - const index = dividedSegmentIndex as number - pathCopy.replaceSegment(index, divided) - - const divisionStartIndex = index - let divisionMidIndex = index + 1 - let divisionEndIndex = index + 2 - - // do not insert the part if it looks like a point - if (!divided[0].isDifferentiable()) { - pathCopy.removeSegment(divisionStartIndex) - divisionMidIndex -= 1 - divisionEndIndex -= 1 - } - - // insert a Moveto segment to ensure secondPath will be valid: - const movetoEnd = pathCopy.getSegment(divisionMidIndex).start - pathCopy.insertSegment(divisionMidIndex, Path.createSegment('M', movetoEnd)) - divisionEndIndex += 1 - - // do not insert the part if it looks like a point - if (!divided[1].isDifferentiable()) { - pathCopy.removeSegment(divisionEndIndex - 1) - divisionEndIndex -= 1 - } - - // ensure that Closepath segments in secondPath will be assigned correct subpathStartSegment: - - const secondPathSegmentIndexConversion = - divisionEndIndex - divisionStartIndex - 1 - - for ( - let i = divisionEndIndex, ii = pathCopy.segments.length; - i < ii; - i += 1 - ) { - const originalSegment = this.getSegment( - i - secondPathSegmentIndexConversion, - ) - const segment = pathCopy.getSegment(i) - - if ( - segment.type === 'Z' && - !originalSegment.subpathStartSegment!.end.equals( - segment.subpathStartSegment!.end, - ) - ) { - // pathCopy segment's subpathStartSegment is different from original segment's one - // convert this Closepath segment to a Lineto and replace it in pathCopy - const convertedSegment = Path.createSegment('L', originalSegment.end) - pathCopy.replaceSegment(i, convertedSegment) - } - } - - // distribute pathCopy segments into two paths and return those: - const firstPath = new Path(pathCopy.segments.slice(0, divisionMidIndex)) - const secondPath = new Path(pathCopy.segments.slice(divisionMidIndex)) - - return [firstPath, secondPath] - } - - intersectsWithLine(line: Line, options: Path.Options = {}) { - const polylines = this.toPolylines(options) - if (polylines == null) { - return null - } - - let intersections: Point[] | null = null - for (let i = 0, ii = polylines.length; i < ii; i += 1) { - const polyline = polylines[i] - const intersection = line.intersect(polyline) - if (intersection) { - if (intersections == null) { - intersections = [] - } - if (Array.isArray(intersection)) { - intersections.push(...intersection) - } else { - intersections.push(intersection) - } - } - } - - return intersections - } - - isDifferentiable() { - for (let i = 0, ii = this.segments.length; i < ii; i += 1) { - const segment = this.segments[i] - if (segment.isDifferentiable()) { - return true - } - } - - return false - } - - isValid() { - const segments = this.segments - const isValid = segments.length === 0 || segments[0].type === 'M' - return isValid - } - - length(options: Path.Options = {}) { - if (this.segments.length === 0) { - return 0 - } - - const segmentSubdivisions = this.getSubdivisions(options) - - let length = 0 - for (let i = 0, ii = this.segments.length; i < ii; i += 1) { - const segment = this.segments[i] - const subdivisions = segmentSubdivisions[i] - length += segment.length({ subdivisions }) - } - - return length - } - - lengthAtT( - t: { segmentIndex: number; value: number }, - options: Path.Options = {}, - ) { - const count = this.segments.length - if (count === 0) { - return 0 - } - - let segmentIndex = t.segmentIndex - if (segmentIndex < 0) { - return 0 - } - - let tValue = clamp(t.value, 0, 1) - if (segmentIndex >= count) { - segmentIndex = count - 1 - tValue = 1 - } - - const precision = this.getPrecision(options) - const segmentSubdivisions = this.getSubdivisions(options) - - let length = 0 - for (let i = 0; i < segmentIndex; i += 1) { - const segment = this.segments[i] - const subdivisions = segmentSubdivisions[i] - length += segment.length({ precision, subdivisions }) - } - - const segment = this.segments[segmentIndex] - const subdivisions = segmentSubdivisions[segmentIndex] - length += segment.lengthAtT(tValue, { precision, subdivisions }) - - return length - } - - tangentAt(ratio: number, options: Path.Options = {}) { - if (this.segments.length === 0) { - return null - } - - const rate = clamp(ratio, 0, 1) - const opts = this.getOptions(options) - const len = this.length(opts) - const length = len * rate - return this.tangentAtLength(length, opts) - } - - tangentAtLength(length: number, options: Path.Options = {}) { - if (this.segments.length === 0) { - return null - } - - let fromStart = true - if (length < 0) { - fromStart = false - length = -length // eslint-disable-line - } - - const precision = this.getPrecision(options) - const segmentSubdivisions = this.getSubdivisions(options) - - let lastValidSegment - let memo = 0 - for (let i = 0, ii = this.segments.length; i < ii; i += 1) { - const index = fromStart ? i : ii - 1 - i - const segment = this.segments[index] - const subdivisions = segmentSubdivisions[index] - const len = segment.length({ precision, subdivisions }) - - if (segment.isDifferentiable()) { - if (length <= memo + len) { - return segment.tangentAtLength( - (fromStart ? 1 : -1) * (length - memo), - { - precision, - subdivisions, - }, - ) - } - - lastValidSegment = segment - } - - memo += len - } - - // if length requested is higher than the length of the path, return tangent of endpoint of last valid segment - if (lastValidSegment) { - const t = fromStart ? 1 : 0 - return lastValidSegment.tangentAtT(t) - } - - // if no valid segment, return null - return null - } - - tangentAtT(t: { segmentIndex: number; value: number }) { - const count = this.segments.length - if (count === 0) { - return null - } - - const segmentIndex = t.segmentIndex - if (segmentIndex < 0) { - return this.segments[0].tangentAtT(0) - } - - if (segmentIndex >= count) { - return this.segments[count - 1].tangentAtT(1) - } - - const tValue = clamp(t.value, 0, 1) - return this.segments[segmentIndex].tangentAtT(tValue) - } - - protected getPrecision(options: Path.Options = {}) { - return options.precision == null ? this.PRECISION : options.precision - } - - protected getSubdivisions(options: Path.Options = {}) { - if (options.segmentSubdivisions == null) { - const precision = this.getPrecision(options) - return this.getSegmentSubdivisions({ precision }) - } - return options.segmentSubdivisions - } - - protected getOptions(options: Path.Options = {}) { - const precision = this.getPrecision(options) - const segmentSubdivisions = this.getSubdivisions(options) - return { precision, segmentSubdivisions } - } - - toPoints(options: Path.Options = {}) { - const segments = this.segments - const count = segments.length - if (count === 0) { - return null - } - - const segmentSubdivisions = this.getSubdivisions(options) - const points = [] - let partialPoints = [] - - for (let i = 0; i < count; i += 1) { - const segment = segments[i] - if (segment.isVisible) { - const divisions = segmentSubdivisions[i] - if (divisions.length > 0) { - // eslint-disable-next-line no-loop-func - divisions.forEach((c) => partialPoints.push(c.start)) - } else { - partialPoints.push(segment.start) - } - } else if (partialPoints.length > 0) { - partialPoints.push(segments[i - 1].end) - points.push(partialPoints) - partialPoints = [] - } - } - - if (partialPoints.length > 0) { - partialPoints.push(this.end!) - points.push(partialPoints) - } - - return points - } - - toPolylines(options: Path.Options = {}) { - const points = this.toPoints(options) - if (!points) { - return null - } - - return points.map((arr) => new Polyline(arr)) - } - - scale(sx: number, sy: number, origin?: Point.PointLike) { - this.segments.forEach((s) => s.scale(sx, sy, origin)) - return this - } - - rotate(angle: number, origin?: Point.PointLike | Point.PointData) { - this.segments.forEach((segment) => segment.rotate(angle, origin)) - return this - } - - translate(tx: number, ty: number): this - translate(p: Point.PointLike): this - translate(tx: number | Point.PointLike, ty?: number) { - if (typeof tx === 'number') { - this.segments.forEach((s) => s.translate(tx, ty as number)) - } else { - this.segments.forEach((s) => s.translate(tx)) - } - return this - } - - clone() { - const path = new Path() - this.segments.forEach((s) => path.appendSegment(s.clone())) - return path - } - - equals(p: Path) { - if (p == null) { - return false - } - - const segments = this.segments - const otherSegments = p.segments - - const count = segments.length - if (otherSegments.length !== count) { - return false - } - - for (let i = 0; i < count; i += 1) { - const a = segments[i] - const b = otherSegments[i] - if (a.type !== b.type || !a.equals(b)) { - return false - } - } - - return true - } - - toJSON() { - return this.segments.map((s) => s.toJSON()) - } - - serialize() { - if (!this.isValid()) { - throw new Error('Invalid path segments.') - } - - return this.segments.map((s) => s.serialize()).join(' ') - } - - toString() { - return this.serialize() - } -} - -export namespace Path { - export const toStringTag = `X6.Geometry.${Path.name}` - - export function isPath(instance: any): instance is Path { - if (instance == null) { - return false - } - - if (instance instanceof Path) { - return true - } - - const tag = instance[Symbol.toStringTag] - const path = instance as Path - - if ( - (tag == null || tag === toStringTag) && - Array.isArray(path.segments) && - typeof path.moveTo === 'function' && - typeof path.lineTo === 'function' && - typeof path.curveTo === 'function' - ) { - return true - } - - return false - } -} - -export namespace Path { - export interface Options { - precision?: number | null - segmentSubdivisions?: Segment[][] | null - } -} - -export namespace Path { - export function parse(pathData: string) { - if (!pathData) { - return new Path() - } - - const path = new Path() - - const commandRe = - /(?:[a-zA-Z] *)(?:(?:-?\d+(?:\.\d+)?(?:e[-+]?\d+)? *,? *)|(?:-?\.\d+ *,? *))+|(?:[a-zA-Z] *)(?! |\d|-|\.)/g - const commands = normalize(pathData).match(commandRe) - if (commands != null) { - for (let i = 0, ii = commands.length; i < ii; i += 1) { - const command = commands[i] - const argRe = - /(?:[a-zA-Z])|(?:(?:-?\d+(?:\.\d+)?(?:e[-+]?\d+)?))|(?:(?:-?\.\d+))/g - // args = [type, coordinate1, coordinate2...] - const args = command.match(argRe) - if (args != null) { - const type = args[0] - const coords = args.slice(1).map((a) => +a) - const segment = createSegment.call(null, type, ...coords) - path.appendSegment(segment) - } - } - } - - return path - } - - export function createSegment(type: 'M', x: number, y: number): MoveTo - export function createSegment(type: 'M', point: Point.PointLike): MoveTo - export function createSegment(type: 'M', line: Line): MoveTo - export function createSegment(type: 'M', curve: Curve): MoveTo - export function createSegment( - type: 'M', - point: Point.PointLike, - ...points: Point.PointLike[] - ): Segment[] - export function createSegment( - type: 'M', - x: number, - y: number, - ...coords: number[] - ): Segment[] - export function createSegment(type: 'L', x: number, y: number): LineTo - export function createSegment(type: 'L', point: Point.PointLike): LineTo - export function createSegment(type: 'L', line: Line): LineTo - export function createSegment( - type: 'L', - point: Point.PointLike, - ...points: Point.PointLike[] - ): LineTo[] - export function createSegment( - type: 'L', - x: number, - y: number, - ...coords: number[] - ): LineTo[] - export function createSegment( - type: 'C', - x0: number, - y0: number, - x1: number, - y1: number, - x2: number, - y2: number, - ): CurveTo - export function createSegment( - type: 'C', - x0: number, - y0: number, - x1: number, - y1: number, - x2: number, - y2: number, - ...coords: number[] - ): CurveTo[] - export function createSegment( - type: 'C', - p1: Point.PointLike, - p2: Point.PointLike, - p3: Point.PointLike, - ): CurveTo - export function createSegment( - type: 'C', - p1: Point.PointLike, - p2: Point.PointLike, - p3: Point.PointLike, - ...points: Point.PointLike[] - ): CurveTo[] - export function createSegment(type: 'Z' | 'z'): Close - export function createSegment( - type: 'M' | 'L' | 'C' | 'Z' | 'z', - ...args: any[] - ): - | MoveTo - | MoveTo[] - | LineTo - | LineTo[] - | CurveTo - | CurveTo[] - | Close - | Segment - | Segment[] { - if (type === 'M') { - return MoveTo.create.call(null, ...args) - } - - if (type === 'L') { - return LineTo.create.call(null, ...args) - } - - if (type === 'C') { - return CurveTo.create.call(null, ...args) - } - - if (type === 'z' || type === 'Z') { - return Close.create() - } - - throw new Error(`Invalid path segment type "${type}"`) - } -} - -export namespace Path { - export const normalize = normalizePathData - export const isValid = Util.isValid - export const drawArc = Util.drawArc - export const drawPoints = Util.drawPoints - export const arcToCurves = Util.arcToCurves -} diff --git a/packages/x6/src/geometry/path/segment.ts b/packages/x6/src/geometry/path/segment.ts deleted file mode 100644 index 76f150e2429..00000000000 --- a/packages/x6/src/geometry/path/segment.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Line } from '../line' -import { Point } from '../point' -import { Rectangle } from '../rectangle' -import { Geometry } from '../geometry' - -export abstract class Segment extends Geometry { - isVisible = true - isSegment = true - isSubpathStart = false - nextSegment: Segment | null - previousSegment: Segment | null - subpathStartSegment: Segment | null - protected endPoint: Point - - get end() { - return this.endPoint - } - - get start() { - if (this.previousSegment == null) { - throw new Error( - 'Missing previous segment. (This segment cannot be the ' + - 'first segment of a path, or segment has not yet been ' + - 'added to a path.)', - ) - } - - return this.previousSegment.end - } - - abstract get type(): string - - abstract bbox(): Rectangle | null - - abstract closestPoint(p: Point.PointLike | Point.PointData): Point - - abstract closestPointLength(p: Point.PointLike | Point.PointData): number - - abstract closestPointNormalizedLength( - p: Point.PointLike | Point.PointData, - ): number - - closestPointT( - p: Point.PointLike | Point.PointData, - options?: Segment.Options, // eslint-disable-line - ) { - if (this.closestPointNormalizedLength) { - return this.closestPointNormalizedLength(p) - } - - throw new Error( - 'Neither `closestPointT` nor `closestPointNormalizedLength` method is implemented.', - ) - } - - abstract closestPointTangent( - p: Point.PointLike | Point.PointData, - ): Line | null - - abstract length(options?: Segment.Options): number - - // eslint-disable-next-line - lengthAtT(t: number, options?: Segment.Options) { - if (t <= 0) { - return 0 - } - - const length = this.length() - if (t >= 1) { - return length - } - - return length * t - } - - abstract divideAt( - ratio: number, - options?: Segment.Options, - ): [Segment, Segment] - - abstract divideAtLength( - length: number, - options?: Segment.Options, - ): [Segment, Segment] - - divideAtT(t: number) { - if (this.divideAt) { - return this.divideAt(t) - } - - throw new Error('Neither `divideAtT` nor `divideAt` method is implemented.') - } - - abstract getSubdivisions(options?: Segment.Options): Segment[] - - abstract pointAt(ratio: number): Point - - abstract pointAtLength(length: number, options?: Segment.Options): Point - - pointAtT(t: number): Point { - if (this.pointAt) { - return this.pointAt(t) - } - - throw new Error('Neither `pointAtT` nor `pointAt` method is implemented.') - } - - abstract tangentAt(ratio: number): Line | null - - abstract tangentAtLength( - length: number, - options?: Segment.Options, - ): Line | null - - tangentAtT(t: number): Line | null { - if (this.tangentAt) { - return this.tangentAt(t) - } - - throw new Error( - 'Neither `tangentAtT` nor `tangentAt` method is implemented.', - ) - } - - abstract isDifferentiable(): boolean - - abstract clone(): Segment -} - -export namespace Segment { - export interface Options { - precision?: number - subdivisions?: Segment[] - } -} diff --git a/packages/x6/src/geometry/path/util.ts b/packages/x6/src/geometry/path/util.ts deleted file mode 100644 index 3410cd876d1..00000000000 --- a/packages/x6/src/geometry/path/util.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { Point } from '../point' - -const regexSupportedData = new RegExp(`^[\\s\\dLMCZz,.]*$`) - -export function isValid(data: any) { - if (typeof data !== 'string') { - return false - } - - return regexSupportedData.test(data) -} - -/** - * Returns the remainder of division of `n` by `m`. You should use this - * instead of the built-in operation as the built-in operation does not - * properly handle negative numbers. - */ -function mod(n: number, m: number) { - return ((n % m) + m) % m -} - -export interface DrawPointsOptions { - round?: number - initialMove?: boolean - close?: boolean - exclude?: number[] -} - -function draw( - points: Point.PointLike[], - round?: number, - initialMove?: boolean, - close?: boolean, - exclude?: number[], -) { - const data: (string | number)[] = [] - const end = points[points.length - 1] - const rounded = round != null && round > 0 - const arcSize = round || 0 - - // Adds virtual waypoint in the center between start and end point - if (close && rounded) { - points = points.slice() // eslint-disable-line - const p0 = points[0] - const wp = new Point(end.x + (p0.x - end.x) / 2, end.y + (p0.y - end.y) / 2) - points.splice(0, 0, wp) - } - - let pt = points[0] - let i = 1 - - // Draws the line segments - if (initialMove) { - data.push('M', pt.x, pt.y) - } else { - data.push('L', pt.x, pt.y) - } - - while (i < (close ? points.length : points.length - 1)) { - let tmp = points[mod(i, points.length)] - let dx = pt.x - tmp.x - let dy = pt.y - tmp.y - - if ( - rounded && - (dx !== 0 || dy !== 0) && - (exclude == null || exclude.indexOf(i - 1) < 0) - ) { - // Draws a line from the last point to the current - // point with a spacing of size off the current point - // into direction of the last point - let dist = Math.sqrt(dx * dx + dy * dy) - const nx1 = (dx * Math.min(arcSize, dist / 2)) / dist - const ny1 = (dy * Math.min(arcSize, dist / 2)) / dist - - const x1 = tmp.x + nx1 - const y1 = tmp.y + ny1 - data.push('L', x1, y1) - - // Draws a curve from the last point to the current - // point with a spacing of size off the current point - // into direction of the next point - let next = points[mod(i + 1, points.length)] - - // Uses next non-overlapping point - while ( - i < points.length - 2 && - Math.round(next.x - tmp.x) === 0 && - Math.round(next.y - tmp.y) === 0 - ) { - next = points[mod(i + 2, points.length)] - i += 1 - } - - dx = next.x - tmp.x - dy = next.y - tmp.y - - dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)) - const nx2 = (dx * Math.min(arcSize, dist / 2)) / dist - const ny2 = (dy * Math.min(arcSize, dist / 2)) / dist - - const x2 = tmp.x + nx2 - const y2 = tmp.y + ny2 - - data.push('Q', tmp.x, tmp.y, x2, y2) - tmp = new Point(x2, y2) - } else { - data.push('L', tmp.x, tmp.y) - } - - pt = tmp - i += 1 - } - - if (close) { - data.push('Z') - } else { - data.push('L', end.x, end.y) - } - - return data.map((v) => (typeof v === 'string' ? v : +v.toFixed(3))).join(' ') -} - -export function drawPoints( - points: (Point.PointLike | Point.PointData)[], - options: DrawPointsOptions = {}, -) { - const pts: Point.PointLike[] = [] - if (points && points.length) { - points.forEach((p) => { - if (Array.isArray(p)) { - pts.push({ x: p[0], y: p[1] }) - } else { - pts.push({ x: p.x, y: p.y }) - } - }) - } - - return draw( - pts, - options.round, - options.initialMove == null || options.initialMove, - options.close, - options.exclude, - ) -} - -/** - * Converts the given arc to a series of curves. - */ -export function arcToCurves( - x0: number, - y0: number, - r1: number, - r2: number, - angle = 0, - largeArcFlag = 0, - sweepFlag = 0, - x: number, - y: number, -) { - if (r1 === 0 || r2 === 0) { - return [] - } - - x -= x0 // eslint-disable-line - y -= y0 // eslint-disable-line - r1 = Math.abs(r1) // eslint-disable-line - r2 = Math.abs(r2) // eslint-disable-line - - const ctx = -x / 2 - const cty = -y / 2 - const cpsi = Math.cos((angle * Math.PI) / 180) - const spsi = Math.sin((angle * Math.PI) / 180) - const rxd = cpsi * ctx + spsi * cty - const ryd = -1 * spsi * ctx + cpsi * cty - const rxdd = rxd * rxd - const rydd = ryd * ryd - const r1x = r1 * r1 - const r2y = r2 * r2 - const lamda = rxdd / r1x + rydd / r2y - - let sds - - if (lamda > 1) { - r1 = Math.sqrt(lamda) * r1 // eslint-disable-line - r2 = Math.sqrt(lamda) * r2 // eslint-disable-line - sds = 0 - } else { - let seif = 1 - if (largeArcFlag === sweepFlag) { - seif = -1 - } - - sds = - seif * - Math.sqrt( - (r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd), - ) - } - - const txd = (sds * r1 * ryd) / r2 - const tyd = (-1 * sds * r2 * rxd) / r1 - const tx = cpsi * txd - spsi * tyd + x / 2 - const ty = spsi * txd + cpsi * tyd + y / 2 - - let rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1) - let s1 = rad >= 0 ? rad : 2 * Math.PI + rad - rad = - Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - let dr = rad >= 0 ? rad : 2 * Math.PI + rad - - if (sweepFlag === 0 && dr > 0) { - dr -= 2 * Math.PI - } else if (sweepFlag !== 0 && dr < 0) { - dr += 2 * Math.PI - } - - const sse = (dr * 2) / Math.PI - const seg = Math.ceil(sse < 0 ? -1 * sse : sse) - const segr = dr / seg - const t = - ((8 / 3) * Math.sin(segr / 4) * Math.sin(segr / 4)) / Math.sin(segr / 2) - const cpsir1 = cpsi * r1 - const cpsir2 = cpsi * r2 - const spsir1 = spsi * r1 - const spsir2 = spsi * r2 - - let mc = Math.cos(s1) - let ms = Math.sin(s1) - let x2 = -t * (cpsir1 * ms + spsir2 * mc) - let y2 = -t * (spsir1 * ms - cpsir2 * mc) - let x3 = 0 - let y3 = 0 - - const result = [] - - for (let n = 0; n < seg; n += 1) { - s1 += segr - mc = Math.cos(s1) - ms = Math.sin(s1) - - x3 = cpsir1 * mc - spsir2 * ms + tx - y3 = spsir1 * mc + cpsir2 * ms + ty - const dx = -t * (cpsir1 * ms + spsir2 * mc) - const dy = -t * (spsir1 * ms - cpsir2 * mc) - - // CurveTo updates x0, y0 so need to restore it - const index = n * 6 - result[index] = Number(x2 + x0) - result[index + 1] = Number(y2 + y0) - result[index + 2] = Number(x3 - dx + x0) - result[index + 3] = Number(y3 - dy + y0) - result[index + 4] = Number(x3 + x0) - result[index + 5] = Number(y3 + y0) - - x2 = x3 + dx - y2 = y3 + dy - } - - return result.map((num) => +num.toFixed(2)) -} - -export function drawArc( - startX: number, - startY: number, - rx: number, - ry: number, - xAxisRotation = 0, - largeArcFlag: 0 | 1 = 0, - sweepFlag: 0 | 1 = 0, - stopX: number, - stopY: number, -) { - const data: (string | number)[] = [] - const points = arcToCurves( - startX, - startY, - rx, - ry, - xAxisRotation, - largeArcFlag, - sweepFlag, - stopX, - stopY, - ) - - if (points != null) { - for (let i = 0, ii = points.length; i < ii; i += 6) { - data.push( - 'C', - points[i], - points[i + 1], - points[i + 2], - points[i + 3], - points[i + 4], - points[i + 5], - ) - } - } - - return data.join(' ') -} diff --git a/packages/x6/src/geometry/point.test.ts b/packages/x6/src/geometry/point.test.ts deleted file mode 100644 index f94ff893062..00000000000 --- a/packages/x6/src/geometry/point.test.ts +++ /dev/null @@ -1,432 +0,0 @@ -import { Point } from './point' - -describe('point', () => { - describe('#constructor', () => { - it('should create a point instance', () => { - expect(new Point()).toBeInstanceOf(Point) - expect(new Point(1)).toBeInstanceOf(Point) - expect(new Point(1, 2)).toBeInstanceOf(Point) - expect(new Point(1, 2).x).toEqual(1) - expect(new Point(1, 2).y).toEqual(2) - expect(new Point().equals(new Point(0, 0))) - }) - }) - - describe('#Point.random', () => { - it('should create random point', () => { - const p = Point.random(1, 5, 2, 6) - expect(p.x >= 1 && p.x <= 5).toBe(true) - expect(p.y >= 2 && p.y <= 6).toBe(true) - }) - }) - - describe('#Point.isPointLike', () => { - it('should return true when the given object is a point-like object', () => { - expect(Point.isPointLike({ x: 1, y: 2 })).toBeTrue() - expect(Point.isPointLike({ x: 1, y: 2, z: 10 })).toBeTrue() - expect(Point.isPointLike({ x: 1, y: 2, z: 10, s: 's' })).toBeTrue() - }) - - it('should return false when the given object is a point-like object', () => { - expect(Point.isPointLike({ x: 1 })).toBeFalse() - expect(Point.isPointLike({ y: 2 })).toBeFalse() - expect(Point.isPointLike({})).toBeFalse() - expect(Point.isPointLike(null)).toBeFalse() - expect(Point.isPointLike(false)).toBeFalse() - expect(Point.isPointLike(1)).toBeFalse() - expect(Point.isPointLike('s')).toBeFalse() - }) - }) - - describe('#Point.isPointData', () => { - it('should return true when the given object is a point-data array', () => { - expect(Point.isPointData([1, 2])).toBeTrue() - }) - - it('should return false when the given object is a point-data array', () => { - expect(Point.isPointData({ x: 1, y: 2 })).toBeFalse() - expect(Point.isPointData([1])).toBeFalse() - expect(Point.isPointData([1, 2, 3])).toBeFalse() - expect(Point.isPointData(null)).toBeFalse() - expect(Point.isPointData(false)).toBeFalse() - expect(Point.isPointData(1)).toBeFalse() - expect(Point.isPointData('s')).toBeFalse() - }) - }) - - describe('#Point.toJSON', () => { - it('should conver the given point to json', () => { - expect(Point.toJSON([1, 2])).toEqual({ x: 1, y: 2 }) - expect(Point.toJSON({ x: 1, y: 2 })).toEqual({ x: 1, y: 2 }) - expect(Point.toJSON(new Point(1, 2))).toEqual({ x: 1, y: 2 }) - }) - }) - - describe('#Point.equals', () => { - it('should return true when the given two points are equal', () => { - const p1 = new Point(1, 2) - expect(Point.equals(p1, p1)).toBeTrue() - expect(Point.equals(p1, { x: 1, y: 2 })).toBeTrue() - }) - - it('should return false when the given two points are not equal', () => { - const p1 = new Point(1, 2) - const p2 = new Point(2, 2) - expect(Point.equals(p1, p2)).toBeFalse() - expect(Point.equals(p1, null as any)).toBeFalse() - expect(Point.equals(p1, { x: 2, y: 2 })).toBeFalse() - }) - }) - - describe('#Point.equalPoints', () => { - it('should return true when the given points array are equal', () => { - const p1 = new Point(1, 2) - expect(Point.equalPoints([p1], [p1])).toBeTrue() - expect(Point.equalPoints([p1], [{ x: 1, y: 2 }])).toBeTrue() - }) - - it('should return false when the given points array are not equal', () => { - const p1 = new Point(1, 2) - const p2 = new Point(2, 2) - expect(Point.equalPoints([p1], [p2])).toBeFalse() - expect(Point.equalPoints(null as any, [p2])).toBeFalse() - expect(Point.equalPoints([p1], null as any)).toBeFalse() - expect(Point.equalPoints([p1, p2], [p2])).toBeFalse() - expect(Point.equalPoints([p1, p2], [p2, p1])).toBeFalse() - expect(Point.equalPoints([p1], [{ x: 2, y: 2 }])).toBeFalse() - }) - }) - - describe('#round', () => { - it('should round x and y properties to the given precision', () => { - const point = new Point(17.231, 4.01) - point.round(2) - expect(point.serialize()).toEqual('17.23 4.01') - point.round(0) - expect(point.serialize()).toEqual('17 4') - }) - }) - - describe('#add', () => { - it('should add x and y width the given amount', () => { - const source = new Point(4, 17) - const target = new Point(20, 20) - - expect(source.clone().add(16, 3)).toEqual(target) - expect(source.clone().add([16, 3])).toEqual(target) - expect(source.clone().add({ x: 16, y: 3 })).toEqual(target) - }) - }) - - describe('#update', () => { - it('should update the values of x and y', () => { - const source = new Point(4, 17) - const target = new Point(16, 24) - expect(source.clone().update(16, 24)).toEqual(target) - expect(source.clone().update([16, 24])).toEqual(target) - expect(source.clone().update({ x: 16, y: 24 })).toEqual(target) - expect(source.clone().update(target)).toEqual(target) - }) - }) - - describe('#translate', () => { - it('should translate x and y by adding the given dx and dy values respectively', () => { - const point = new Point(0, 0) - point.translate(2, 3) - expect(point.toJSON()).toEqual({ x: 2, y: 3 }) - point.translate(new Point(-2, 4)) - expect(point.toJSON()).toEqual({ x: 0, y: 7 }) - }) - }) - - describe('#rotate', () => { - const rotate = (p: Point, angle: number, center?: Point) => - p.clone().rotate(angle, center).round(3).serialize() - - it('should return a rotated version of self', () => { - const point = new Point(5, 5) - let angle - - const zeroPoint = new Point(0, 0) - const arbitraryPoint = new Point(14, 6) - - angle = 0 - expect(rotate(point, angle)).toEqual('5 5') - expect(rotate(point, angle, zeroPoint)).toEqual('5 5') - expect(rotate(point, angle, point)).toEqual('5 5') - expect(rotate(point, angle, arbitraryPoint)).toEqual('5 5') - - angle = 154 - expect(rotate(point, angle)).toEqual('-2.302 -6.686') - expect(rotate(point, angle, zeroPoint)).toEqual('-2.302 -6.686') - expect(rotate(point, angle, point)).toEqual('5 5') - expect(rotate(point, angle, arbitraryPoint)).toEqual('21.651 10.844') - }) - }) - - describe('#scale', () => { - it('should scale point with the given amount', () => { - expect(new Point(20, 30).scale(2, 3)).toEqual(new Point(40, 90)) - }) - - it('should scale point with the given amount and center ', () => { - expect(new Point(20, 30).scale(2, 3, new Point(40, 45))).toEqual( - new Point(0, 0), - ) - }) - }) - - describe('#closest', () => { - it('should return the closest point', () => { - const a = new Point(10, 10) - const b = { x: 20, y: 20 } - const c = { x: 30, y: 30 } - - expect(a.closest([])).toBeNull() - - expect(a.closest([b])).toBeInstanceOf(Point) - expect(a.closest([b])!.toJSON()).toEqual(b) - expect(a.closest([b, c])!.toJSON()).toEqual(b) - expect(a.closest([b, c])!.toJSON()).toEqual(b) - }) - }) - - describe('#distance', () => { - it('should return the distance between me and the given point', () => { - const source = new Point(1, 2) - const target = new Point(4, 6) - - expect(source.distance(target)).toEqual(5) - }) - }) - - describe('#squaredDistance', () => { - it('should return the squared distance between me and the given point', () => { - const source = new Point(1, 2) - const target = new Point(4, 6) - - expect(source.squaredDistance(target)).toEqual(25) - }) - }) - - describe('#manhattanDistance', () => { - it('should return the manhattan distance between me and the given point', () => { - const source = new Point(1, 2) - const target = new Point(4, 6) - - expect(source.manhattanDistance(target)).toEqual(7) - }) - }) - - describe('#magnitude', () => { - it('should return the magnitude of the given point', () => { - expect(new Point(3, 4).magnitude()).toEqual(5) - }) - - it('should return `0.01` when the given point is `{0, 0}`', () => { - expect(new Point(0, 0).magnitude()).toEqual(0.01) - }) - }) - - describe('#theta', () => { - it('should return the angle between me and p and the x-axis.', () => { - const me = new Point(1, 1) - - expect(me.theta(me)).toBe(-0) - expect(me.theta(new Point(2, 1))).toBe(-0) - expect(me.theta(new Point(2, 0))).toBe(45) - expect(me.theta(new Point(1, 0))).toBe(90) - expect(me.theta(new Point(0, 0))).toBe(135) - expect(me.theta(new Point(0, 1))).toBe(180) - expect(me.theta(new Point(0, 2))).toBe(225) - expect(me.theta(new Point(1, 2))).toBe(270) - expect(me.theta(new Point(2, 2))).toBe(315) - }) - }) - - describe('#angleBetween', () => { - it('should returns the angle between vector from me to `p1` and the vector from me to `p2`.', () => { - const me = new Point(1, 2) - const p1 = new Point(2, 4) - const p2 = new Point(4, 3) - - const PRECISION = 10 - - expect(me.angleBetween(me, me)).toBeNaN() - expect(me.angleBetween(p1, me)).toBeNaN() - expect(me.angleBetween(me, p2)).toBeNaN() - expect(me.angleBetween(p1, p2).toFixed(PRECISION)).toBe('45.0000000000') - expect(me.angleBetween(p2, p1).toFixed(PRECISION)).toBe('315.0000000000') - }) - }) - - describe('#changeInAngle', () => { - it('should return the change in angle from my previous position `-dx, -dy` to my new position relative to origin point', () => { - const p = new Point(1, 1) - expect(p.changeInAngle(1, 0)).toEqual(-45) - }) - - it('should return the change in angle from my previous position `-dx, -dy` to my new position relative to `ref` point', () => { - const p = new Point(2, 2) - expect(p.changeInAngle(1, 0, { x: 1, y: 1 })).toEqual(-45) - }) - }) - - describe('#adhereToRect', () => { - it('should return `p` when `p` is contained in `rect`', () => { - const p = new Point(2, 2) - const rect = { x: 1, y: 1, width: 4, height: 4 } - expect(p.adhereToRect(rect).equals(p)).toBeTrue() - }) - - it('should adhere to target `rect` when `p` is outside of `rect`', () => { - const p = new Point(2, 8) - const rect = { x: 1, y: 1, width: 4, height: 4 } - expect(p.adhereToRect(rect).equals({ x: 2, y: 5 })).toBeTrue() - }) - }) - - describe('#bearing', () => { - it('should return the bearing between me and the given point.', () => { - const p = new Point(2, 8) - expect(p.bearing(new Point())).toEqual('S') - }) - }) - - describe('#vectorAngle', () => { - it('should return the angle between the vector from `0,0` to me and the vector from `0,0` to `p`', () => { - const p0 = new Point(1, 2) - const p = new Point(3, 1) - const zero = new Point(0, 0) - - const PRECISION = 10 - - expect(zero.vectorAngle(zero)).toBeNaN() - expect(p0.vectorAngle(zero)).toBeNaN() - expect(p.vectorAngle(zero)).toBeNaN() - expect(zero.vectorAngle(p0)).toBeNaN() - expect(zero.vectorAngle(p)).toBeNaN() - expect(p0.vectorAngle(p).toFixed(PRECISION)).toBe('45.0000000000') - expect(p.vectorAngle(p0).toFixed(PRECISION)).toBe('315.0000000000') - }) - }) - - describe('#diff', () => { - it('should return the diff as a point with the given point', () => { - expect(new Point(0, 10).diff(4, 8)).toEqual(new Point(-4, 2)) - expect(new Point(5, 8).diff({ x: 5, y: 10 })).toEqual(new Point(0, -2)) - }) - }) - - describe('#lerp', () => { - it('should return an interpolation between me and the given point `p`', () => { - expect(new Point(1, 1).lerp({ x: 3, y: 3 }, 0.5)).toEqual(new Point(2, 2)) - }) - }) - - describe('#normalize', () => { - it('should scale x and y such that the distance between the point and the origin (0,0) is equal to the given length', () => { - expect(new Point(0, 10).normalize(20).serialize()).toEqual('0 20') - expect(new Point(2, 0).normalize(4).serialize()).toEqual('4 0') - }) - }) - - describe('#move', () => { - it('should move the point on a line that leads to another point `ref` by a certain `distance`.', () => { - expect(new Point(1, 1).move({ x: 1, y: 0 }, 5).round(0)).toEqual( - new Point(1, 6), - ) - }) - }) - - describe('#reflection', () => { - it('should return a point that is the reflection of me with the center of inversion in `ref` point.', () => { - expect(new Point(1, 0).reflection({ x: 1, y: 1 }).round(0)).toEqual( - new Point(1, 2), - ) - }) - }) - - describe('#cross', () => { - it('should return the cross product of the vector from me to `p1` and the vector from me to `p2`', () => { - const p0 = new Point(3, 15) - const p1 = new Point(4, 17) - const p2 = new Point(2, 10) - - expect(p0.cross(p1, p2)).toBe(3) - expect(p0.cross(p2, p1)).toBe(-3) - }) - - it('shoule return `NAN` when any of the given point is null', () => { - expect(new Point().cross(null as any, new Point(1, 2))).toBeNaN() - }) - }) - - describe('#dot', () => { - it('should return the dot product of `p`', () => { - const p1 = new Point(4, 17) - const p2 = new Point(2, 10) - expect(p1.dot(p2)).toBe(178) - expect(p2.dot(p1)).toBe(178) - }) - }) - - describe('#equals', () => { - it('should return the true when have same coord', () => { - const p1 = new Point(4, 17) - const p2 = p1.clone() - expect(p1.equals(p2)).toBe(true) - }) - }) - - describe('#toJSON', () => { - it('should return json-object', () => { - const p1 = new Point(4, 17) - const p2 = p1.toJSON() - expect(p1.equals(p2)).toBe(true) - }) - }) - - describe('#toPolar', () => { - it('should convert rectangular to polar coordinates.', () => { - const p = new Point(4, 3).toPolar() - expect(p.x).toBe(5) - }) - }) - - describe('#fromPolar', () => { - it('should convert polar to rectangular coordinates.', () => { - const p1 = new Point(4, 3).toPolar() - const p2 = Point.fromPolar(p1.x, p1.y) - expect(Math.round(p2.x)).toBe(4) - expect(Math.round(p2.y)).toBe(3) - - const p3 = new Point(-4, 3).toPolar() - const p4 = Point.fromPolar(p3.x, p3.y) - expect(Math.round(p4.x)).toBe(-4) - expect(Math.round(p4.y)).toBe(3) - - const p5 = new Point(4, -3).toPolar() - const p6 = Point.fromPolar(p5.x, p5.y) - expect(Math.round(p6.x)).toBe(4) - expect(Math.round(p6.y)).toBe(-3) - - const p7 = new Point(-4, -3).toPolar() - const p8 = Point.fromPolar(p7.x, p7.y) - expect(Math.round(p8.x)).toBe(-4) - expect(Math.round(p8.y)).toBe(-3) - }) - }) - - describe('#snapToGrid', () => { - it('should snap to grid', () => { - const p1 = new Point(4, 17) - const p2 = p1.clone().snapToGrid(10) - const p3 = p1.clone().snapToGrid(3, 5) - expect(p2.x).toBe(0) - expect(p2.y).toBe(20) - expect(p3.x).toBe(3) - expect(p3.y).toBe(15) - }) - }) -}) diff --git a/packages/x6/src/geometry/point.ts b/packages/x6/src/geometry/point.ts deleted file mode 100644 index 944404abba2..00000000000 --- a/packages/x6/src/geometry/point.ts +++ /dev/null @@ -1,595 +0,0 @@ -import * as util from './util' -import { Angle } from './angle' -import { Geometry } from './geometry' -import { Rectangle } from './rectangle' - -export class Point extends Geometry implements Point.PointLike { - public x: number - public y: number - - protected get [Symbol.toStringTag]() { - return Point.toStringTag - } - - constructor(x?: number, y?: number) { - super() - this.x = x == null ? 0 : x - this.y = y == null ? 0 : y - } - - /** - * Rounds the point to the given precision. - */ - round(precision = 0) { - this.x = util.round(this.x, precision) - this.y = util.round(this.y, precision) - return this - } - - add(x: number, y: number): this - add(p: Point.PointLike | Point.PointData): this - add(x: number | Point.PointLike | Point.PointData, y?: number): this { - const p = Point.create(x, y) - this.x += p.x - this.y += p.y - return this - } - - /** - * Update the point's `x` and `y` coordinates with new values and return the - * point itself. Useful for chaining. - */ - update(x: number, y: number): this - update(p: Point.PointLike | Point.PointData): this - update(x: number | Point.PointLike | Point.PointData, y?: number): this { - const p = Point.create(x, y) - this.x = p.x - this.y = p.y - return this - } - - translate(dx: number, dy: number): this - translate(p: Point.PointLike | Point.PointData): this - translate(dx: number | Point.PointLike | Point.PointData, dy?: number): this { - const t = Point.create(dx, dy) - this.x += t.x - this.y += t.y - return this - } - - /** - * Rotate the point by `degree` around `center`. - */ - rotate(degree: number, center?: Point.PointLike | Point.PointData): this { - const p = Point.rotate(this, degree, center) - this.x = p.x - this.y = p.y - return this - } - - /** - * Scale point by `sx` and `sy` around the given `origin`. If origin is not - * specified, the point is scaled around `0,0`. - */ - scale( - sx: number, - sy: number, - origin: Point.PointLike | Point.PointData = new Point(), - ) { - const ref = Point.create(origin) - this.x = ref.x + sx * (this.x - ref.x) - this.y = ref.y + sy * (this.y - ref.y) - return this - } - - /** - * Chooses the point closest to this point from among `points`. If `points` - * is an empty array, `null` is returned. - */ - closest(points: (Point.PointLike | Point.PointData)[]) { - if (points.length === 1) { - return Point.create(points[0]) - } - - let ret: Point.PointLike | Point.PointData | null = null - let min = Infinity - points.forEach((p) => { - const dist = this.squaredDistance(p) - if (dist < min) { - ret = p - min = dist - } - }) - - return ret ? Point.create(ret) : null - } - - /** - * Returns the distance between the point and another point `p`. - */ - distance(p: Point.PointLike | Point.PointData) { - return Math.sqrt(this.squaredDistance(p)) - } - - /** - * Returns the squared distance between the point and another point `p`. - * - * Useful for distance comparisons in which real distance is not necessary - * (saves one `Math.sqrt()` operation). - */ - squaredDistance(p: Point.PointLike | Point.PointData) { - const ref = Point.create(p) - const dx = this.x - ref.x - const dy = this.y - ref.y - return dx * dx + dy * dy - } - - manhattanDistance(p: Point.PointLike | Point.PointData) { - const ref = Point.create(p) - return Math.abs(ref.x - this.x) + Math.abs(ref.y - this.y) - } - - /** - * Returns the magnitude of the point vector. - * - * @see http://en.wikipedia.org/wiki/Magnitude_(mathematics) - */ - magnitude() { - return Math.sqrt(this.x * this.x + this.y * this.y) || 0.01 - } - - /** - * Returns the angle(in degrees) between vector from this point to `p` and - * the x-axis. - */ - theta(p: Point.PointLike | Point.PointData = new Point()): number { - const ref = Point.create(p) - const y = -(ref.y - this.y) // invert the y-axis. - const x = ref.x - this.x - let rad = Math.atan2(y, x) - - // Correction for III. and IV. quadrant. - if (rad < 0) { - rad = 2 * Math.PI + rad - } - - return (180 * rad) / Math.PI - } - - /** - * Returns the angle(in degrees) between vector from this point to `p1` and - * the vector from this point to `p2`. - * - * The ordering of points `p1` and `p2` is important. - * - * The function returns a value between `0` and `180` when the angle (in the - * direction from `p1` to `p2`) is clockwise, and a value between `180` and - * `360` when the angle is counterclockwise. - * - * Returns `NaN` if either of the points `p1` and `p2` is equal with this point. - */ - angleBetween( - p1: Point.PointLike | Point.PointData, - p2: Point.PointLike | Point.PointData, - ) { - if (this.equals(p1) || this.equals(p2)) { - return NaN - } - - let angle = this.theta(p2) - this.theta(p1) - if (angle < 0) { - angle += 360 - } - - return angle - } - - /** - * Returns the angle(in degrees) between the line from `(0,0)` and this point - * and the line from `(0,0)` to `p`. - * - * The function returns a value between `0` and `180` when the angle (in the - * direction from this point to `p`) is clockwise, and a value between `180` - * and `360` when the angle is counterclockwise. Returns `NaN` if called from - * point `(0,0)` or if `p` is `(0,0)`. - */ - vectorAngle(p: Point.PointLike | Point.PointData) { - const zero = new Point(0, 0) - return zero.angleBetween(this, p) - } - - /** - * Converts rectangular to polar coordinates. - */ - toPolar(origin?: Point.PointLike | Point.PointData) { - this.update(Point.toPolar(this, origin)) - return this - } - - /** - * Returns the change in angle(in degrees) that is the result of moving the - * point from its previous position to its current position. - * - * More specifically, this function computes the angle between the line from - * the ref point to the previous position of this point(i.e. current position - * `-dx`, `-dy`) and the line from the `ref` point to the current position of - * this point. - * - * The function returns a positive value between `0` and `180` when the angle - * (in the direction from previous position of this point to its current - * position) is clockwise, and a negative value between `0` and `-180` when - * the angle is counterclockwise. - * - * The function returns `0` if the previous and current positions of this - * point are the same (i.e. both `dx` and `dy` are `0`). - */ - changeInAngle( - dx: number, - dy: number, - ref: Point.PointLike | Point.PointData = new Point(), - ) { - // Revert the translation and measure the change in angle around x-axis. - return this.clone().translate(-dx, -dy).theta(ref) - this.theta(ref) - } - - /** - * If the point lies outside the rectangle `rect`, adjust the point so that - * it becomes the nearest point on the boundary of `rect`. - */ - adhereToRect(rect: Rectangle.RectangleLike) { - if (!util.containsPoint(rect, this)) { - this.x = Math.min(Math.max(this.x, rect.x), rect.x + rect.width) - this.y = Math.min(Math.max(this.y, rect.y), rect.y + rect.height) - } - return this - } - - /** - * Returns the bearing(cardinal direction) between me and the given point. - * - * @see https://en.wikipedia.org/wiki/Cardinal_direction - */ - bearing(p: Point.PointLike | Point.PointData) { - const ref = Point.create(p) - const lat1 = Angle.toRad(this.y) - const lat2 = Angle.toRad(ref.y) - const lon1 = this.x - const lon2 = ref.x - const dLon = Angle.toRad(lon2 - lon1) - const y = Math.sin(dLon) * Math.cos(lat2) - const x = - Math.cos(lat1) * Math.sin(lat2) - - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon) - - const brng = Angle.toDeg(Math.atan2(y, x)) - const bearings = ['NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N'] - - let index = brng - 22.5 - if (index < 0) { - index += 360 - } - index = parseInt((index / 45) as any, 10) - return bearings[index] as Point.Bearing - } - - /** - * Returns the cross product of the vector from me to `p1` and the vector - * from me to `p2`. - * - * The left-hand rule is used because the coordinate system is left-handed. - */ - cross( - p1: Point.PointLike | Point.PointData, - p2: Point.PointLike | Point.PointData, - ) { - if (p1 != null && p2 != null) { - const a = Point.create(p1) - const b = Point.create(p2) - return (b.x - this.x) * (a.y - this.y) - (b.y - this.y) * (a.x - this.x) - } - - return NaN - } - - /** - * Returns the dot product of this point with given other point. - */ - dot(p: Point.PointLike | Point.PointData) { - const ref = Point.create(p) - return this.x * ref.x + this.y * ref.y - } - - /** - * Returns a point that has coordinates computed as a difference between the - * point and another point with coordinates `dx` and `dy`. - * - * If only `dx` is specified and is a number, `dy` is considered to be zero. - * If only `dx` is specified and is an object, it is considered to be another - * point or an object in the form `{ x: [number], y: [number] }` - */ - diff(dx: number, dy: number): Point - diff(p: Point.PointLike | Point.PointData): Point - diff(dx: number | Point.PointLike | Point.PointData, dy?: number): Point { - if (typeof dx === 'number') { - return new Point(this.x - dx, this.y - dy!) - } - - const p = Point.create(dx) - return new Point(this.x - p.x, this.y - p.y) - } - - /** - * Returns an interpolation between me and point `p` for a parametert in - * the closed interval `[0, 1]`. - */ - lerp(p: Point.PointLike | Point.PointData, t: number) { - const ref = Point.create(p) - return new Point((1 - t) * this.x + t * ref.x, (1 - t) * this.y + t * ref.y) - } - - /** - * Normalize the point vector, scale the line segment between `(0, 0)` - * and the point in order for it to have the given length. If length is - * not specified, it is considered to be `1`; in that case, a unit vector - * is computed. - */ - normalize(length = 1) { - const scale = length / this.magnitude() - return this.scale(scale, scale) - } - - /** - * Moves this point along the line starting from `ref` to this point by a - * certain `distance`. - */ - move(ref: Point.PointLike | Point.PointData, distance: number) { - const p = Point.create(ref) - const rad = Angle.toRad(p.theta(this)) - return this.translate(Math.cos(rad) * distance, -Math.sin(rad) * distance) - } - - /** - * Returns a point that is the reflection of me with the center of inversion - * in `ref` point. - */ - reflection(ref: Point.PointLike | Point.PointData) { - return Point.create(ref).move(this, this.distance(ref)) - } - - /** - * Snaps the point(change its x and y coordinates) to a grid of size `gridSize` - * (or `gridSize` x `gridSizeY` for non-uniform grid). - */ - snapToGrid(gridSize: number): this - snapToGrid(gx: number, gy: number): this - snapToGrid(gx: number, gy?: number): this - snapToGrid(gx: number, gy?: number): this { - this.x = util.snapToGrid(this.x, gx) - this.y = util.snapToGrid(this.y, gy == null ? gx : gy) - return this - } - - equals(p: Point.PointLike | Point.PointData) { - const ref = Point.create(p) - return ref != null && ref.x === this.x && ref.y === this.y - } - - clone() { - return Point.clone(this) - } - - /** - * Returns the point as a simple JSON object. For example: `{ x: 0, y: 0 }`. - */ - toJSON() { - return Point.toJSON(this) - } - - serialize() { - return `${this.x} ${this.y}` - } -} - -export namespace Point { - export const toStringTag = `X6.Geometry.${Point.name}` - - export function isPoint(instance: any): instance is Point { - if (instance == null) { - return false - } - if (instance instanceof Point) { - return true - } - - const tag = instance[Symbol.toStringTag] - const point = instance as Point - - if ( - (tag == null || tag === toStringTag) && - typeof point.x === 'number' && - typeof point.y === 'number' && - typeof point.toPolar === 'function' - ) { - return true - } - - return false - } -} - -export namespace Point { - export interface PointLike { - x: number - y: number - } - - export type PointData = [number, number] - - export type Bearing = 'NE' | 'E' | 'SE' | 'S' | 'SW' | 'W' | 'NW' | 'N' - - export function isPointLike(p: any): p is PointLike { - return ( - p != null && - typeof p === 'object' && - typeof p.x === 'number' && - typeof p.y === 'number' - ) - } - - export function isPointData(p: any): p is PointData { - return ( - p != null && - Array.isArray(p) && - p.length === 2 && - typeof p[0] === 'number' && - typeof p[1] === 'number' - ) - } -} - -export namespace Point { - export function create( - x?: number | Point | PointLike | PointData, - y?: number, - ): Point { - if (x == null || typeof x === 'number') { - return new Point(x, y) - } - - return clone(x) - } - - export function clone(p: Point | PointLike | PointData) { - if (Point.isPoint(p)) { - return new Point(p.x, p.y) - } - - if (Array.isArray(p)) { - return new Point(p[0], p[1]) - } - - return new Point(p.x, p.y) - } - - export function toJSON(p: Point | PointLike | PointData) { - if (Point.isPoint(p)) { - return { x: p.x, y: p.y } - } - - if (Array.isArray(p)) { - return { x: p[0], y: p[1] } - } - - return { x: p.x, y: p.y } - } - - /** - * Returns a new Point object from the given polar coordinates. - * @see http://en.wikipedia.org/wiki/Polar_coordinate_system - */ - export function fromPolar( - r: number, - rad: number, - origin: Point | PointLike | PointData = new Point(), - ) { - let x = Math.abs(r * Math.cos(rad)) - let y = Math.abs(r * Math.sin(rad)) - const org = clone(origin) - const deg = Angle.normalize(Angle.toDeg(rad)) - - if (deg < 90) { - y = -y - } else if (deg < 180) { - x = -x - y = -y - } else if (deg < 270) { - x = -x - } - - return new Point(org.x + x, org.y + y) - } - - /** - * Converts rectangular to polar coordinates. - */ - export function toPolar( - point: Point | PointLike | PointData, - origin: Point | PointLike | PointData = new Point(), - ) { - const p = clone(point) - const o = clone(origin) - const dx = p.x - o.x - const dy = p.y - o.y - return new Point( - Math.sqrt(dx * dx + dy * dy), // r - Angle.toRad(o.theta(p)), - ) - } - - export function equals(p1?: Point.PointLike, p2?: Point.PointLike) { - if (p1 === p2) { - return true - } - - if (p1 != null && p2 != null) { - return p1.x === p2.x && p1.y === p2.y - } - - return false - } - - export function equalPoints(p1: Point.PointLike[], p2: Point.PointLike[]) { - if ( - (p1 == null && p2 != null) || - (p1 != null && p2 == null) || - (p1 != null && p2 != null && p1.length !== p2.length) - ) { - return false - } - - if (p1 != null && p2 != null) { - for (let i = 0, ii = p1.length; i < ii; i += 1) { - if (!equals(p1[i], p2[i])) { - return false - } - } - } - - return true - } - - /** - * Returns a point with random coordinates that fall within the range - * `[x1, x2]` and `[y1, y2]`. - */ - export function random(x1: number, x2: number, y1: number, y2: number) { - return new Point(util.random(x1, x2), util.random(y1, y2)) - } - - export function rotate( - point: Point | PointLike | PointData, - angle: number, - center?: Point | PointLike | PointData, - ) { - const rad = Angle.toRad(Angle.normalize(-angle)) - const sin = Math.sin(rad) - const cos = Math.cos(rad) - - return rotateEx(point, cos, sin, center) - } - - export function rotateEx( - point: Point | PointLike | PointData, - cos: number, - sin: number, - center: Point | PointLike | PointData = new Point(), - ) { - const source = clone(point) - const origin = clone(center) - const dx = source.x - origin.x - const dy = source.y - origin.y - const x1 = dx * cos - dy * sin - const y1 = dy * cos + dx * sin - return new Point(x1 + origin.x, y1 + origin.y) - } -} diff --git a/packages/x6/src/geometry/polyline.ts b/packages/x6/src/geometry/polyline.ts deleted file mode 100644 index 61f86898c9d..00000000000 --- a/packages/x6/src/geometry/polyline.ts +++ /dev/null @@ -1,670 +0,0 @@ -import { Point } from './point' -import { Rectangle } from './rectangle' -import { Line } from './line' -import { Geometry } from './geometry' - -export class Polyline extends Geometry { - points: Point[] - - protected get [Symbol.toStringTag]() { - return Polyline.toStringTag - } - - get start() { - if (this.points.length === 0) { - return null - } - return this.points[0] - } - - get end() { - if (this.points.length === 0) { - return null - } - return this.points[this.points.length - 1] - } - - constructor(points?: (Point.PointLike | Point.PointData)[] | string) { - super() - if (points != null) { - if (typeof points === 'string') { - return Polyline.parse(points) - } - this.points = points.map((p) => Point.create(p)) - } else { - this.points = [] - } - } - - scale( - sx: number, - sy: number, - origin: Point.PointLike | Point.PointData = new Point(), - ) { - this.points.forEach((p) => p.scale(sx, sy, origin)) - return this - } - - rotate(angle: number, origin?: Point.PointLike | Point.PointData) { - this.points.forEach((p) => p.rotate(angle, origin)) - return this - } - - translate(dx: number, dy: number): this - translate(p: Point.PointLike | Point.PointData): this - translate(dx: number | Point.PointLike | Point.PointData, dy?: number): this { - const t = Point.create(dx, dy) - this.points.forEach((p) => p.translate(t.x, t.y)) - return this - } - - bbox() { - if (this.points.length === 0) { - return new Rectangle() - } - - let x1 = Infinity - let x2 = -Infinity - let y1 = Infinity - let y2 = -Infinity - - const points = this.points - for (let i = 0, ii = points.length; i < ii; i += 1) { - const point = points[i] - const x = point.x - const y = point.y - - if (x < x1) x1 = x - if (x > x2) x2 = x - if (y < y1) y1 = y - if (y > y2) y2 = y - } - - return new Rectangle(x1, y1, x2 - x1, y2 - y1) - } - - closestPoint(p: Point.PointLike | Point.PointData) { - const cpLength = this.closestPointLength(p) - return this.pointAtLength(cpLength) - } - - closestPointLength(p: Point.PointLike | Point.PointData) { - const points = this.points - const count = points.length - if (count === 0 || count === 1) { - return 0 - } - - let length = 0 - let cpLength = 0 - let minSqrDistance = Infinity - for (let i = 0, ii = count - 1; i < ii; i += 1) { - const line = new Line(points[i], points[i + 1]) - const lineLength = line.length() - const cpNormalizedLength = line.closestPointNormalizedLength(p) - const cp = line.pointAt(cpNormalizedLength) - - const sqrDistance = cp.squaredDistance(p) - if (sqrDistance < minSqrDistance) { - minSqrDistance = sqrDistance - cpLength = length + cpNormalizedLength * lineLength - } - - length += lineLength - } - - return cpLength - } - - closestPointNormalizedLength(p: Point.PointLike | Point.PointData) { - const cpLength = this.closestPointLength(p) - if (cpLength === 0) { - return 0 - } - - const length = this.length() - if (length === 0) { - return 0 - } - - return cpLength / length - } - - closestPointTangent(p: Point.PointLike | Point.PointData) { - const cpLength = this.closestPointLength(p) - return this.tangentAtLength(cpLength) - } - - containsPoint(p: Point.PointLike | Point.PointData) { - if (this.points.length === 0) { - return false - } - - const ref = Point.clone(p) - const x = ref.x - const y = ref.y - const points = this.points - const count = points.length - - let startIndex = count - 1 - let intersectionCount = 0 - for (let endIndex = 0; endIndex < count; endIndex += 1) { - const start = points[startIndex] - const end = points[endIndex] - if (ref.equals(start)) { - return true - } - - const segment = new Line(start, end) - if (segment.containsPoint(p)) { - return true - } - - // do we have an intersection? - if ((y <= start.y && y > end.y) || (y > start.y && y <= end.y)) { - // this conditional branch IS NOT entered when `segment` is collinear/coincident with `ray` - // (when `y === start.y === end.y`) - // this conditional branch IS entered when `segment` touches `ray` at only one point - // (e.g. when `y === start.y !== end.y`) - // since this branch is entered again for the following segment, the two touches cancel out - - const xDifference = start.x - x > end.x - x ? start.x - x : end.x - x - if (xDifference >= 0) { - // segment lies at least partially to the right of `p` - const rayEnd = new Point(x + xDifference, y) // right - const ray = new Line(p, rayEnd) - - if (segment.intersectsWithLine(ray)) { - // an intersection was detected to the right of `p` - intersectionCount += 1 - } - } // else: `segment` lies completely to the left of `p` (i.e. no intersection to the right) - } - - // move to check the next polyline segment - startIndex = endIndex - } - - // returns `true` for odd numbers of intersections (even-odd algorithm) - return intersectionCount % 2 === 1 - } - - intersectsWithLine(line: Line) { - const intersections = [] - for (let i = 0, n = this.points.length - 1; i < n; i += 1) { - const a = this.points[i] - const b = this.points[i + 1] - const int = line.intersectsWithLine(new Line(a, b)) - if (int) { - intersections.push(int) - } - } - return intersections.length > 0 ? intersections : null - } - - isDifferentiable() { - for (let i = 0, ii = this.points.length - 1; i < ii; i += 1) { - const a = this.points[i] - const b = this.points[i + 1] - const line = new Line(a, b) - if (line.isDifferentiable()) { - return true - } - } - - return false - } - - length() { - let len = 0 - for (let i = 0, ii = this.points.length - 1; i < ii; i += 1) { - const a = this.points[i] - const b = this.points[i + 1] - len += a.distance(b) - } - return len - } - - pointAt(ratio: number) { - const points = this.points - const count = points.length - if (count === 0) { - return null - } - - if (count === 1) { - return points[0].clone() - } - - if (ratio <= 0) { - return points[0].clone() - } - - if (ratio >= 1) { - return points[count - 1].clone() - } - - const total = this.length() - const length = total * ratio - return this.pointAtLength(length) - } - - pointAtLength(length: number) { - const points = this.points - const count = points.length - if (count === 0) { - return null - } - - if (count === 1) { - return points[0].clone() - } - - let fromStart = true - if (length < 0) { - fromStart = false - length = -length // eslint-disable-line - } - - let tmp = 0 - for (let i = 0, ii = count - 1; i < ii; i += 1) { - const index = fromStart ? i : ii - 1 - i - const a = points[index] - const b = points[index + 1] - const l = new Line(a, b) - const d = a.distance(b) - - if (length <= tmp + d) { - return l.pointAtLength((fromStart ? 1 : -1) * (length - tmp)) - } - - tmp += d - } - - const lastPoint = fromStart ? points[count - 1] : points[0] - return lastPoint.clone() - } - - tangentAt(ratio: number) { - const points = this.points - const count = points.length - if (count === 0 || count === 1) { - return null - } - - if (ratio < 0) { - ratio = 0 // eslint-disable-line - } - - if (ratio > 1) { - ratio = 1 // eslint-disable-line - } - - const total = this.length() - const length = total * ratio - - return this.tangentAtLength(length) - } - - tangentAtLength(length: number) { - const points = this.points - const count = points.length - if (count === 0 || count === 1) { - return null - } - - let fromStart = true - if (length < 0) { - fromStart = false - length = -length // eslint-disable-line - } - - let lastValidLine - let tmp = 0 - for (let i = 0, ii = count - 1; i < ii; i += 1) { - const index = fromStart ? i : ii - 1 - i - const a = points[index] - const b = points[index + 1] - const l = new Line(a, b) - const d = a.distance(b) - - if (l.isDifferentiable()) { - // has a tangent line (line length is not 0) - if (length <= tmp + d) { - return l.tangentAtLength((fromStart ? 1 : -1) * (length - tmp)) - } - - lastValidLine = l - } - - tmp += d - } - - if (lastValidLine) { - const ratio = fromStart ? 1 : 0 - return lastValidLine.tangentAt(ratio) - } - - return null - } - - simplify( - // TODO: Accept startIndex and endIndex to specify where to start and end simplification - options: { - /** - * The max distance of middle point from chord to be simplified. - */ - threshold?: number - } = {}, - ) { - const points = this.points - // we need at least 3 points - if (points.length < 3) { - return this - } - - const threshold = options.threshold || 0 - - // start at the beginning of the polyline and go forward - let currentIndex = 0 - // we need at least one intermediate point (3 points) in every iteration - // as soon as that stops being true, we know we reached the end of the polyline - while (points[currentIndex + 2]) { - const firstIndex = currentIndex - const middleIndex = currentIndex + 1 - const lastIndex = currentIndex + 2 - - const firstPoint = points[firstIndex] - const middlePoint = points[middleIndex] - const lastPoint = points[lastIndex] - - const chord = new Line(firstPoint, lastPoint) // = connection between first and last point - const closestPoint = chord.closestPoint(middlePoint) // = closest point on chord from middle point - const closestPointDistance = closestPoint.distance(middlePoint) - if (closestPointDistance <= threshold) { - // middle point is close enough to the chord = simplify - // 1) remove middle point: - points.splice(middleIndex, 1) - // 2) in next iteration, investigate the newly-created triplet of points - // - do not change `currentIndex` - // = (first point stays, point after removed point becomes middle point) - } else { - // middle point is far from the chord - // 1) preserve middle point - // 2) in next iteration, move `currentIndex` by one step: - currentIndex += 1 - // = (point after first point becomes first point) - } - } - - // `points` array was modified in-place - return this - } - - toHull() { - const points = this.points - const count = points.length - if (count === 0) { - return new Polyline() - } - - // Step 1: find the starting point -- point with - // the lowest y (if equality, highest x). - let startPoint: Point = points[0] - for (let i = 1; i < count; i += 1) { - if (points[i].y < startPoint.y) { - startPoint = points[i] - } else if (points[i].y === startPoint.y && points[i].x > startPoint.x) { - startPoint = points[i] - } - } - - // Step 2: sort the list of points by angle between line - // from start point to current point and the x-axis (theta). - - // Step 2a: create the point records = [point, originalIndex, angle] - const sortedRecords: Types.HullRecord[] = [] - for (let i = 0; i < count; i += 1) { - let angle = startPoint.theta(points[i]) - if (angle === 0) { - // Give highest angle to start point. - // The start point will end up at end of sorted list. - // The start point will end up at beginning of hull points list. - angle = 360 - } - - sortedRecords.push([points[i], i, angle]) - } - - // Step 2b: sort the list in place - sortedRecords.sort((record1, record2) => { - let ret = record1[2] - record2[2] - if (ret === 0) { - ret = record2[1] - record1[1] - } - - return ret - }) - - // Step 2c: duplicate start record from the top of - // the stack to the bottom of the stack. - if (sortedRecords.length > 2) { - const startPoint = sortedRecords[sortedRecords.length - 1] - sortedRecords.unshift(startPoint) - } - - // Step 3 - // ------ - - // Step 3a: go through sorted points in order and find those with - // right turns, and we want to get our results in clockwise order. - - // Dictionary of points with left turns - cannot be on the hull. - const insidePoints: { [key: string]: Point } = {} - // Stack of records with right turns - hull point candidates. - const hullRecords: Types.HullRecord[] = [] - const getKey = (record: Types.HullRecord) => - `${record[0].toString()}@${record[1]}` - - while (sortedRecords.length !== 0) { - const currentRecord = sortedRecords.pop()! - const currentPoint = currentRecord[0] - - // Check if point has already been discarded. - if (insidePoints[getKey(currentRecord)]) { - continue - } - - let correctTurnFound = false - while (!correctTurnFound) { - if (hullRecords.length < 2) { - // Not enough points for comparison, just add current point. - hullRecords.push(currentRecord) - correctTurnFound = true - } else { - const lastHullRecord = hullRecords.pop()! - const lastHullPoint = lastHullRecord[0] - const secondLastHullRecord = hullRecords.pop()! - const secondLastHullPoint = secondLastHullRecord[0] - - const crossProduct = secondLastHullPoint.cross( - lastHullPoint, - currentPoint, - ) - - if (crossProduct < 0) { - // Found a right turn. - hullRecords.push(secondLastHullRecord) - hullRecords.push(lastHullRecord) - hullRecords.push(currentRecord) - correctTurnFound = true - } else if (crossProduct === 0) { - // the three points are collinear - // three options: - // there may be a 180 or 0 degree angle at lastHullPoint - // or two of the three points are coincident - - // we have to take rounding errors into account - const THRESHOLD = 1e-10 - const angleBetween = lastHullPoint.angleBetween( - secondLastHullPoint, - currentPoint, - ) - - if (Math.abs(angleBetween - 180) < THRESHOLD) { - // rouding around 180 to 180 - // if the cross product is 0 because the angle is 180 degrees - // discard last hull point (add to insidePoints) - // insidePoints.unshift(lastHullPoint); - insidePoints[getKey(lastHullRecord)] = lastHullPoint - // reenter second-to-last hull point (will be last at next iter) - hullRecords.push(secondLastHullRecord) - // do not do anything with current point - // correct turn not found - } else if ( - lastHullPoint.equals(currentPoint) || - secondLastHullPoint.equals(lastHullPoint) - ) { - // if the cross product is 0 because two points are the same - // discard last hull point (add to insidePoints) - // insidePoints.unshift(lastHullPoint); - insidePoints[getKey(lastHullRecord)] = lastHullPoint - // reenter second-to-last hull point (will be last at next iter) - hullRecords.push(secondLastHullRecord) - // do not do anything with current point - // correct turn not found - } else if (Math.abs(((angleBetween + 1) % 360) - 1) < THRESHOLD) { - // rounding around 0 and 360 to 0 - // if the cross product is 0 because the angle is 0 degrees - // remove last hull point from hull BUT do not discard it - // reenter second-to-last hull point (will be last at next iter) - hullRecords.push(secondLastHullRecord) - // put last hull point back into the sorted point records list - sortedRecords.push(lastHullRecord) - // we are switching the order of the 0deg and 180deg points - // correct turn not found - } - } else { - // found a left turn - // discard last hull point (add to insidePoints) - // insidePoints.unshift(lastHullPoint); - insidePoints[getKey(lastHullRecord)] = lastHullPoint - // reenter second-to-last hull point (will be last at next iter of loop) - hullRecords.push(secondLastHullRecord) - // do not do anything with current point - // correct turn not found - } - } - } - } - - // At this point, hullPointRecords contains the output points in clockwise order - // the points start with lowest-y,highest-x startPoint, and end at the same point - - // Step 3b: remove duplicated startPointRecord from the end of the array - if (hullRecords.length > 2) { - hullRecords.pop() - } - - // Step 4: find the lowest originalIndex record and put it at the beginning of hull - let lowestHullIndex // the lowest originalIndex on the hull - let indexOfLowestHullIndexRecord = -1 // the index of the record with lowestHullIndex - for (let i = 0, n = hullRecords.length; i < n; i += 1) { - const currentHullIndex = hullRecords[i][1] - - if (lowestHullIndex === undefined || currentHullIndex < lowestHullIndex) { - lowestHullIndex = currentHullIndex - indexOfLowestHullIndexRecord = i - } - } - - let hullPointRecordsReordered = [] - if (indexOfLowestHullIndexRecord > 0) { - const newFirstChunk = hullRecords.slice(indexOfLowestHullIndexRecord) - const newSecondChunk = hullRecords.slice(0, indexOfLowestHullIndexRecord) - hullPointRecordsReordered = newFirstChunk.concat(newSecondChunk) - } else { - hullPointRecordsReordered = hullRecords - } - - const hullPoints = [] - for (let i = 0, n = hullPointRecordsReordered.length; i < n; i += 1) { - hullPoints.push(hullPointRecordsReordered[i][0]) - } - - return new Polyline(hullPoints) - } - - equals(p: Polyline) { - if (p == null) { - return false - } - - if (p.points.length !== this.points.length) { - return false - } - - return p.points.every((a, i) => a.equals(this.points[i])) - } - - clone() { - return new Polyline(this.points.map((p) => p.clone())) - } - - toJSON() { - return this.points.map((p) => p.toJSON()) - } - - serialize() { - return this.points.map((p) => `${p.x}, ${p.y}`).join(' ') - } -} - -export namespace Polyline { - export const toStringTag = `X6.Geometry.${Polyline.name}` - - export function isPolyline(instance: any): instance is Polyline { - if (instance == null) { - return false - } - - if (instance instanceof Polyline) { - return true - } - - const tag = instance[Symbol.toStringTag] - const polyline = instance as Polyline - - if ( - (tag == null || tag === toStringTag) && - typeof polyline.toHull === 'function' && - typeof polyline.simplify === 'function' - ) { - return true - } - - return false - } -} - -export namespace Polyline { - export function parse(svgString: string) { - const str = svgString.trim() - if (str === '') { - return new Polyline() - } - - const points = [] - - const coords = str.split(/\s*,\s*|\s+/) - for (let i = 0, ii = coords.length; i < ii; i += 2) { - points.push({ x: +coords[i], y: +coords[i + 1] }) - } - - return new Polyline(points) - } -} - -namespace Types { - export type HullRecord = [Point, number, number] -} diff --git a/packages/x6/src/geometry/rectangle.test.ts b/packages/x6/src/geometry/rectangle.test.ts deleted file mode 100644 index 21833be462d..00000000000 --- a/packages/x6/src/geometry/rectangle.test.ts +++ /dev/null @@ -1,545 +0,0 @@ -import { Ellipse } from './ellipse' -import { Line } from './line' -import { Point } from './point' -import { Rectangle } from './rectangle' - -describe('rectangle', () => { - describe('#constructor', () => { - it('should create a rectangle instance', () => { - expect(new Rectangle()).toBeInstanceOf(Rectangle) - expect(new Rectangle(1)).toBeInstanceOf(Rectangle) - expect(new Rectangle(1, 2)).toBeInstanceOf(Rectangle) - expect(new Rectangle(1, 2).x).toEqual(1) - expect(new Rectangle(1, 2).y).toEqual(2) - expect(new Rectangle(1, 2).width).toEqual(0) - expect(new Rectangle(1, 2).height).toEqual(0) - expect(new Rectangle().equals(new Rectangle(0, 0, 0, 0))) - }) - - it('should work with key points', () => { - const rect = new Rectangle(1, 2, 3, 4) - expect(rect.origin.equals({ x: 1, y: 2 })).toBeTrue() - expect(rect.topLeft.equals({ x: 1, y: 2 })).toBeTrue() - expect(rect.topCenter.equals({ x: 2.5, y: 2 })).toBeTrue() - expect(rect.topRight.equals({ x: 4, y: 2 })).toBeTrue() - expect(rect.center.equals({ x: 2.5, y: 4 })).toBeTrue() - expect(rect.bottomLeft.equals({ x: 1, y: 6 })).toBeTrue() - expect(rect.bottomCenter.equals({ x: 2.5, y: 6 })).toBeTrue() - expect(rect.bottomRight.equals({ x: 4, y: 6 })).toBeTrue() - expect(rect.corner.equals({ x: 4, y: 6 })).toBeTrue() - expect(rect.leftMiddle.equals({ x: 1, y: 4 })).toBeTrue() - expect(rect.rightMiddle.equals({ x: 4, y: 4 })).toBeTrue() - }) - - it('should return the key points', () => { - const rect = new Rectangle(1, 2, 3, 4) - expect(rect.getOrigin().equals({ x: 1, y: 2 })).toBeTrue() - expect(rect.getTopLeft().equals({ x: 1, y: 2 })).toBeTrue() - expect(rect.getTopCenter().equals({ x: 2.5, y: 2 })).toBeTrue() - expect(rect.getTopRight().equals({ x: 4, y: 2 })).toBeTrue() - expect(rect.getCenter().equals({ x: 2.5, y: 4 })).toBeTrue() - expect(rect.getBottomLeft().equals({ x: 1, y: 6 })).toBeTrue() - expect(rect.getBottomCenter().equals({ x: 2.5, y: 6 })).toBeTrue() - expect(rect.getBottomRight().equals({ x: 4, y: 6 })).toBeTrue() - expect(rect.getCorner().equals({ x: 4, y: 6 })).toBeTrue() - expect(rect.getLeftMiddle().equals({ x: 1, y: 4 })).toBeTrue() - expect(rect.getRightMiddle().equals({ x: 4, y: 4 })).toBeTrue() - - expect(rect.getCenterX()).toEqual(2.5) - expect(rect.getCenterY()).toEqual(4) - }) - - it('should work with key lines', () => { - const rect = new Rectangle(1, 2, 3, 4) - expect(rect.topLine.equals(new Line(1, 2, 4, 2))) - expect(rect.rightLine.equals(new Line(4, 2, 4, 6))) - expect(rect.bottomLine.equals(new Line(1, 6, 4, 6))) - expect(rect.leftLine.equals(new Line(1, 2, 1, 6))) - }) - - it('should return the key lines', () => { - const rect = new Rectangle(1, 2, 3, 4) - expect(rect.getTopLine().equals(new Line(1, 2, 4, 2))) - expect(rect.getRightLine().equals(new Line(4, 2, 4, 6))) - expect(rect.getBottomLine().equals(new Line(1, 6, 4, 6))) - expect(rect.getLeftLine().equals(new Line(1, 2, 1, 6))) - }) - }) - - describe('#Rectangle.clone', () => { - it('should clone rectangle', () => { - const obj = { x: 1, y: 2, width: 3, height: 4 } - expect(Rectangle.clone(new Rectangle(1, 2, 3, 4)).toJSON()).toEqual(obj) - expect(Rectangle.clone(obj).toJSON()).toEqual(obj) - expect(Rectangle.clone([1, 2, 3, 4]).toJSON()).toEqual(obj) - }) - }) - - describe('#Rectangle.isRectangleLike', () => { - it('should return true if the given object is a rectangle-like object', () => { - const obj = { x: 1, y: 2, width: 3, height: 4 } - expect(Rectangle.isRectangleLike(obj)).toBeTrue() - expect(Rectangle.isRectangleLike({ ...obj, z: 10 })).toBeTrue() - expect(Rectangle.isRectangleLike({ ...obj, z: 10, s: 's' })).toBeTrue() - }) - - it('should return false if the given object is a rectangle-like object', () => { - expect(Rectangle.isRectangleLike({ x: 1 })).toBeFalse() - expect(Rectangle.isRectangleLike({ y: 2 })).toBeFalse() - expect(Rectangle.isRectangleLike({})).toBeFalse() - expect(Rectangle.isRectangleLike(null)).toBeFalse() - expect(Rectangle.isRectangleLike(false)).toBeFalse() - expect(Rectangle.isRectangleLike(1)).toBeFalse() - expect(Rectangle.isRectangleLike('s')).toBeFalse() - }) - }) - - describe('#Rectangle.fromSize', () => { - it('should create a rectangle from the given size', () => { - expect(Rectangle.fromSize({ width: 10, height: 8 }).toJSON()).toEqual({ - x: 0, - y: 0, - width: 10, - height: 8, - }) - }) - }) - - describe('#Rectangle.fromPositionAndSize', () => { - it('should create a rectangle from the given position and size', () => { - expect( - Rectangle.fromPositionAndSize( - { x: 2, y: 5 }, - { width: 10, height: 8 }, - ).toJSON(), - ).toEqual({ - x: 2, - y: 5, - width: 10, - height: 8, - }) - }) - }) - - describe('#Rectangle.fromEllipse', () => { - it('should create a rectangle from the given ellipse', () => { - expect(Rectangle.fromEllipse(new Ellipse(1, 2, 3, 4)).toJSON()).toEqual({ - x: -2, - y: -2, - width: 6, - height: 8, - }) - }) - }) - - describe('#valueOf', () => { - it('should return JSON object', () => { - const obj = { x: 1, y: 2, width: 3, height: 4 } - const rect = Rectangle.create(obj) - expect(rect.valueOf()).toEqual(obj) - }) - }) - - describe('#toString', () => { - it('should return JSON string', () => { - const obj = { x: 1, y: 2, width: 3, height: 4 } - const rect = Rectangle.create(obj) - expect(rect.toString()).toEqual(JSON.stringify(obj)) - }) - }) - - describe('#bbox', () => { - it('should return a rectangle that is the bounding box of the rectangle.', () => { - const rect = new Rectangle(1, 2, 3, 4) - expect(rect.bbox().equals(rect)).toBeTrue() - }) - - it('should rotate the rectangle if angle specified.', () => { - const rect = new Rectangle(0, 0, 2, 4) - expect( - rect.bbox(90).round().equals(new Rectangle(-1, 1, 4, 2)), - ).toBeTrue() - }) - }) - - describe('#add', () => { - it('should add the given `rect` to me', () => { - const rect = new Rectangle(1, 2, 3, 4) - expect(rect.add(0, 0, 0, 0)).toEqual(new Rectangle(0, 0, 4, 6)) - }) - }) - - describe('#update', () => { - it('should update me with the given `rect`', () => { - const rect = new Rectangle(1, 2, 3, 4) - expect(rect.update(0, 0, 1, 1)).toEqual(new Rectangle(0, 0, 1, 1)) - }) - }) - - describe('#inflate', () => { - it('should inflate me with the given `amount`', () => { - const rect = new Rectangle(1, 2, 3, 4) - expect(rect.inflate(2)).toEqual(new Rectangle(-1, 0, 7, 8)) - }) - - it('should inflate me with the given `dx` and `dy`', () => { - const rect = new Rectangle(1, 2, 3, 4) - expect(rect.inflate(2, 1)).toEqual(new Rectangle(-1, 1, 7, 6)) - }) - }) - - describe('#snapToGrid', () => { - it('should snap to grid', () => { - const rect1 = new Rectangle(2, 6, 33, 44) - const rect2 = rect1.clone().snapToGrid(10) - const rect3 = rect1.clone().snapToGrid(3, 5) - expect(rect2.equals({ x: 0, y: 10, width: 40, height: 40 })).toBeTrue() - expect(rect3.equals({ x: 3, y: 5, width: 33, height: 45 })).toBeTrue() - }) - }) - - describe('#translate', () => { - it('should translate x and y by adding the given `dx` and `dy` values respectively', () => { - const rect = new Rectangle(1, 2, 3, 4) - - expect(rect.clone().translate(2, 3).toJSON()).toEqual({ - x: 3, - y: 5, - width: 3, - height: 4, - }) - - expect(rect.clone().translate(new Point(-2, 4)).toJSON()).toEqual({ - x: -1, - y: 6, - width: 3, - height: 4, - }) - }) - }) - - describe('#scale', () => { - it('should scale point with the given amount', () => { - expect(new Rectangle(1, 2, 3, 4).scale(2, 3).toJSON()).toEqual({ - x: 2, - y: 6, - width: 6, - height: 12, - }) - }) - - it('should scale point with the given amount and center ', () => { - expect( - new Rectangle(20, 30, 10, 20).scale(2, 3, new Point(40, 45)).toJSON(), - ).toEqual({ x: 0, y: 0, width: 20, height: 60 }) - }) - }) - - describe('#rotate', () => { - it('should rorate the rect by the given angle', () => { - expect(new Rectangle(1, 2, 3, 4).rotate(180).round().toJSON()).toEqual({ - x: 1, - y: 2, - width: 3, - height: 4, - }) - }) - - it('should keep the same when the given angle is `0`', () => { - expect(new Rectangle(1, 2, 3, 4).rotate(0).toJSON()).toEqual({ - x: 1, - y: 2, - width: 3, - height: 4, - }) - }) - }) - - describe('#rotate90', () => { - it("should rorate the rect by 90deg around it's center", () => { - expect(new Rectangle(1, 2, 3, 4).rotate90().toJSON()).toEqual({ - x: 0.5, - y: 2.5, - width: 4, - height: 3, - }) - }) - }) - - describe('#getMaxScaleToFit', () => { - it('should return the scale amount', () => { - const scale1 = new Rectangle(1, 2, 3, 4).getMaxScaleToFit([5, 6, 7, 8]) - const scale2 = new Rectangle(1, 2, 3, 4).getMaxScaleToFit([0, 0, 7, 8]) - expect(scale1.sx.toFixed(2)).toEqual('0.16') - expect(scale1.sy.toFixed(2)).toEqual('0.20') - expect(scale2.sx.toFixed(2)).toEqual('0.33') - expect(scale2.sy.toFixed(2)).toEqual('0.50') - }) - }) - - describe('#getMaxUniformScaleToFit', () => { - it('should return the scale amount', () => { - const s1 = new Rectangle(1, 2, 3, 4).getMaxUniformScaleToFit([5, 6, 7, 8]) - const s2 = new Rectangle(1, 2, 3, 4).getMaxUniformScaleToFit([0, 0, 7, 8]) - expect(s1.toFixed(2)).toEqual('0.16') - expect(s2.toFixed(2)).toEqual('0.33') - }) - }) - - describe('#moveAndExpand', () => { - it('should translate and expand me by the given `rect`', () => { - expect( - new Rectangle(1, 2, 3, 4) - .moveAndExpand(new Rectangle(1, 2, 3, 4)) - .toJSON(), - ).toEqual({ x: 2, y: 4, width: 6, height: 8 }) - - expect( - new Rectangle(1, 2, 3, 4).moveAndExpand(new Rectangle()).toJSON(), - ).toEqual({ x: 1, y: 2, width: 3, height: 4 }) - }) - }) - - describe('#containsPoint', () => { - it('should return true when rect contains the given point', () => { - expect(new Rectangle(50, 50, 100, 100).containsPoint(60, 60)).toBeTrue() - }) - }) - - describe('#containsRect', () => { - it('should return true when rect is completely inside the other rect', () => { - expect( - new Rectangle(50, 50, 100, 100).containsRect(60, 60, 80, 80), - ).toBeTrue() - }) - - it('should return true when rect is equal the other rect', () => { - expect( - new Rectangle(50, 50, 100, 100).containsRect({ - x: 50, - y: 50, - width: 100, - height: 100, - }), - ).toBeTrue() - }) - - it('should return false when rect is not inside the other rect', () => { - expect( - new Rectangle(50, 50, 100, 100).containsRect(20, 20, 200, 200), - ).toBeFalse() - expect( - new Rectangle(50, 50, 100, 100).containsRect(40, 40, 100, 100), - ).toBeFalse() - expect( - new Rectangle(50, 50, 100, 100).containsRect(60, 60, 100, 40), - ).toBeFalse() - expect( - new Rectangle(50, 50, 100, 100).containsRect(60, 60, 100, 100), - ).toBeFalse() - expect( - new Rectangle(50, 50, 100, 100).containsRect(60, 60, 40, 100), - ).toBeFalse() - }) - - it('should return false when one of the dimensions is `0`', () => { - expect( - new Rectangle(50, 50, 100, 100).containsRect(75, 75, 0, 0), - ).toBeFalse() - expect(new Rectangle(50, 50, 0, 0).containsRect(50, 50, 0, 0)).toBeFalse() - }) - }) - - describe('#intersectsWithRect', () => { - it('should return the intersection', () => { - // inside - expect( - new Rectangle(20, 20, 100, 100) - .intersectsWithRect([40, 40, 20, 20]) - ?.toJSON(), - ).toEqual({ x: 40, y: 40, width: 20, height: 20 }) - - expect( - new Rectangle(20, 20, 100, 100) - .intersectsWithRect([0, 0, 100, 100]) - ?.toJSON(), - ).toEqual({ x: 20, y: 20, width: 80, height: 80 }) - - expect( - new Rectangle(20, 20, 100, 100) - .intersectsWithRect([40, 40, 100, 100]) - ?.toJSON(), - ).toEqual({ x: 40, y: 40, width: 80, height: 80 }) - }) - - it('should return null when no intersection', () => { - expect( - new Rectangle(20, 20, 100, 100).intersectsWithRect([140, 140, 20, 20]), - ).toBeNull() - }) - }) - - describe('#intersectsWithLine', () => { - it('should return the intersection points', () => { - const points1 = new Rectangle(0, 0, 4, 4).intersectsWithLine( - new Line(2, 2, 2, 8), - ) - const points2 = new Rectangle(0, 0, 4, 4).intersectsWithLine( - new Line(2, -2, 2, 8), - ) - expect(Point.equalPoints(points1!, [{ x: 2, y: 4 }])) - expect( - Point.equalPoints(points2!, [ - { x: 2, y: 0 }, - { x: 2, y: 4 }, - ]), - ) - }) - - it('should return null when no intersection exists', () => { - expect( - new Rectangle(0, 0, 4, 4).intersectsWithLine(new Line(-2, -2, -2, -8)), - ).toBeNull() - }) - }) - - describe('#intersectsWithLineFromCenterToPoint', () => { - it('should return the intersection point', () => { - expect( - new Rectangle(0, 0, 4, 4) - .intersectsWithLineFromCenterToPoint([2, 8]) - ?.equals([2, 4]), - ).toBeTrue() - - expect( - new Rectangle(0, 0, 4, 4) - .intersectsWithLineFromCenterToPoint([2, 8], 90) - ?.round() - .equals([2, 4]), - ).toBeTrue() - }) - - it('should return null when no intersection exists', () => { - expect( - new Rectangle(0, 0, 4, 4).intersectsWithLineFromCenterToPoint([3, 3]), - ).toBeNull() - }) - }) - - describe('#normalize', () => { - it('should keep the same when width and height is positive', () => { - expect(new Rectangle(1, 2, 3, 4).normalize().toJSON()).toEqual({ - x: 1, - y: 2, - width: 3, - height: 4, - }) - }) - - it('should make the width positive', () => { - expect(new Rectangle(1, 2, -3, 4).normalize().toJSON()).toEqual({ - x: -2, - y: 2, - width: 3, - height: 4, - }) - }) - - it('should make the height positive', () => { - expect(new Rectangle(1, 2, 3, -4).normalize().toJSON()).toEqual({ - x: 1, - y: -2, - width: 3, - height: 4, - }) - }) - }) - - describe('#union', () => {}) - - describe('#getNearestSideToPoint', () => { - it('should return the nearest side to point when point is on the side', () => { - const rect = new Rectangle(0, 0, 4, 4) - expect(rect.getNearestSideToPoint({ x: 0, y: 0 })).toEqual('left') - expect(rect.getNearestSideToPoint({ x: 4, y: 4 })).toEqual('right') - expect(rect.getNearestSideToPoint({ x: 0, y: 4 })).toEqual('left') - expect(rect.getNearestSideToPoint({ x: 4, y: 0 })).toEqual('right') - expect(rect.getNearestSideToPoint({ x: 2, y: 0 })).toEqual('top') - expect(rect.getNearestSideToPoint({ x: 0, y: 2 })).toEqual('left') - expect(rect.getNearestSideToPoint({ x: 4, y: 2 })).toEqual('right') - expect(rect.getNearestSideToPoint({ x: 2, y: 4 })).toEqual('bottom') - }) - - it('should return the nearest side to point when point is outside', () => { - const rect = new Rectangle(0, 0, 4, 4) - expect(rect.getNearestSideToPoint({ x: 5, y: 5 })).toEqual('right') - expect(rect.getNearestSideToPoint({ x: 5, y: 4 })).toEqual('right') - expect(rect.getNearestSideToPoint({ x: 5, y: 2 })).toEqual('right') - expect(rect.getNearestSideToPoint({ x: 5, y: 0 })).toEqual('right') - expect(rect.getNearestSideToPoint({ x: 5, y: -1 })).toEqual('right') - expect(rect.getNearestSideToPoint({ x: -1, y: 5 })).toEqual('left') - expect(rect.getNearestSideToPoint({ x: -1, y: 4 })).toEqual('left') - expect(rect.getNearestSideToPoint({ x: -1, y: 2 })).toEqual('left') - expect(rect.getNearestSideToPoint({ x: -1, y: 0 })).toEqual('left') - expect(rect.getNearestSideToPoint({ x: -1, y: -1 })).toEqual('left') - expect(rect.getNearestSideToPoint({ x: 0, y: 5 })).toEqual('bottom') - expect(rect.getNearestSideToPoint({ x: 2, y: 5 })).toEqual('bottom') - expect(rect.getNearestSideToPoint({ x: 4, y: 5 })).toEqual('bottom') - expect(rect.getNearestSideToPoint({ x: 0, y: -1 })).toEqual('top') - expect(rect.getNearestSideToPoint({ x: 2, y: -1 })).toEqual('top') - expect(rect.getNearestSideToPoint({ x: 4, y: -1 })).toEqual('top') - }) - - it('should return the nearest side to point when point is inside', () => { - const rect = new Rectangle(0, 0, 4, 4) - expect(rect.getNearestSideToPoint({ x: 2, y: 1 })).toEqual('top') - expect(rect.getNearestSideToPoint({ x: 3, y: 2 })).toEqual('right') - expect(rect.getNearestSideToPoint({ x: 2, y: 3 })).toEqual('bottom') - expect(rect.getNearestSideToPoint({ x: 1, y: 2 })).toEqual('left') - }) - }) - - describe('#getNearestPointToPoint', () => { - it('should return the nearest point to point when point is inside the rect', () => { - const rect = new Rectangle(0, 0, 4, 4) - // left - expect(rect.getNearestPointToPoint({ x: 1, y: 2 }).toJSON()).toEqual({ - x: 0, - y: 2, - }) - // right - expect(rect.getNearestPointToPoint({ x: 3, y: 2 }).toJSON()).toEqual({ - x: 4, - y: 2, - }) - // top - expect(rect.getNearestPointToPoint({ x: 2, y: 1 }).toJSON()).toEqual({ - x: 2, - y: 0, - }) - // bottom - expect(rect.getNearestPointToPoint({ x: 2, y: 3 }).toJSON()).toEqual({ - x: 2, - y: 4, - }) - }) - - it('should return the nearest point to point when point is outside the rect', () => { - const rect = new Rectangle(0, 0, 4, 4) - expect(rect.getNearestPointToPoint({ x: 5, y: 5 }).toJSON()).toEqual({ - x: 4, - y: 4, - }) - expect(rect.getNearestPointToPoint({ x: -1, y: -1 }).toJSON()).toEqual({ - x: 0, - y: 0, - }) - }) - }) - - describe('#serialize', () => { - it('should return the serialized string', () => { - expect(new Rectangle(1, 2, 3, 4).serialize()).toEqual('1 2 3 4') - }) - }) -}) diff --git a/packages/x6/src/geometry/rectangle.ts b/packages/x6/src/geometry/rectangle.ts deleted file mode 100644 index bfb48d91248..00000000000 --- a/packages/x6/src/geometry/rectangle.ts +++ /dev/null @@ -1,851 +0,0 @@ -import { Size } from '../types' -import * as util from './util' -import { Angle } from './angle' -import { Line } from './line' -import { Point } from './point' -import { Ellipse } from './ellipse' -import { Geometry } from './geometry' - -export class Rectangle extends Geometry implements Rectangle.RectangleLike { - x: number - y: number - width: number - height: number - - protected get [Symbol.toStringTag]() { - return Rectangle.toStringTag - } - - get left() { - return this.x - } - - get top() { - return this.y - } - - get right() { - return this.x + this.width - } - - get bottom() { - return this.y + this.height - } - - get origin() { - return new Point(this.x, this.y) - } - - get topLeft() { - return new Point(this.x, this.y) - } - - get topCenter() { - return new Point(this.x + this.width / 2, this.y) - } - - get topRight() { - return new Point(this.x + this.width, this.y) - } - - get center() { - return new Point(this.x + this.width / 2, this.y + this.height / 2) - } - - get bottomLeft() { - return new Point(this.x, this.y + this.height) - } - - get bottomCenter() { - return new Point(this.x + this.width / 2, this.y + this.height) - } - - get bottomRight() { - return new Point(this.x + this.width, this.y + this.height) - } - - get corner() { - return new Point(this.x + this.width, this.y + this.height) - } - - get rightMiddle() { - return new Point(this.x + this.width, this.y + this.height / 2) - } - - get leftMiddle() { - return new Point(this.x, this.y + this.height / 2) - } - - get topLine() { - return new Line(this.topLeft, this.topRight) - } - - get rightLine() { - return new Line(this.topRight, this.bottomRight) - } - - get bottomLine() { - return new Line(this.bottomLeft, this.bottomRight) - } - - get leftLine() { - return new Line(this.topLeft, this.bottomLeft) - } - - constructor(x?: number, y?: number, width?: number, height?: number) { - super() - this.x = x == null ? 0 : x - this.y = y == null ? 0 : y - this.width = width == null ? 0 : width - this.height = height == null ? 0 : height - } - - getOrigin() { - return this.origin - } - - getTopLeft() { - return this.topLeft - } - - getTopCenter() { - return this.topCenter - } - - getTopRight() { - return this.topRight - } - - getCenter() { - return this.center - } - - getCenterX() { - return this.x + this.width / 2 - } - - getCenterY() { - return this.y + this.height / 2 - } - - getBottomLeft() { - return this.bottomLeft - } - - getBottomCenter() { - return this.bottomCenter - } - - getBottomRight() { - return this.bottomRight - } - - getCorner() { - return this.corner - } - - getRightMiddle() { - return this.rightMiddle - } - - getLeftMiddle() { - return this.leftMiddle - } - - getTopLine() { - return this.topLine - } - - getRightLine() { - return this.rightLine - } - - getBottomLine() { - return this.bottomLine - } - - getLeftLine() { - return this.leftLine - } - - /** - * Returns a rectangle that is the bounding box of the rectangle. - * - * If `angle` is specified, the bounding box calculation will take into - * account the rotation of the rectangle by angle degrees around its center. - */ - bbox(angle?: number) { - if (!angle) { - return this.clone() - } - - const rad = Angle.toRad(angle) - const st = Math.abs(Math.sin(rad)) - const ct = Math.abs(Math.cos(rad)) - const w = this.width * ct + this.height * st - const h = this.width * st + this.height * ct - return new Rectangle( - this.x + (this.width - w) / 2, - this.y + (this.height - h) / 2, - w, - h, - ) - } - - round(precision = 0) { - this.x = util.round(this.x, precision) - this.y = util.round(this.y, precision) - this.width = util.round(this.width, precision) - this.height = util.round(this.height, precision) - return this - } - - add(x: number, y: number, width: number, height: number): this - add(rect: Rectangle.RectangleLike | Rectangle.RectangleData): this - add( - x: number | Rectangle.RectangleLike | Rectangle.RectangleData, - y?: number, - width?: number, - height?: number, - ): this { - const rect = Rectangle.create(x, y, width, height) - const minX = Math.min(this.x, rect.x) - const minY = Math.min(this.y, rect.y) - const maxX = Math.max(this.x + this.width, rect.x + rect.width) - const maxY = Math.max(this.y + this.height, rect.y + rect.height) - - this.x = minX - this.y = minY - this.width = maxX - minX - this.height = maxY - minY - - return this - } - - update(x: number, y: number, width: number, height: number): this - update(rect: Rectangle.RectangleLike | Rectangle.RectangleData): this - update( - x: number | Rectangle.RectangleLike | Rectangle.RectangleData, - y?: number, - width?: number, - height?: number, - ): this { - const rect = Rectangle.create(x, y, width, height) - this.x = rect.x - this.y = rect.y - this.width = rect.width - this.height = rect.height - return this - } - - inflate(amount: number): this - /** - * Returns a rectangle inflated in axis-x by `2*dx` and in axis-y by `2*dy`. - */ - inflate(dx: number, dy: number): this - inflate(dx: number, dy?: number): this { - const w = dx - const h = dy != null ? dy : dx - this.x -= w - this.y -= h - this.width += 2 * w - this.height += 2 * h - - return this - } - - /** - * Adjust the position and dimensions of the rectangle such that its edges - * are on the nearest increment of `gx` on the x-axis and `gy` on the y-axis. - */ - snapToGrid(gridSize: number): this - snapToGrid(gx: number, gy: number): this - snapToGrid(gx: number, gy?: number): this - snapToGrid(gx: number, gy?: number): this { - const origin = this.origin.snapToGrid(gx, gy) - const corner = this.corner.snapToGrid(gx, gy) - this.x = origin.x - this.y = origin.y - this.width = corner.x - origin.x - this.height = corner.y - origin.y - return this - } - - translate(tx: number, ty: number): this - translate(p: Point.PointLike | Point.PointData): this - translate(tx: number | Point.PointLike | Point.PointData, ty?: number): this { - const p = Point.create(tx, ty) - this.x += p.x - this.y += p.y - return this - } - - scale( - sx: number, - sy: number, - origin: Point.PointLike | Point.PointData = new Point(), - ) { - const pos = this.origin.scale(sx, sy, origin) - this.x = pos.x - this.y = pos.y - this.width *= sx - this.height *= sy - return this - } - - rotate( - degree: number, - center: Point.PointLike | Point.PointData = this.getCenter(), - ) { - if (degree !== 0) { - const rad = Angle.toRad(degree) - const cos = Math.cos(rad) - const sin = Math.sin(rad) - - let p1 = this.getOrigin() - let p2 = this.getTopRight() - let p3 = this.getBottomRight() - let p4 = this.getBottomLeft() - - p1 = Point.rotateEx(p1, cos, sin, center) - p2 = Point.rotateEx(p2, cos, sin, center) - p3 = Point.rotateEx(p3, cos, sin, center) - p4 = Point.rotateEx(p4, cos, sin, center) - - const rect = new Rectangle(p1.x, p1.y, 0, 0) - rect.add(p2.x, p2.y, 0, 0) - rect.add(p3.x, p3.y, 0, 0) - rect.add(p4.x, p4.y, 0, 0) - - this.update(rect) - } - return this - } - - rotate90() { - const t = (this.width - this.height) / 2 - this.x += t - this.y -= t - const tmp = this.width - this.width = this.height - this.height = tmp - - return this - } - - /** - * Translates the rectangle by `rect.x` and `rect.y` and expand it by - * `rect.width` and `rect.height`. - */ - moveAndExpand(rect: Rectangle.RectangleLike | Rectangle.RectangleData) { - const ref = Rectangle.clone(rect) - this.x += ref.x || 0 - this.y += ref.y || 0 - this.width += ref.width || 0 - this.height += ref.height || 0 - return this - } - - /** - * Returns an object where `sx` and `sy` give the maximum scaling that can be - * applied to the rectangle so that it would still fit into `limit`. If - * `origin` is specified, the rectangle is scaled around it; otherwise, it is - * scaled around its center. - */ - getMaxScaleToFit( - limit: Rectangle.RectangleLike | Rectangle.RectangleData, - origin: Point = this.center, - ) { - const rect = Rectangle.clone(limit) - const ox = origin.x - const oy = origin.y - - // Find the maximal possible scale for all corners, so when the scale - // is applied the point is still inside the rectangle. - let sx1 = Infinity - let sx2 = Infinity - let sx3 = Infinity - let sx4 = Infinity - let sy1 = Infinity - let sy2 = Infinity - let sy3 = Infinity - let sy4 = Infinity - - // Top Left - const p1 = rect.topLeft - if (p1.x < ox) { - sx1 = (this.x - ox) / (p1.x - ox) - } - if (p1.y < oy) { - sy1 = (this.y - oy) / (p1.y - oy) - } - - // Bottom Right - const p2 = rect.bottomRight - if (p2.x > ox) { - sx2 = (this.x + this.width - ox) / (p2.x - ox) - } - if (p2.y > oy) { - sy2 = (this.y + this.height - oy) / (p2.y - oy) - } - - // Top Right - const p3 = rect.topRight - if (p3.x > ox) { - sx3 = (this.x + this.width - ox) / (p3.x - ox) - } - if (p3.y < oy) { - sy3 = (this.y - oy) / (p3.y - oy) - } - - // Bottom Left - const p4 = rect.bottomLeft - if (p4.x < ox) { - sx4 = (this.x - ox) / (p4.x - ox) - } - if (p4.y > oy) { - sy4 = (this.y + this.height - oy) / (p4.y - oy) - } - - return { - sx: Math.min(sx1, sx2, sx3, sx4), - sy: Math.min(sy1, sy2, sy3, sy4), - } - } - - /** - * Returns a number that specifies the maximum scaling that can be applied to - * the rectangle along both axes so that it would still fit into `limit`. If - * `origin` is specified, the rectangle is scaled around it; otherwise, it is - * scaled around its center. - */ - getMaxUniformScaleToFit( - limit: Rectangle.RectangleLike | Rectangle.RectangleData, - origin: Point = this.center, - ) { - const scale = this.getMaxScaleToFit(limit, origin) - return Math.min(scale.sx, scale.sy) - } - - /** - * Returns `true` if the point is inside the rectangle (inclusive). - * Returns `false` otherwise. - */ - containsPoint(x: number, y: number): boolean - containsPoint(point: Point.PointLike | Point.PointData): boolean - containsPoint( - x: number | Point.PointLike | Point.PointData, - y?: number, - ): boolean { - return util.containsPoint(this, Point.create(x, y)) - } - - /** - * Returns `true` if the rectangle is (completely) inside the - * rectangle (inclusive). Returns `false` otherwise. - */ - containsRect(x: number, y: number, w: number, h: number): boolean - containsRect(rect: Rectangle.RectangleLike | Rectangle.RectangleData): boolean - containsRect( - x: number | Rectangle.RectangleLike | Rectangle.RectangleData, - y?: number, - width?: number, - height?: number, - ) { - const b = Rectangle.create(x, y, width, height) - const x1 = this.x - const y1 = this.y - const w1 = this.width - const h1 = this.height - - const x2 = b.x - const y2 = b.y - const w2 = b.width - const h2 = b.height - - // one of the dimensions is 0 - if (w1 === 0 || h1 === 0 || w2 === 0 || h2 === 0) { - return false - } - - return x2 >= x1 && y2 >= y1 && x2 + w2 <= x1 + w1 && y2 + h2 <= y1 + h1 - } - - /** - * Returns an array of the intersection points of the rectangle and the line. - * Return `null` if no intersection exists. - */ - intersectsWithLine(line: Line) { - const rectLines = [ - this.topLine, - this.rightLine, - this.bottomLine, - this.leftLine, - ] - const points: Point[] = [] - const dedupeArr: string[] = [] - rectLines.forEach((l) => { - const p = line.intersectsWithLine(l) - if (p !== null && dedupeArr.indexOf(p.toString()) < 0) { - points.push(p) - dedupeArr.push(p.toString()) - } - }) - - return points.length > 0 ? points : null - } - - /** - * Returns the point on the boundary of the rectangle that is the intersection - * of the rectangle with a line starting in the center the rectangle ending in - * the point `p`. - * - * If `angle` is specified, the intersection will take into account the - * rotation of the rectangle by `angle` degrees around its center. - */ - intersectsWithLineFromCenterToPoint( - p: Point.PointLike | Point.PointData, - angle?: number, - ) { - const ref = Point.clone(p) - const center = this.center - let result: Point | null = null - - if (angle != null && angle !== 0) { - ref.rotate(angle, center) - } - - const sides = [this.topLine, this.rightLine, this.bottomLine, this.leftLine] - const connector = new Line(center, ref) - - for (let i = sides.length - 1; i >= 0; i -= 1) { - const intersection = sides[i].intersectsWithLine(connector) - if (intersection !== null) { - result = intersection - break - } - } - if (result && angle != null && angle !== 0) { - result.rotate(-angle, center) - } - - return result - } - - /** - * Returns a rectangle that is a subtraction of the two rectangles if such an - * object exists (the two rectangles intersect). Returns `null` otherwise. - */ - intersectsWithRect( - x: number, - y: number, - w: number, - h: number, - ): Rectangle | null - intersectsWithRect( - rect: Rectangle.RectangleLike | Rectangle.RectangleData, - ): Rectangle | null - intersectsWithRect( - x: number | Rectangle.RectangleLike | Rectangle.RectangleData, - y?: number, - width?: number, - height?: number, - ) { - const ref = Rectangle.create(x, y, width, height) - - // no intersection - if (!this.isIntersectWithRect(ref)) { - return null - } - - const myOrigin = this.origin - const myCorner = this.corner - const rOrigin = ref.origin - const rCorner = ref.corner - - const xx = Math.max(myOrigin.x, rOrigin.x) - const yy = Math.max(myOrigin.y, rOrigin.y) - - return new Rectangle( - xx, - yy, - Math.min(myCorner.x, rCorner.x) - xx, - Math.min(myCorner.y, rCorner.y) - yy, - ) - } - - isIntersectWithRect(x: number, y: number, w: number, h: number): boolean - isIntersectWithRect( - rect: Rectangle.RectangleLike | Rectangle.RectangleData, - ): boolean - isIntersectWithRect( - x: number | Rectangle.RectangleLike | Rectangle.RectangleData, - y?: number, - width?: number, - height?: number, - ) { - const ref = Rectangle.create(x, y, width, height) - const myOrigin = this.origin - const myCorner = this.corner - const rOrigin = ref.origin - const rCorner = ref.corner - - if ( - rCorner.x <= myOrigin.x || - rCorner.y <= myOrigin.y || - rOrigin.x >= myCorner.x || - rOrigin.y >= myCorner.y - ) { - return false - } - return true - } - - /** - * Normalize the rectangle, i.e. make it so that it has non-negative - * width and height. If width is less than `0`, the function swaps left and - * right corners and if height is less than `0`, the top and bottom corners - * are swapped. - */ - normalize() { - let newx = this.x - let newy = this.y - let newwidth = this.width - let newheight = this.height - if (this.width < 0) { - newx = this.x + this.width - newwidth = -this.width - } - if (this.height < 0) { - newy = this.y + this.height - newheight = -this.height - } - this.x = newx - this.y = newy - this.width = newwidth - this.height = newheight - return this - } - - /** - * Returns a rectangle that is a union of this rectangle and rectangle `rect`. - */ - union(rect: Rectangle.RectangleLike | Rectangle.RectangleData) { - const ref = Rectangle.clone(rect) - const myOrigin = this.origin - const myCorner = this.corner - const rOrigin = ref.origin - const rCorner = ref.corner - - const originX = Math.min(myOrigin.x, rOrigin.x) - const originY = Math.min(myOrigin.y, rOrigin.y) - const cornerX = Math.max(myCorner.x, rCorner.x) - const cornerY = Math.max(myCorner.y, rCorner.y) - - return new Rectangle(originX, originY, cornerX - originX, cornerY - originY) - } - - /** - * Returns a string ("top", "left", "right" or "bottom") denoting the side of - * the rectangle which is nearest to the point `p`. - */ - getNearestSideToPoint(p: Point.PointLike | Point.PointData): Rectangle.Side { - const ref = Point.clone(p) - const distLeft = ref.x - this.x - const distRight = this.x + this.width - ref.x - const distTop = ref.y - this.y - const distBottom = this.y + this.height - ref.y - let closest = distLeft - let side: Rectangle.Side = 'left' - - if (distRight < closest) { - closest = distRight - side = 'right' - } - - if (distTop < closest) { - closest = distTop - side = 'top' - } - - if (distBottom < closest) { - side = 'bottom' - } - - return side - } - - /** - * Returns a point on the boundary of the rectangle nearest to the point `p`. - */ - getNearestPointToPoint(p: Point.PointLike | Point.PointData) { - const ref = Point.clone(p) - if (this.containsPoint(ref)) { - const side = this.getNearestSideToPoint(ref) - switch (side) { - case 'right': - return new Point(this.x + this.width, ref.y) - case 'left': - return new Point(this.x, ref.y) - case 'bottom': - return new Point(ref.x, this.y + this.height) - case 'top': - return new Point(ref.x, this.y) - default: - break - } - } - - return ref.adhereToRect(this) - } - - equals(rect: Rectangle.RectangleLike) { - return ( - rect != null && - rect.x === this.x && - rect.y === this.y && - rect.width === this.width && - rect.height === this.height - ) - } - - clone() { - return new Rectangle(this.x, this.y, this.width, this.height) - } - - toJSON() { - return { x: this.x, y: this.y, width: this.width, height: this.height } - } - - serialize() { - return `${this.x} ${this.y} ${this.width} ${this.height}` - } -} - -export namespace Rectangle { - export const toStringTag = `X6.Geometry.${Rectangle.name}` - - export function isRectangle(instance: any): instance is Rectangle { - if (instance == null) { - return false - } - - if (instance instanceof Rectangle) { - return true - } - - const tag = instance[Symbol.toStringTag] - const rect = instance as Rectangle - - if ( - (tag == null || tag === toStringTag) && - typeof rect.x === 'number' && - typeof rect.y === 'number' && - typeof rect.width === 'number' && - typeof rect.height === 'number' && - typeof rect.inflate === 'function' && - typeof rect.moveAndExpand === 'function' - ) { - return true - } - - return false - } -} - -export namespace Rectangle { - export type RectangleData = [number, number, number, number] - - export interface RectangleLike extends Point.PointLike { - x: number - y: number - width: number - height: number - } - - export function isRectangleLike(o: any): o is RectangleLike { - return ( - o != null && - typeof o === 'object' && - typeof o.x === 'number' && - typeof o.y === 'number' && - typeof o.width === 'number' && - typeof o.height === 'number' - ) - } - - export type Side = 'left' | 'right' | 'top' | 'bottom' - - export type KeyPoint = - | 'center' - | 'origin' - | 'corner' - | 'topLeft' - | 'topCenter' - | 'topRight' - | 'bottomLeft' - | 'bottomCenter' - | 'bottomRight' - | 'rightMiddle' - | 'leftMiddle' -} - -export namespace Rectangle { - export function create(rect: RectangleLike | RectangleData): Rectangle - export function create( - x?: number, - y?: number, - width?: number, - height?: number, - ): Rectangle - export function create( - x?: number | RectangleLike | RectangleData, - y?: number, - width?: number, - height?: number, - ): Rectangle - export function create( - x?: number | RectangleLike | RectangleData, - y?: number, - width?: number, - height?: number, - ): Rectangle { - if (x == null || typeof x === 'number') { - return new Rectangle(x, y, width, height) - } - - return clone(x) - } - - export function clone(rect: RectangleLike | RectangleData) { - if (Rectangle.isRectangle(rect)) { - return rect.clone() - } - - if (Array.isArray(rect)) { - return new Rectangle(rect[0], rect[1], rect[2], rect[3]) - } - - return new Rectangle(rect.x, rect.y, rect.width, rect.height) - } - - export function fromSize(size: Size) { - return new Rectangle(0, 0, size.width, size.height) - } - - export function fromPositionAndSize(pos: Point.PointLike, size: Size) { - return new Rectangle(pos.x, pos.y, size.width, size.height) - } - - /** - * Returns a new rectangle from the given ellipse. - */ - export function fromEllipse(ellipse: Ellipse) { - return new Rectangle( - ellipse.x - ellipse.a, - ellipse.y - ellipse.b, - 2 * ellipse.a, - 2 * ellipse.b, - ) - } -} diff --git a/packages/x6/src/geometry/util.ts b/packages/x6/src/geometry/util.ts deleted file mode 100644 index dc8845591ac..00000000000 --- a/packages/x6/src/geometry/util.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Point } from './point' -import { Rectangle } from './rectangle' - -export function round(num: number, precision = 0) { - return Number.isInteger(num) ? num : +num.toFixed(precision) -} - -export function random(): number -export function random(max: number): number -export function random(min: number, max: number): number -export function random(min?: number, max?: number): number { - let mmin - let mmax - - if (max == null) { - mmax = min == null ? 1 : min - mmin = 0 - } else { - mmax = max - mmin = min == null ? 0 : min - } - - if (mmax < mmin) { - const temp = mmin - mmin = mmax - mmax = temp - } - - return Math.floor(Math.random() * (mmax - mmin + 1) + mmin) -} - -export function clamp(value: number, min: number, max: number) { - if (Number.isNaN(value)) { - return NaN - } - - if (Number.isNaN(min) || Number.isNaN(max)) { - return 0 - } - - return min < max - ? value < min - ? min - : value > max - ? max - : value - : value < max - ? max - : value > min - ? min - : value -} - -export function snapToGrid(value: number, gridSize: number) { - return gridSize * Math.round(value / gridSize) -} - -export function containsPoint( - rect: Rectangle.RectangleLike, - point: Point.PointLike, -) { - return ( - point != null && - rect != null && - point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height - ) -} - -export function squaredLength(p1: Point.PointLike, p2: Point.PointLike) { - const dx = p1.x - p2.x - const dy = p1.y - p2.y - return dx * dx + dy * dy -} diff --git a/packages/x6/src/global/config.ts b/packages/x6/src/global/config.ts deleted file mode 100644 index 86a81ad77a9..00000000000 --- a/packages/x6/src/global/config.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const Config = { - prefixCls: 'x6', - autoInsertCSS: true, - useCSSSelector: true, - trackable: false, - trackInfo: {}, - - /** - * Turn on/off collect information of user client. - * - * In order to serve the users better, x6 will send the URL and version - * information back to the AntV server:https://kcart.alipay.com/web/bi.do - * - * Except for URL and G2 version information, no other information will - * be collected. - * - * @param enabled Specifies if seed client information to AntV server. - */ - track(enabled: boolean) { - Config.trackable = enabled - }, -} diff --git a/packages/x6/src/global/index.ts b/packages/x6/src/global/index.ts deleted file mode 100644 index 11fcaf146dd..00000000000 --- a/packages/x6/src/global/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './util' -export * from './config' -export * from './version' diff --git a/packages/x6/src/global/track.ts b/packages/x6/src/global/track.ts deleted file mode 100644 index 35a402cf8e3..00000000000 --- a/packages/x6/src/global/track.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Config } from './config' -import { version } from './version' - -function track() { - if (Config.trackable) { - const host = 'https://kcart.alipay.com/web/bi.do' - const img = new Image() - const metadata = { - ...Config.trackInfo, - version, - pg: document.URL, - r: new Date().getTime(), - x6: true, - page_type: 'syslog', - } - const data = encodeURIComponent(JSON.stringify([metadata])) - img.src = `${host}?BIProfile=merge&d=${data}` - } -} - -if (process.env.NODE_ENV !== 'development' && Config.trackable) { - setTimeout(track, 3000) -} diff --git a/packages/x6/src/global/util.ts b/packages/x6/src/global/util.ts deleted file mode 100644 index 3bebfe43ea3..00000000000 --- a/packages/x6/src/global/util.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { KeyValue } from '../types' -import { snapToGrid as snap } from '../geometry/util' -import { normalize } from '../registry/marker/util' -import { Cell } from '../model/cell' -import { Node } from '../model/node' -import { Edge } from '../model/edge' -import { Config } from './config' - -export namespace Util { - export const snapToGrid = snap - export const normalizeMarker = normalize - - export function prefix(suffix: string) { - return `${Config.prefixCls}-${suffix}` - } -} - -export namespace Util { - export interface TreeItem extends KeyValue { - name: string - } - - export interface MakeTreeOptions { - children?: string | ((parent: TreeItem) => TreeItem[]) - createNode: (metadata: TreeItem) => Node - createEdge: (parent: Node, child: Node) => Edge - } - - export function makeTree( - parent: TreeItem, - options: MakeTreeOptions, - parentNode: Node, - collector: Cell[] = [], - ) { - const children = - typeof options.children === 'function' - ? options.children(parent) - : parent[options.children || 'children'] - - if (!parentNode) { - parentNode = options.createNode(parent) // eslint-disable-line - collector.push(parentNode) - } - - if (Array.isArray(children)) { - children.forEach((child: TreeItem) => { - const node = options.createNode(child) - const edge = options.createEdge(parentNode, node) - collector.push(node, edge) - this.makeTree(child, options, node, collector) - }) - } - - return collector - } -} diff --git a/packages/x6/src/global/version.test.ts b/packages/x6/src/global/version.test.ts deleted file mode 100644 index 4ed8cd95324..00000000000 --- a/packages/x6/src/global/version.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { version } from './version' - -describe('version', () => { - it('should match the `version` field of package.json', () => { - // eslint-disable-next-line - const expected = require('../../package.json').version - expect(version).toBe(expected) - }) -}) diff --git a/packages/x6/src/global/version.ts b/packages/x6/src/global/version.ts deleted file mode 100644 index 1f3803bdf17..00000000000 --- a/packages/x6/src/global/version.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable */ - -/** - * Auto generated version file, do not modify it! - */ -const version = '1.30.2' -export { version } diff --git a/packages/x6/src/graph/background.ts b/packages/x6/src/graph/background.ts index 27313957927..914380971d0 100644 --- a/packages/x6/src/graph/background.ts +++ b/packages/x6/src/graph/background.ts @@ -1,5 +1,5 @@ -import { ObjectExt } from '../util' -import { Rectangle } from '../geometry' +import { ObjectExt } from '@antv/x6-common' +import { Rectangle } from '@antv/x6-geometry' import { Background } from '../registry' import { Base } from './base' diff --git a/packages/x6/src/graph/base.ts b/packages/x6/src/graph/base.ts index 92d2d52640a..80475105884 100644 --- a/packages/x6/src/graph/base.ts +++ b/packages/x6/src/graph/base.ts @@ -1,5 +1,5 @@ +import { Disposable } from '@antv/x6-common' import { Graph } from './graph' -import { Disposable } from '../common' export class Base extends Disposable { public readonly graph: Graph diff --git a/packages/x6/src/graph/clipboard.ts b/packages/x6/src/graph/clipboard.ts deleted file mode 100644 index 9bc080c9b55..00000000000 --- a/packages/x6/src/graph/clipboard.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { IDisablable } from '../common' -import { Clipboard } from '../addon/clipboard' -import { Cell } from '../model/cell' -import { Graph } from './graph' -import { Base } from './base' - -export class ClipboardManager extends Base implements IDisablable { - public widget: Clipboard - - protected get commonOptions() { - const { enabled, ...others } = this.instanceOptions - return others - } - - protected get instanceOptions() { - return this.options.clipboard - } - - get cells() { - return this.widget.cells - } - - get disabled() { - return this.instanceOptions.enabled !== true - } - - protected init() { - this.widget = this.graph.hook.createClipboard() - this.widget.deserialize(this.instanceOptions) - } - - enable() { - if (this.disabled) { - this.instanceOptions.enabled = true - } - } - - disable() { - if (!this.disabled) { - this.instanceOptions.enabled = false - } - } - - copy(cells: Cell[], options: Clipboard.CopyOptions = {}) { - if (!this.disabled) { - this.widget.copy(cells, this.graph, { - ...this.commonOptions, - ...options, - }) - } - } - - cut(cells: Cell[], options: Clipboard.CopyOptions = {}) { - if (!this.disabled) { - this.widget.cut(cells, this.graph, { - ...this.commonOptions, - ...options, - }) - } - } - - paste(options: Clipboard.PasteOptions = {}, graph: Graph = this.graph) { - if (!this.disabled) { - return this.widget.paste(graph, { - ...this.commonOptions, - ...options, - }) - } - return [] - } - - clean(force?: boolean) { - if (!this.disabled || force) { - this.widget.clean() - } - } - - isEmpty() { - return this.widget.isEmpty() - } - - @Base.dispose() - dispose() { - this.clean(true) - } -} - -export namespace ClipboardManager { - export interface Options extends Clipboard.Options { - enabled?: boolean - } - - export interface CopyOptions extends Clipboard.CopyOptions {} - export interface PasteOptions extends Clipboard.PasteOptions {} -} diff --git a/packages/x6/src/graph/coord.ts b/packages/x6/src/graph/coord.ts index ea140d71fa7..0c951ca37a0 100644 --- a/packages/x6/src/graph/coord.ts +++ b/packages/x6/src/graph/coord.ts @@ -1,6 +1,7 @@ +import { Dom } from '@antv/x6-common' +import { Point, Rectangle } from '@antv/x6-geometry' import { Base } from './base' -import { Dom } from '../util' -import { Point, Rectangle } from '../geometry' +import { Util } from '../util' export class CoordManager extends Base { getClientMatrix() { @@ -34,12 +35,12 @@ export class CoordManager extends Base { localToGraphPoint(x: number | Point | Point.PointLike, y?: number) { const localPoint = Point.create(x, y) - return Dom.transformPoint(localPoint, this.graph.matrix()) + return Util.transformPoint(localPoint, this.graph.matrix()) } localToClientPoint(x: number | Point | Point.PointLike, y?: number) { const localPoint = Point.create(x, y) - return Dom.transformPoint(localPoint, this.getClientMatrix()) + return Util.transformPoint(localPoint, this.getClientMatrix()) } localToPagePoint(x: number | Point | Point.PointLike, y?: number) { @@ -57,7 +58,7 @@ export class CoordManager extends Base { height?: number, ) { const localRect = Rectangle.create(x, y, width, height) - return Dom.transformRectangle(localRect, this.graph.matrix()) + return Util.transformRectangle(localRect, this.graph.matrix()) } localToClientRect( @@ -67,7 +68,7 @@ export class CoordManager extends Base { height?: number, ) { const localRect = Rectangle.create(x, y, width, height) - return Dom.transformRectangle(localRect, this.getClientMatrix()) + return Util.transformRectangle(localRect, this.getClientMatrix()) } localToPageRect( @@ -85,17 +86,17 @@ export class CoordManager extends Base { graphToLocalPoint(x: number | Point | Point.PointLike, y?: number) { const graphPoint = Point.create(x, y) - return Dom.transformPoint(graphPoint, this.graph.matrix().inverse()) + return Util.transformPoint(graphPoint, this.graph.matrix().inverse()) } clientToLocalPoint(x: number | Point | Point.PointLike, y?: number) { const clientPoint = Point.create(x, y) - return Dom.transformPoint(clientPoint, this.getClientMatrix().inverse()) + return Util.transformPoint(clientPoint, this.getClientMatrix().inverse()) } clientToGraphPoint(x: number | Point | Point.PointLike, y?: number) { const clientPoint = Point.create(x, y) - return Dom.transformPoint( + return Util.transformPoint( clientPoint, this.graph.matrix().multiply(this.getClientMatrix().inverse()), ) @@ -114,7 +115,7 @@ export class CoordManager extends Base { height?: number, ) { const graphRect = Rectangle.create(x, y, width, height) - return Dom.transformRectangle(graphRect, this.graph.matrix().inverse()) + return Util.transformRectangle(graphRect, this.graph.matrix().inverse()) } clientToLocalRect( @@ -124,7 +125,7 @@ export class CoordManager extends Base { height?: number, ) { const clientRect = Rectangle.create(x, y, width, height) - return Dom.transformRectangle(clientRect, this.getClientMatrix().inverse()) + return Util.transformRectangle(clientRect, this.getClientMatrix().inverse()) } clientToGraphRect( @@ -134,7 +135,7 @@ export class CoordManager extends Base { height?: number, ) { const clientRect = Rectangle.create(x, y, width, height) - return Dom.transformRectangle( + return Util.transformRectangle( clientRect, this.graph.matrix().multiply(this.getClientMatrix().inverse()), ) diff --git a/packages/x6/src/graph/css.ts b/packages/x6/src/graph/css.ts index faa1cc29cce..593ff7ddc58 100644 --- a/packages/x6/src/graph/css.ts +++ b/packages/x6/src/graph/css.ts @@ -1,48 +1,17 @@ -import { Config } from '../global/config' +import { CssLoader } from '@antv/x6-common' +import { Config } from '../config' import { content } from '../style/raw' -import { Platform } from '../util' import { Base } from './base' export class CSSManager extends Base { protected init() { if (Config.autoInsertCSS) { - CSSManager.ensure() + CssLoader.ensure('core', content) } } @CSSManager.dispose() dispose() { - CSSManager.clean() - } -} - -export namespace CSSManager { - let styleElement: HTMLStyleElement | null - let counter = 0 - - export function ensure() { - counter += 1 - if (counter > 1) return - - if (!Platform.isApplyingHMR()) { - styleElement = document.createElement('style') - styleElement.setAttribute('type', 'text/css') - styleElement.textContent = content - - const head = document.querySelector('head') as HTMLHeadElement - if (head) { - head.insertBefore(styleElement, head.firstChild) - } - } - } - - export function clean() { - counter -= 1 - if (counter > 0) return - - if (styleElement && styleElement.parentNode) { - styleElement.parentNode.removeChild(styleElement) - } - styleElement = null + CssLoader.clean('core') } } diff --git a/packages/x6/src/graph/decorator.ts b/packages/x6/src/graph/decorator.ts deleted file mode 100644 index 95307ea036d..00000000000 --- a/packages/x6/src/graph/decorator.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Graph } from './graph' - -export namespace Decorator { - export function checkScroller(err?: boolean, warning?: boolean) { - return ( - target: Graph, - methodName: string, - descriptor: PropertyDescriptor, - ) => { - const raw = descriptor.value - - descriptor.value = function (this: Graph, ...args: any[]) { - const scroller = this.scroller.widget - if (scroller == null) { - const msg = `Shoule enable scroller to use method '${methodName}'` - if (err !== false) { - console.error(msg) - throw new Error(msg) - } - if (warning !== false) { - console.warn(msg) - } - return this - } - return raw.call(this, ...args) - } - } - } -} diff --git a/packages/x6/src/graph/defs.ts b/packages/x6/src/graph/defs.ts index 2cfc3d592b7..7e74502fd47 100644 --- a/packages/x6/src/graph/defs.ts +++ b/packages/x6/src/graph/defs.ts @@ -1,4 +1,4 @@ -import { StringExt, Dom, Vector } from '../util' +import { StringExt, Dom, Vector } from '@antv/x6-common' import { Attr, Filter, Marker } from '../registry' import { Markup } from '../view' import { Base } from './base' diff --git a/packages/x6/src/graph/events.ts b/packages/x6/src/graph/events.ts index 929eeacb4f7..d92025b49ba 100644 --- a/packages/x6/src/graph/events.ts +++ b/packages/x6/src/graph/events.ts @@ -1,7 +1,6 @@ +import { Dom } from '@antv/x6-common' import { Model } from '../model' import { CellView } from '../view' -import { Selection } from '../addon/selection' -import { Renderer } from './renderer' interface CommonEventArgs { e: E @@ -14,42 +13,26 @@ interface PositionEventArgs extends CommonEventArgs { export interface EventArgs extends Omit, - CellView.EventArgs, - Selection.SelectionEventArgs { + CellView.EventArgs { 'model:sorted'?: Model.EventArgs['sorted'] 'model:updated': Model.EventArgs['updated'] 'model:reseted': Model.EventArgs['reseted'] - 'blank:click': PositionEventArgs - 'blank:dblclick': PositionEventArgs - 'blank:contextmenu': PositionEventArgs - 'blank:mousedown': PositionEventArgs - 'blank:mousemove': PositionEventArgs - 'blank:mouseup': PositionEventArgs - 'blank:mouseout': CommonEventArgs - 'blank:mouseover': CommonEventArgs - 'graph:mouseenter': CommonEventArgs - 'graph:mouseleave': CommonEventArgs - 'blank:mousewheel': PositionEventArgs & { + 'blank:click': PositionEventArgs + 'blank:dblclick': PositionEventArgs + 'blank:contextmenu': PositionEventArgs + 'blank:mousedown': PositionEventArgs + 'blank:mousemove': PositionEventArgs + 'blank:mouseup': PositionEventArgs + 'blank:mouseout': CommonEventArgs + 'blank:mouseover': CommonEventArgs + 'graph:mouseenter': CommonEventArgs + 'graph:mouseleave': CommonEventArgs + 'blank:mousewheel': PositionEventArgs & { delta: number } - 'tools:event': { name: string } - 'tools:remove'?: null - 'tools:hide'?: null - 'tools:show'?: null - - 'render:done': { - stats: { - priority: number - updatedCount: number - } - options: Renderer.UpdateViewsAsyncOptions - } - scale: { sx: number; sy: number; ox: number; oy: number } resize: { width: number; height: number } translate: { tx: number; ty: number } - freeze: { key?: string } - unfreeze: { key?: string } } diff --git a/packages/x6/src/graph/format.ts b/packages/x6/src/graph/format.ts deleted file mode 100644 index bc0409e0770..00000000000 --- a/packages/x6/src/graph/format.ts +++ /dev/null @@ -1,352 +0,0 @@ -import JQuery from 'jquery' -import { DataUri, NumberExt, FunctionExt, Vector } from '../util' -import { Size, KeyValue } from '../types' -import { Rectangle } from '../geometry' -import { Graph } from './graph' -import { Base } from './base' - -export class FormatManager extends Base { - toSVG( - callback: FormatManager.ToSVGCallback, - options: FormatManager.ToSVGOptions = {}, - ) { - this.graph.trigger('before:export', options) - - const rawSVG = this.view.svg - const vSVG = Vector.create(rawSVG).clone() - let clonedSVG = vSVG.node as SVGSVGElement - const vStage = vSVG.findOne( - `.${this.view.prefixClassName('graph-svg-stage')}`, - )! - - const viewBox = - options.viewBox || this.graph.graphToLocal(this.graph.getContentBBox()) - const dimension = options.preserveDimensions - if (dimension) { - const size = typeof dimension === 'boolean' ? viewBox : dimension - vSVG.attr({ - width: size.width, - height: size.height, - }) - } - - vSVG - .removeAttribute('style') - .attr( - 'viewBox', - [viewBox.x, viewBox.y, viewBox.width, viewBox.height].join(' '), - ) - - vStage.removeAttribute('transform') - - // Stores all the CSS declarations from external stylesheets to the - // `style` attribute of the SVG document nodes. - - // This is achieved in three steps. - // ----------------------------------- - - // 1. Disabling all the stylesheets in the page and therefore collecting - // only default style values. This, together with the step 2, makes it - // possible to discard default CSS property values and store only those - // that differ. - // - // 2. Enabling back all the stylesheets in the page and collecting styles - // that differ from the default values. - // - // 3. Applying the difference between default values and the ones set by - // custom stylesheets onto the `style` attribute of each of the nodes - // in SVG. - - if (options.copyStyles !== false) { - const document = rawSVG.ownerDocument! - const raws = Array.from(rawSVG.querySelectorAll('*')) - const clones = Array.from(clonedSVG.querySelectorAll('*')) - - const styleSheetCount = document.styleSheets.length - const styleSheetsCopy = [] - for (let k = styleSheetCount - 1; k >= 0; k -= 1) { - // There is a bug (bugSS) in Chrome 14 and Safari. When you set - // `stylesheet.disable = true` it will also remove it from - // `document.styleSheets`. So we need to store all stylesheets before - // we disable them. Later on we put them back to `document.styleSheets` - // if needed. - - // See the bug `https://code.google.com/p/chromium/issues/detail?id=88310`. - styleSheetsCopy[k] = document.styleSheets[k] - document.styleSheets[k].disabled = true - } - - const defaultComputedStyles: KeyValue> = {} - raws.forEach((elem, index) => { - const computedStyle = window.getComputedStyle(elem, null) - // We're making a deep copy of the `computedStyle` so that it's not affected - // by that next step when all the stylesheets are re-enabled again. - const defaultComputedStyle: KeyValue = {} - Object.keys(computedStyle).forEach((property) => { - defaultComputedStyle[property] = - computedStyle.getPropertyValue(property) - }) - - defaultComputedStyles[index] = defaultComputedStyle - }) - - // Copy all stylesheets back - if (styleSheetCount !== document.styleSheets.length) { - styleSheetsCopy.forEach((copy, index) => { - document.styleSheets[index] = copy - }) - } - - for (let i = 0; i < styleSheetCount; i += 1) { - document.styleSheets[i].disabled = false - } - - const customStyles: KeyValue> = {} - raws.forEach((elem, index) => { - const computedStyle = window.getComputedStyle(elem, null) - const defaultComputedStyle = defaultComputedStyles[index] - const customStyle: KeyValue = {} - - Object.keys(computedStyle).forEach((property) => { - if ( - !NumberExt.isNumeric(property) && - computedStyle.getPropertyValue(property) !== - defaultComputedStyle[property] - ) { - customStyle[property] = computedStyle.getPropertyValue(property) - } - }) - - customStyles[index] = customStyle - }) - - clones.forEach((elem, index) => { - JQuery(elem).css(customStyles[index]) - }) - } - - const stylesheet = options.stylesheet - if (typeof stylesheet === 'string') { - const cDATASection = rawSVG - .ownerDocument!.implementation.createDocument(null, 'xml', null) - .createCDATASection(stylesheet) - - vSVG.prepend( - Vector.create( - 'style', - { - type: 'text/css', - }, - [cDATASection as any], - ), - ) - } - - const format = () => { - const beforeSerialize = options.beforeSerialize - if (typeof beforeSerialize === 'function') { - const ret = FunctionExt.call(beforeSerialize, this.graph, clonedSVG) - if (ret instanceof SVGSVGElement) { - clonedSVG = ret - } - } - - const dataUri = new XMLSerializer() - .serializeToString(clonedSVG) - .replace(/ /g, '\u00a0') - - this.graph.trigger('after:export', options) - callback(dataUri) - } - - if (options.serializeImages) { - const deferrals = vSVG.find('image').map((vImage) => { - return new Promise((resolve) => { - const url = vImage.attr('xlink:href') || vImage.attr('href') - DataUri.imageToDataUri(url, (err, dataUri) => { - if (!err && dataUri) { - vImage.attr('xlink:href', dataUri) - } - resolve() - }) - }) - }) - - Promise.all(deferrals).then(format) - } else { - format() - } - } - - toDataURL( - callback: FormatManager.ToSVGCallback, - options: FormatManager.ToDataURLOptions, - ) { - let viewBox = options.viewBox || this.graph.getContentBBox() - - const padding = NumberExt.normalizeSides(options.padding) - if (options.width && options.height) { - if (padding.left + padding.right >= options.width) { - padding.left = padding.right = 0 - } - if (padding.top + padding.bottom >= options.height) { - padding.top = padding.bottom = 0 - } - } - - const expanding = new Rectangle( - -padding.left, - -padding.top, - padding.left + padding.right, - padding.top + padding.bottom, - ) - - if (options.width && options.height) { - const width = viewBox.width + padding.left + padding.right - const height = viewBox.height + padding.top + padding.bottom - expanding.scale(width / options.width, height / options.height) - } - - viewBox = Rectangle.create(viewBox).moveAndExpand(expanding) - - const rawSize = - typeof options.width === 'number' && typeof options.height === 'number' - ? { width: options.width, height: options.height } - : viewBox - - let scale = options.ratio ? parseFloat(options.ratio) : 1 - if (!Number.isFinite(scale) || scale === 0) { - scale = 1 - } - - const size = { - width: Math.max(Math.round(rawSize.width * scale), 1), - height: Math.max(Math.round(rawSize.height * scale), 1), - } - - { - const imgDataCanvas = document.createElement('canvas') - const context2D = imgDataCanvas.getContext('2d')! - imgDataCanvas.width = size.width - imgDataCanvas.height = size.height - const x = size.width - 1 - const y = size.height - 1 - context2D.fillStyle = 'rgb(1,1,1)' - context2D.fillRect(x, y, 1, 1) - const data = context2D.getImageData(x, y, 1, 1).data - if (data[0] !== 1 || data[1] !== 1 || data[2] !== 1) { - throw new Error('size exceeded') - } - } - - const img = new Image() - img.onload = () => { - const canvas = document.createElement('canvas') - canvas.width = size.width - canvas.height = size.height - - const context = canvas.getContext('2d')! - context.fillStyle = options.backgroundColor || 'white' - context.fillRect(0, 0, size.width, size.height) - - try { - context.drawImage(img, 0, 0, size.width, size.height) - const dataUri = canvas.toDataURL(options.type, options.quality) - callback(dataUri) - } catch (error) { - // pass - } - } - - this.toSVG( - (dataUri) => { - img.src = `data:image/svg+xml,${encodeURIComponent(dataUri)}` - }, - { - ...options, - viewBox, - serializeImages: true, - preserveDimensions: { - ...size, - }, - }, - ) - } - - toPNG( - callback: FormatManager.ToSVGCallback, - options: FormatManager.ToImageOptions = {}, - ) { - this.toDataURL(callback, { - ...options, - type: 'image/png', - }) - } - - toJPEG( - callback: FormatManager.ToSVGCallback, - options: FormatManager.ToImageOptions = {}, - ) { - this.toDataURL(callback, { - ...options, - type: 'image/jpeg', - }) - } -} - -export namespace FormatManager { - export type ToSVGCallback = (dataUri: string) => any - - export interface ToSVGOptions { - /** - * By default, the resulting SVG has set width and height to `100%`. - * If you'd like to have the dimensions to be set to the actual content - * width and height, set `preserveDimensions` to `true`. An object with - * `width` and `height` properties can be also used here if you need to - * define the export size explicitely. - */ - preserveDimensions?: boolean | Size - - viewBox?: Rectangle.RectangleLike - - /** - * When set to `true` all the styles from external stylesheets are copied - * to the resulting SVG export. Note this requires a lot of computations - * and it might significantly affect the export time. - */ - copyStyles?: boolean - - stylesheet?: string - - /** - * Converts all contained images into Data URI format. - */ - serializeImages?: boolean - - /** - * A function called before the XML serialization. It may be used to - * modify the exported SVG before it is converted to a string. The - * function can also return a new SVGDocument. - */ - beforeSerialize?: (this: Graph, svg: SVGSVGElement) => any - } - - export interface ToImageOptions extends ToSVGOptions { - /** - * The width of the image in pixels. - */ - width?: number - /** - * The height of the image in pixels. - */ - height?: number - ratio?: string - backgroundColor?: string - padding?: NumberExt.SideOptions - quality?: number - } - - export interface ToDataURLOptions extends ToImageOptions { - type: 'image/png' | 'image/jpeg' - } -} diff --git a/packages/x6/src/graph/graph.ts b/packages/x6/src/graph/graph.ts index ccb918d207b..65be3fdf69f 100644 --- a/packages/x6/src/graph/graph.ts +++ b/packages/x6/src/graph/graph.ts @@ -1,73 +1,44 @@ -import { Basecoat } from '../common' -import { NumberExt, Dom } from '../util' -import { Point, Rectangle } from '../geometry' -import { KeyValue, ModifierKey } from '../types' -import { Cell } from '../model/cell' -import { Node } from '../model/node' -import { Edge } from '../model/edge' -import { Model } from '../model/model' -import { Collection } from '../model/collection' -import { CellView } from '../view/cell' +import { Basecoat, NumberExt, Dom, KeyValue } from '@antv/x6-common' +import { Point, Rectangle } from '@antv/x6-geometry' +import { Model, Collection, Cell, Node, Edge } from '../model' import * as Registry from '../registry' -import { HTML } from '../shape/standard/html' -import { Scroller as ScrollerWidget } from '../addon/scroller' import { Base } from './base' import { GraphView } from './view' import { EventArgs } from './events' -import { Decorator } from './decorator' -import { CSSManager } from './css' -import { SizeManager } from './size' -import { Hook as HookManager } from './hook' +import { CSSManager as Css } from './css' import { Options as GraphOptions } from './options' -import { DefsManager as Defs } from './defs' import { GridManager as Grid } from './grid' -import { CoordManager as Coord } from './coord' -import { Keyboard as Shortcut } from './keyboard' -import { KnobManager as Knob } from './knob' -import { PrintManager as Print } from './print' -import { MouseWheel as Wheel } from './mousewheel' -import { FormatManager as Format } from './format' -import { Renderer as ViewRenderer } from './renderer' -import { HistoryManager as History } from './history' -import { PanningManager as Panning } from './panning' -import { MiniMapManager as MiniMap } from './minimap' -import { SnaplineManager as Snapline } from './snapline' -import { ScrollerManager as Scroller } from './scroller' -import { SelectionManager as Selection } from './selection' -import { HighlightManager as Highlight } from './highlight' import { TransformManager as Transform } from './transform' -import { ClipboardManager as Clipboard } from './clipboard' import { BackgroundManager as Background } from './background' +import { PanningManager as Panning } from './panning' +import { MouseWheel as Wheel } from './mousewheel' +import { VirtualRenderManager as VirtualRender } from './virtual-render' +import { Renderer as ViewRenderer } from '../renderer' +import { DefsManager as Defs } from './defs' +import { CoordManager as Coord } from './coord' +import { HighlightManager as Highlight } from './highlight' +import { CellView } from '../view' export class Graph extends Basecoat { + private installedPlugins: Set = new Set() + public readonly options: GraphOptions.Definition - public readonly css: CSSManager + public readonly css: Css public readonly model: Model public readonly view: GraphView - public readonly hook: HookManager public readonly grid: Grid public readonly defs: Defs - public readonly knob: Knob public readonly coord: Coord public readonly renderer: ViewRenderer - public readonly snapline: Snapline public readonly highlight: Highlight public readonly transform: Transform - public readonly clipboard: Clipboard - public readonly selection: Selection public readonly background: Background - public readonly history: History - public readonly scroller: Scroller - public readonly minimap: MiniMap - public readonly keyboard: Shortcut - public readonly mousewheel: Wheel public readonly panning: Panning - public readonly print: Print - public readonly format: Format - public readonly size: SizeManager + public readonly mousewheel: Wheel + public readonly virtualRender: VirtualRender public get container() { - return this.view.container + return this.options.container } protected get [Symbol.toStringTag]() { @@ -76,32 +47,23 @@ export class Graph extends Basecoat { constructor(options: Partial) { super() - this.options = GraphOptions.get(options) - this.css = new CSSManager(this) - this.hook = new HookManager(this) - this.view = this.hook.createView() - this.defs = this.hook.createDefsManager() - this.coord = this.hook.createCoordManager() - this.transform = this.hook.createTransformManager() - this.knob = this.hook.createKnobManager() - this.highlight = this.hook.createHighlightManager() - this.grid = this.hook.createGridManager() - this.background = this.hook.createBackgroundManager() - this.model = this.hook.createModel() - this.renderer = this.hook.createRenderer() - this.clipboard = this.hook.createClipboardManager() - this.snapline = this.hook.createSnaplineManager() - this.selection = this.hook.createSelectionManager() - this.history = this.hook.createHistoryManager() - this.scroller = this.hook.createScrollerManager() - this.minimap = this.hook.createMiniMapManager() - this.keyboard = this.hook.createKeyboard() - this.mousewheel = this.hook.createMouseWheel() - this.print = this.hook.createPrintManager() - this.format = this.hook.createFormatManager() - this.panning = this.hook.createPanningManager() - this.size = this.hook.createSizeManager() + this.css = new Css(this) + this.view = new GraphView(this) + this.defs = new Defs(this) + this.coord = new Coord(this) + this.transform = new Transform(this) + this.highlight = new Highlight(this) + this.grid = new Grid(this) + this.background = new Background(this) + + this.model = this.options.model ? this.options.model : new Model() + this.model.graph = this + + this.renderer = new ViewRenderer(this) + this.panning = new Panning(this) + this.mousewheel = new Wheel(this) + this.virtualRender = new VirtualRender(this) } // #region model @@ -217,16 +179,6 @@ export class Graph extends Basecoat { return this.model.has(cell as Cell) } - /** - * **Deprecation Notice:** `getCell` is deprecated and will be moved in next - * major release. Use `getCellById()` instead. - * - * @deprecated - */ - getCell(id: string) { - return this.model.getCell(id) - } - getCells() { return this.model.getCells() } @@ -504,30 +456,7 @@ export class Graph extends Basecoat { // #region view - isFrozen() { - return this.renderer.isFrozen() - } - - freeze(options: ViewRenderer.FreezeOptions = {}) { - this.renderer.freeze(options) - return this - } - - unfreeze(options: ViewRenderer.UnfreezeOptions = {}) { - this.renderer.unfreeze(options) - return this - } - - isAsync() { - return this.renderer.isAsync() - } - - setAsync(async: boolean) { - this.renderer.setAsync(async) - return this - } - - findView(ref: Cell | JQuery | Element) { + findView(ref: Cell | Element) { if (Cell.isCell(ref)) { return this.findViewByCell(ref) } @@ -555,7 +484,7 @@ export class Graph extends Basecoat { return this.renderer.findViewByCell(cell as Cell) } - findViewByElem(elem: string | JQuery | Element | undefined | null) { + findViewByElem(elem: string | Element | undefined | null) { return this.renderer.findViewByElem(elem) } @@ -600,18 +529,6 @@ export class Graph extends Basecoat { return this.renderer.findViewsInArea(rect, localOptions) } - isViewMounted(view: CellView) { - return this.renderer.isViewMounted(view) - } - - getMountedViews() { - return this.renderer.getMountedViews() - } - - getUnmountedViews() { - return this.renderer.getUnmountedViews() - } - // #endregion // #region transform @@ -633,22 +550,7 @@ export class Graph extends Basecoat { } resize(width?: number, height?: number) { - this.size.resize(width, height) - return this - } - - resizeGraph(width?: number, height?: number) { - this.size.resizeGraph(width, height) - return this - } - - resizeScroller(width?: number, height?: number) { - this.size.resizeScroller(width, height) - return this - } - - resizePage(width?: number, height?: number) { - this.size.resizePage(width, height) + this.transform.resize(width, height) return this } @@ -665,18 +567,10 @@ export class Graph extends Basecoat { zoom(): number zoom(factor: number, options?: Transform.ZoomOptions): this zoom(factor?: number, options?: Transform.ZoomOptions) { - const scroller = this.scroller.widget - if (scroller) { - if (typeof factor === 'undefined') { - return scroller.zoom() - } - scroller.zoom(factor, options) - } else { - if (typeof factor === 'undefined') { - return this.transform.getZoom() - } - this.transform.zoom(factor, options) + if (typeof factor === 'undefined') { + return this.transform.getZoom() } + this.transform.zoom(factor, options) return this } @@ -685,12 +579,9 @@ export class Graph extends Basecoat { factor: number, options: Omit = {}, ) { - const scroller = this.scroller.widget - if (scroller) { - scroller.zoom(factor, { ...options, absolute: true }) - } else { - this.transform.zoom(factor, { ...options, absolute: true }) - } + this.transform.zoom(factor, { ...options, absolute: true }) + + return this } zoomToRect( @@ -698,12 +589,7 @@ export class Graph extends Basecoat { options: Transform.ScaleContentToFitOptions & Transform.ScaleContentToFitOptions = {}, ) { - const scroller = this.scroller.widget - if (scroller) { - scroller.zoomToRect(rect, options) - } else { - this.transform.zoomToRect(rect, options) - } + this.transform.zoomToRect(rect, options) return this } @@ -712,12 +598,7 @@ export class Graph extends Basecoat { options: Transform.GetContentAreaOptions & Transform.ScaleContentToFitOptions = {}, ) { - const scroller = this.scroller.widget - if (scroller) { - scroller.zoomToFit(options) - } else { - this.transform.zoomToFit(options) - } + this.transform.zoomToFit(options) return this } @@ -751,16 +632,6 @@ export class Graph extends Basecoat { return this.translate(tx, ty) } - /** - * **Deprecation Notice:** `getArea` is deprecated and will be moved in next - * major release. Use `getGraphArea()` instead. - * - * @deprecated - */ - getArea() { - return this.transform.getGraphArea() - } - getGraphArea() { return this.transform.getGraphArea() } @@ -797,8 +668,8 @@ export class Graph extends Basecoat { /** * Position the center of graph to the center of the viewport. */ - center(optons?: ScrollerWidget.CenterOptions) { - return this.centerPoint(optons) + center() { + return this.centerPoint() } /** @@ -807,50 +678,21 @@ export class Graph extends Basecoat { * only center along the specified dimension and keep the other coordinate * unchanged. */ - centerPoint( - x: number, - y: null | number, - options?: ScrollerWidget.CenterOptions, - ): this - centerPoint( - x: null | number, - y: number, - options?: ScrollerWidget.CenterOptions, - ): this - centerPoint(optons?: ScrollerWidget.CenterOptions): this - centerPoint( - x?: number | null | ScrollerWidget.CenterOptions, - y?: number | null, - options?: ScrollerWidget.CenterOptions, - ) { - const scroller = this.scroller.widget - if (scroller) { - scroller.centerPoint(x as number, y as number, options) - } else { - this.transform.centerPoint(x as number, y as number) - } - + centerPoint(x: number, y: null | number): this + centerPoint(x: null | number, y: number): this + centerPoint(): this + centerPoint(x?: number | null, y?: number | null) { + this.transform.centerPoint(x as number, y as number) return this } - centerContent(options?: ScrollerWidget.PositionContentOptions) { - const scroller = this.scroller.widget - if (scroller) { - scroller.centerContent(options) - } else { - this.transform.centerContent(options) - } + centerContent(options?: Transform.PositionContentOptions) { + this.transform.centerContent(options) return this } - centerCell(cell: Cell, options?: ScrollerWidget.CenterOptions) { - const scroller = this.scroller.widget - if (scroller) { - scroller.centerCell(cell, options) - } else { - this.transform.centerCell(cell) - } - + centerCell(cell: Cell) { + this.transform.centerCell(cell) return this } @@ -858,59 +700,26 @@ export class Graph extends Basecoat { point: Point.PointLike, x: number | string, y: number | string, - options: ScrollerWidget.CenterOptions = {}, ) { - const scroller = this.scroller.widget - if (scroller) { - scroller.positionPoint(point, x, y, options) - } else { - this.transform.positionPoint(point, x, y) - } - + this.transform.positionPoint(point, x, y) return this } - positionRect( - rect: Rectangle.RectangleLike, - direction: ScrollerWidget.Direction, - options?: ScrollerWidget.CenterOptions, - ) { - const scroller = this.scroller.widget - if (scroller) { - scroller.positionRect(rect, direction, options) - } else { - this.transform.positionRect(rect, direction) - } - + positionRect(rect: Rectangle.RectangleLike, direction: Transform.Direction) { + this.transform.positionRect(rect, direction) return this } - positionCell( - cell: Cell, - direction: ScrollerWidget.Direction, - options?: ScrollerWidget.CenterOptions, - ) { - const scroller = this.scroller.widget - if (scroller) { - scroller.positionCell(cell, direction, options) - } else { - this.transform.positionCell(cell, direction) - } - + positionCell(cell: Cell, direction: Transform.Direction) { + this.transform.positionCell(cell, direction) return this } positionContent( - pos: ScrollerWidget.Direction, - options?: ScrollerWidget.PositionContentOptions, + pos: Transform.Direction, + options?: Transform.PositionContentOptions, ) { - const scroller = this.scroller.widget - if (scroller) { - scroller.positionContent(pos, options) - } else { - this.transform.positionContent(pos, options) - } - + this.transform.positionContent(pos, options) return this } @@ -918,24 +727,6 @@ export class Graph extends Basecoat { // #region coord - getClientMatrix() { - return this.coord.getClientMatrix() - } - - /** - * Returns coordinates of the graph viewport, relative to the window. - */ - getClientOffset() { - return this.coord.getClientOffset() - } - - /** - * Returns coordinates of the graph viewport, relative to the document. - */ - getPageOffset() { - return this.coord.getPageOffset() - } - snapToGrid(p: Point.PointLike): Point snapToGrid(x: number, y: number): Point snapToGrid(x: number | Point.PointLike, y?: number) { @@ -1195,198 +986,27 @@ export class Graph extends Basecoat { return this } - drawBackground(options?: Background.Options, onGraph?: boolean) { - const scroller = this.scroller.widget - if (scroller != null && (this.options.background == null || !onGraph)) { - scroller.backgroundManager.draw(options) - } else { - this.background.draw(options) - } + drawBackground(options?: Background.Options) { + this.background.draw(options) return this } - clearBackground(onGraph?: boolean) { - const scroller = this.scroller.widget - if (scroller != null && (this.options.background == null || !onGraph)) { - scroller.backgroundManager.clear() - } else { - this.background.clear() - } + clearBackground() { + this.background.clear() return this } // #endregion - // #region clipboard + // #region virtual-render - isClipboardEnabled() { - return !this.clipboard.disabled - } - - enableClipboard() { - this.clipboard.enable() - return this - } - - disableClipboard() { - this.clipboard.disable() - return this - } - - toggleClipboard(enabled?: boolean) { - if (enabled != null) { - if (enabled !== this.isClipboardEnabled()) { - if (enabled) { - this.enableClipboard() - } else { - this.disableClipboard() - } - } - } else if (this.isClipboardEnabled()) { - this.disableClipboard() - } else { - this.enableClipboard() - } - - return this - } - - isClipboardEmpty() { - return this.clipboard.isEmpty() - } - - getCellsInClipboard() { - return this.clipboard.cells - } - - cleanClipboard() { - this.clipboard.clean() - return this - } - - copy(cells: Cell[], options: Clipboard.CopyOptions = {}) { - this.clipboard.copy(cells, options) - return this - } - - cut(cells: Cell[], options: Clipboard.CopyOptions = {}) { - this.clipboard.cut(cells, options) - return this - } - - paste(options: Clipboard.PasteOptions = {}, graph: Graph = this) { - return this.clipboard.paste(options, graph) - } - - // #endregion - - // #region redo/undo - - isHistoryEnabled() { - return !this.history.disabled - } - - enableHistory() { - this.history.enable() - return this - } - - disableHistory() { - this.history.disable() - return this - } - - toggleHistory(enabled?: boolean) { - if (enabled != null) { - if (enabled !== this.isHistoryEnabled()) { - if (enabled) { - this.enableHistory() - } else { - this.disableHistory() - } - } - } else if (this.isHistoryEnabled()) { - this.disableHistory() - } else { - this.enableHistory() - } - - return this - } - - undo(options: KeyValue = {}) { - this.history.undo(options) - return this - } - - undoAndCancel(options: KeyValue = {}) { - this.history.cancel(options) - return this - } - - redo(options: KeyValue = {}) { - this.history.redo(options) + enableVirtualRender() { + this.virtualRender.enableVirtualRender() return this } - canUndo() { - return this.history.canUndo() - } - - canRedo() { - return this.history.canRedo() - } - - cleanHistory(options: KeyValue = {}) { - this.history.clean(options) - } - - // #endregion - - // #region keyboard - - isKeyboardEnabled() { - return !this.keyboard.disabled - } - - enableKeyboard() { - this.keyboard.enable() - return this - } - - disableKeyboard() { - this.keyboard.disable() - return this - } - - toggleKeyboard(enabled?: boolean) { - if (enabled != null) { - if (enabled !== this.isKeyboardEnabled()) { - if (enabled) { - this.enableKeyboard() - } else { - this.disableKeyboard() - } - } - } else if (this.isKeyboardEnabled()) { - this.disableKeyboard() - } else { - this.enableKeyboard() - } - return this - } - - bindKey( - keys: string | string[], - callback: Shortcut.Handler, - action?: Shortcut.Action, - ) { - this.keyboard.on(keys, callback, action) - return this - } - - unbindKey(keys: string | string[], action?: Shortcut.Action) { - this.keyboard.off(keys, action) + disableVirtualRender() { + this.virtualRender.disableVirtualRender() return this } @@ -1428,32 +1048,16 @@ export class Graph extends Basecoat { // #region panning isPannable() { - const scroller = this.scroller.widget - if (scroller) { - return this.scroller.pannable - } return this.panning.pannable } enablePanning() { - const scroller = this.scroller.widget - if (scroller) { - this.scroller.enablePanning() - } else { - this.panning.enablePanning() - } - + this.panning.enablePanning() return this } disablePanning() { - const scroller = this.scroller.widget - if (scroller) { - this.scroller.disablePanning() - } else { - this.panning.disablePanning() - } - + this.panning.disablePanning() return this } @@ -1477,494 +1081,26 @@ export class Graph extends Basecoat { // #endregion - // #region scroller - - @Decorator.checkScroller() - lockScroller() { - this.scroller.widget?.lock() - } - - @Decorator.checkScroller() - unlockScroller() { - this.scroller.widget?.unlock() - } - - @Decorator.checkScroller() - updateScroller() { - this.scroller.widget?.update() - } - - @Decorator.checkScroller() - getScrollbarPosition() { - const scroller = this.scroller.widget! - return scroller.scrollbarPosition() - } - - @Decorator.checkScroller() - setScrollbarPosition( - left?: number, - top?: number, - options?: ScrollerWidget.ScrollOptions, - ) { - const scroller = this.scroller.widget! - scroller.scrollbarPosition(left, top, options) - return this - } - - /** - * Try to scroll to ensure that the position (x,y) on the graph (in local - * coordinates) is at the center of the viewport. If only one of the - * coordinates is specified, only scroll in the specified dimension and - * keep the other coordinate unchanged. - */ - @Decorator.checkScroller() - scrollToPoint( - x: number | null | undefined, - y: number | null | undefined, - options?: ScrollerWidget.ScrollOptions, - ) { - const scroller = this.scroller.widget! - scroller.scrollToPoint(x, y, options) - return this - } - - /** - * Try to scroll to ensure that the center of graph content is at the - * center of the viewport. - */ - @Decorator.checkScroller() - scrollToContent(options?: ScrollerWidget.ScrollOptions) { - const scroller = this.scroller.widget! - scroller.scrollToContent(options) - return this - } - - /** - * Try to scroll to ensure that the center of cell is at the center of - * the viewport. - */ - @Decorator.checkScroller() - scrollToCell(cell: Cell, options?: ScrollerWidget.ScrollOptions) { - const scroller = this.scroller.widget! - scroller.scrollToCell(cell, options) - return this - } - - transitionToPoint( - p: Point.PointLike, - options?: ScrollerWidget.TransitionOptions, - ): this - transitionToPoint( - x: number, - y: number, - options?: ScrollerWidget.TransitionOptions, - ): this - @Decorator.checkScroller() - transitionToPoint( - x: number | Point.PointLike, - y?: number | ScrollerWidget.TransitionOptions, - options?: ScrollerWidget.TransitionOptions, - ) { - const scroller = this.scroller.widget! - scroller.transitionToPoint(x as number, y as number, options) - return this - } - - @Decorator.checkScroller() - transitionToRect( - rect: Rectangle.RectangleLike, - options: ScrollerWidget.TransitionToRectOptions = {}, - ) { - const scroller = this.scroller.widget! - scroller.transitionToRect(rect, options) - return this - } - // #endregion - - // #region selection - - isSelectionEnabled() { - return !this.selection.disabled - } - - enableSelection() { - this.selection.enable() - return this - } - - disableSelection() { - this.selection.disable() - return this - } - - toggleSelection(enabled?: boolean) { - if (enabled != null) { - if (enabled !== this.isSelectionEnabled()) { - if (enabled) { - this.enableSelection() - } else { - this.disableSelection() - } - } - } else if (this.isSelectionEnabled()) { - this.disableSelection() - } else { - this.enableSelection() - } - - return this - } - - isMultipleSelection() { - return this.selection.isMultiple() - } - - enableMultipleSelection() { - this.selection.enableMultiple() - return this - } - - disableMultipleSelection() { - this.selection.disableMultiple() - return this - } - - toggleMultipleSelection(multiple?: boolean) { - if (multiple != null) { - if (multiple !== this.isMultipleSelection()) { - if (multiple) { - this.enableMultipleSelection() - } else { - this.disableMultipleSelection() - } - } - } else if (this.isMultipleSelection()) { - this.disableMultipleSelection() - } else { - this.enableMultipleSelection() - } - - return this - } - - isSelectionMovable() { - return this.selection.widget.options.movable !== false - } - - enableSelectionMovable() { - this.selection.widget.options.movable = true - return this - } - - disableSelectionMovable() { - this.selection.widget.options.movable = false - return this - } - - toggleSelectionMovable(movable?: boolean) { - if (movable != null) { - if (movable !== this.isSelectionMovable()) { - if (movable) { - this.enableSelectionMovable() - } else { - this.disableSelectionMovable() - } - } - } else if (this.isSelectionMovable()) { - this.disableSelectionMovable() - } else { - this.enableSelectionMovable() - } - - return this - } - - isRubberbandEnabled() { - return !this.selection.rubberbandDisabled - } - - enableRubberband() { - this.selection.enableRubberband() - return this - } - - disableRubberband() { - this.selection.disableRubberband() - return this - } - - toggleRubberband(enabled?: boolean) { - if (enabled != null) { - if (enabled !== this.isRubberbandEnabled()) { - if (enabled) { - this.enableRubberband() - } else { - this.disableRubberband() - } - } - } else if (this.isRubberbandEnabled()) { - this.disableRubberband() - } else { - this.enableRubberband() - } - - return this - } - - isStrictRubberband() { - return this.selection.widget.options.strict === true - } - - enableStrictRubberband() { - this.selection.widget.options.strict = true - return this - } - - disableStrictRubberband() { - this.selection.widget.options.strict = false - return this - } - - toggleStrictRubberband(strict?: boolean) { - if (strict != null) { - if (strict !== this.isStrictRubberband()) { - if (strict) { - this.enableStrictRubberband() - } else { - this.disableStrictRubberband() - } - } - } else if (this.isStrictRubberband()) { - this.disableStrictRubberband() - } else { - this.enableStrictRubberband() - } - - return this - } - - setRubberbandModifiers(modifiers?: string | ModifierKey[] | null) { - this.selection.setModifiers(modifiers) - } - - setSelectionFilter(filter?: Selection.Filter) { - this.selection.setFilter(filter) - return this - } - - setSelectionDisplayContent(content?: Selection.Content) { - this.selection.setContent(content) - return this - } - - isSelectionEmpty() { - return this.selection.isEmpty() - } - - cleanSelection(options?: Selection.SetOptions) { - this.selection.clean(options) - return this - } - - resetSelection( - cells?: Cell | string | (Cell | string)[], - options?: Selection.SetOptions, - ) { - this.selection.reset(cells, options) - return this - } - - getSelectedCells() { - return this.selection.cells - } - - getSelectedCellCount() { - return this.selection.length - } - - isSelected(cell: Cell | string) { - return this.selection.isSelected(cell) - } - - select( - cells: Cell | string | (Cell | string)[], - options?: Selection.AddOptions, - ) { - this.selection.select(cells, options) - return this - } - - unselect( - cells: Cell | string | (Cell | string)[], - options?: Selection.RemoveOptions, - ) { - this.selection.unselect(cells, options) - return this - } - - // #endregion - - // #region snapline - - isSnaplineEnabled() { - return !this.snapline.widget.disabled - } - - enableSnapline() { - this.snapline.widget.enable() - return this - } - - disableSnapline() { - this.snapline.widget.disable() - return this - } - - toggleSnapline(enabled?: boolean) { - if (enabled != null) { - if (enabled !== this.isSnaplineEnabled()) { - if (enabled) { - this.enableSnapline() - } else { - this.disableSnapline() - } - } - } else { - if (this.isSnaplineEnabled()) { - this.disableSnapline() - } else { - this.enableSnapline() - } - return this - } - } - - hideSnapline() { - this.snapline.widget.hide() - return this - } - - setSnaplineFilter(filter?: Snapline.Filter) { - this.snapline.widget.setFilter(filter) - return this - } - - isSnaplineOnResizingEnabled() { - return this.snapline.widget.options.resizing === true - } - - enableSnaplineOnResizing() { - this.snapline.widget.options.resizing = true - return this - } - - disableSnaplineOnResizing() { - this.snapline.widget.options.resizing = false - return this - } + // #region plugin - toggleSnaplineOnResizing(enableOnResizing?: boolean) { - if (enableOnResizing != null) { - if (enableOnResizing !== this.isSnaplineOnResizingEnabled()) { - if (enableOnResizing) { - this.enableSnaplineOnResizing() - } else { - this.disableSnaplineOnResizing() - } - } - } else if (this.isSnaplineOnResizingEnabled()) { - this.disableSnaplineOnResizing() - } else { - this.enableSnaplineOnResizing() + use(plugin: Graph.Plugin, ...options: any[]) { + if (!this.installedPlugins.has(plugin)) { + this.installedPlugins.add(plugin) + plugin.init(this, ...options) } return this } - isSharpSnapline() { - return this.snapline.widget.options.sharp === true - } - - enableSharpSnapline() { - this.snapline.widget.options.sharp = true - return this - } - - disableSharpSnapline() { - this.snapline.widget.options.sharp = false - return this - } + getPlugin(pluginName: string) { + let result: Graph.Plugin | undefined - toggleSharpSnapline(sharp?: boolean) { - if (sharp != null) { - if (sharp !== this.isSharpSnapline()) { - if (sharp) { - this.enableSharpSnapline() - } else { - this.disableSharpSnapline() - } + this.installedPlugins.forEach((plugin) => { + if (plugin.name === pluginName) { + result = plugin } - } else if (this.isSharpSnapline()) { - this.disableSharpSnapline() - } else { - this.enableSharpSnapline() - } - return this - } + }) - getSnaplineTolerance() { - return this.snapline.widget.options.tolerance - } - - setSnaplineTolerance(tolerance: number) { - this.snapline.widget.options.tolerance = tolerance - return this - } - - // #endregion - - // #region tools - - removeTools() { - this.emit('tools:remove') - return this - } - - hideTools() { - this.emit('tools:hide') - return this - } - - showTools() { - this.emit('tools:show') - return this - } - - // #endregion - - // #region format - - toSVG(callback: Format.ToSVGCallback, options: Format.ToSVGOptions = {}) { - this.format.toSVG(callback, options) - } - - toDataURL(callback: Format.ToSVGCallback, options: Format.ToDataURLOptions) { - this.format.toDataURL(callback, options) - } - - toPNG(callback: Format.ToSVGCallback, options: Format.ToImageOptions = {}) { - this.format.toPNG(callback, options) - } - - toJPEG(callback: Format.ToSVGCallback, options: Format.ToImageOptions = {}) { - this.format.toJPEG(callback, options) - } - - // #endregion - - // #region print - - printPreview(options?: Partial) { - this.print.show(options) + return result } // #endregion @@ -1977,28 +1113,20 @@ export class Graph extends Basecoat { this.off() this.css.dispose() - this.hook.dispose() this.defs.dispose() this.grid.dispose() this.coord.dispose() this.transform.dispose() - this.knob.dispose() this.highlight.dispose() this.background.dispose() - this.clipboard.dispose() - this.snapline.dispose() - this.selection.dispose() - this.history.dispose() - this.keyboard.dispose() this.mousewheel.dispose() - this.print.dispose() - this.format.dispose() - this.minimap.dispose() this.panning.dispose() - this.scroller.dispose() this.view.dispose() this.renderer.dispose() - this.size.dispose() + + this.installedPlugins.forEach((plugin) => { + plugin.dispose() + }) } // #endregion @@ -2007,25 +1135,15 @@ export class Graph extends Basecoat { export namespace Graph { /* eslint-disable @typescript-eslint/no-unused-vars */ export import View = GraphView - export import Hook = HookManager export import Renderer = ViewRenderer - export import Keyboard = Shortcut export import MouseWheel = Wheel export import BaseManager = Base export import DefsManager = Defs export import GridManager = Grid export import CoordManager = Coord - export import PrintManager = Print - export import FormatManager = Format - export import MiniMapManager = MiniMap - export import HistoryManager = History - export import SnaplineManager = Snapline - export import ScrollerManager = Scroller - export import ClipboardManager = Clipboard export import TransformManager = Transform export import HighlightManager = Highlight export import BackgroundManager = Background - export import SelectionManager = Selection } export namespace Graph { @@ -2045,14 +1163,8 @@ export namespace Graph { } const tag = instance[Symbol.toStringTag] - const graph = instance as Graph - if ( - (tag == null || tag === toStringTag) && - graph.hook != null && - graph.view != null && - graph.model != null - ) { + if (tag == null || tag === toStringTag) { return true } @@ -2107,9 +1219,6 @@ export namespace Graph { export const registerEdgeAnchor = Registry.EdgeAnchor.registry.register export const registerConnectionPoint = Registry.ConnectionPoint.registry.register - export const registerConnectionStrategy = - Registry.ConnectionStrategy.registry.register - export const registerHTMLComponent = HTML.componentRegistry.register } export namespace Graph { @@ -2133,7 +1242,12 @@ export namespace Graph { export const unregisterEdgeAnchor = Registry.EdgeAnchor.registry.unregister export const unregisterConnectionPoint = Registry.ConnectionPoint.registry.unregister - export const unregisterConnectionStrategy = - Registry.ConnectionStrategy.registry.unregister - export const unregisterHTMLComponent = HTML.componentRegistry.unregister +} + +export namespace Graph { + export type Plugin = { + name: string + init: (graph: Graph, ...options: any[]) => any + dispose: () => void + } } diff --git a/packages/x6/src/graph/grid.ts b/packages/x6/src/graph/grid.ts index 359b29284bc..40c935e9cbd 100644 --- a/packages/x6/src/graph/grid.ts +++ b/packages/x6/src/graph/grid.ts @@ -1,5 +1,5 @@ +import { Dom, Vector } from '@antv/x6-common' import * as Registry from '../registry' -import { Dom, Vector } from '../util' import { Base } from './base' export class GridManager extends Base { diff --git a/packages/x6/src/graph/highlight.ts b/packages/x6/src/graph/highlight.ts index 68b77abcea5..4a78631e294 100644 --- a/packages/x6/src/graph/highlight.ts +++ b/packages/x6/src/graph/highlight.ts @@ -1,5 +1,4 @@ -import { Dom } from '../util' -import { KeyValue } from '../types' +import { Dom, KeyValue } from '@antv/x6-common' import { CellView } from '../view' import { Highlighter } from '../registry' import { EventArgs } from './events' diff --git a/packages/x6/src/graph/history.ts b/packages/x6/src/graph/history.ts deleted file mode 100644 index b17d291684e..00000000000 --- a/packages/x6/src/graph/history.ts +++ /dev/null @@ -1,777 +0,0 @@ -import { KeyValue } from '../types' -import { ObjectExt, FunctionExt } from '../util' -import { Basecoat, IDisablable } from '../common' -import { Cell } from '../model/cell' -import { Model } from '../model/model' -import { Graph } from './graph' - -export class HistoryManager - extends Basecoat - implements IDisablable -{ - public readonly model: Model - public readonly graph: Graph - public readonly options: HistoryManager.CommonOptions - public readonly validator: HistoryManager.Validator - protected redoStack: HistoryManager.Commands[] - protected undoStack: HistoryManager.Commands[] - protected batchCommands: HistoryManager.Command[] | null = null - protected batchLevel = 0 - protected lastBatchIndex = -1 - protected freezed = false - - protected readonly handlers: (( - event: T, - args: Model.EventArgs[T], - ) => any)[] = [] - - constructor(options: HistoryManager.Options) { - super() - this.graph = options.graph - this.model = options.graph.model - this.options = Util.getOptions(options) - this.validator = new HistoryManager.Validator({ - history: this, - cancelInvalid: this.options.cancelInvalid, - }) - this.clean() - this.startListening() - } - - get disabled() { - return this.options.enabled !== true - } - - enable() { - if (this.disabled) { - this.options.enabled = true - } - } - - disable() { - if (!this.disabled) { - this.options.enabled = false - } - } - - undo(options: KeyValue = {}) { - if (!this.disabled) { - const cmd = this.undoStack.pop() - if (cmd) { - this.revertCommand(cmd, options) - this.redoStack.push(cmd) - this.notify('undo', cmd, options) - } - } - return this - } - - redo(options: KeyValue = {}) { - if (!this.disabled) { - const cmd = this.redoStack.pop() - if (cmd) { - this.applyCommand(cmd, options) - this.undoStack.push(cmd) - this.notify('redo', cmd, options) - } - } - return this - } - - /** - * Same as `undo()` but does not store the undo-ed command to the - * `redoStack`. Canceled command therefore cannot be redo-ed. - */ - cancel(options: KeyValue = {}) { - if (!this.disabled) { - const cmd = this.undoStack.pop() - if (cmd) { - this.revertCommand(cmd, options) - this.redoStack = [] - this.notify('cancel', cmd, options) - } - } - return this - } - - clean(options: KeyValue = {}) { - this.undoStack = [] - this.redoStack = [] - this.notify('clean', null, options) - return this - } - - canUndo() { - return !this.disabled && this.undoStack.length > 0 - } - - canRedo() { - return !this.disabled && this.redoStack.length > 0 - } - - validate( - events: string | string[], - ...callbacks: HistoryManager.Validator.Callback[] - ) { - this.validator.validate(events, ...callbacks) - return this - } - - @Basecoat.dispose() - dispose() { - this.validator.dispose() - this.clean() - this.stopListening() - } - - protected startListening() { - this.model.on('batch:start', this.initBatchCommand, this) - this.model.on('batch:stop', this.storeBatchCommand, this) - if (this.options.eventNames) { - this.options.eventNames.forEach((name, index) => { - this.handlers[index] = this.addCommand.bind(this, name) - this.model.on(name, this.handlers[index]) - }) - } - - this.validator.on('invalid', (args) => this.trigger('invalid', args)) - } - - protected stopListening() { - this.model.off('batch:start', this.initBatchCommand, this) - this.model.off('batch:stop', this.storeBatchCommand, this) - if (this.options.eventNames) { - this.options.eventNames.forEach((name, index) => { - this.model.off(name, this.handlers[index]) - }) - this.handlers.length = 0 - } - this.validator.off('invalid') - } - - protected createCommand(options?: { - batch: boolean - }): HistoryManager.Command { - return { - batch: options ? options.batch : false, - data: {} as HistoryManager.CreationData, - } - } - - protected revertCommand(cmd: HistoryManager.Commands, options?: KeyValue) { - this.freezed = true - - const cmds = Array.isArray(cmd) ? Util.sortBatchCommands(cmd) : [cmd] - for (let i = cmds.length - 1; i >= 0; i -= 1) { - const cmd = cmds[i] - const localOptions = { - ...options, - ...ObjectExt.pick(cmd.options, this.options.revertOptionsList || []), - } - this.executeCommand(cmd, true, localOptions) - } - - this.freezed = false - } - - protected applyCommand(cmd: HistoryManager.Commands, options?: KeyValue) { - this.freezed = true - - const cmds = Array.isArray(cmd) ? Util.sortBatchCommands(cmd) : [cmd] - for (let i = 0; i < cmds.length; i += 1) { - const cmd = cmds[i] - const localOptions = { - ...options, - ...ObjectExt.pick(cmd.options, this.options.applyOptionsList || []), - } - this.executeCommand(cmd, false, localOptions) - } - - this.freezed = false - } - - protected executeCommand( - cmd: HistoryManager.Command, - revert: boolean, - options: KeyValue, - ) { - const model = this.model - // const cell = cmd.modelChange ? model : model.getCell(cmd.data.id!) - const cell = model.getCell(cmd.data.id!) - const event = cmd.event - - if ( - (Util.isAddEvent(event) && revert) || - (Util.isRemoveEvent(event) && !revert) - ) { - cell.remove(options) - } else if ( - (Util.isAddEvent(event) && !revert) || - (Util.isRemoveEvent(event) && revert) - ) { - const data = cmd.data as HistoryManager.CreationData - if (data.node) { - model.addNode(data.props, options) - } else if (data.edge) { - model.addEdge(data.props, options) - } - } else if (Util.isChangeEvent(event)) { - const data = cmd.data as HistoryManager.ChangingData - const key = data.key - if (key) { - const value = revert ? data.prev[key] : data.next[key] - cell.prop(key, value, options) - } - } else { - const executeCommand = this.options.executeCommand - if (executeCommand) { - FunctionExt.call(executeCommand, this, cmd, revert, options) - } - } - } - - protected addCommand( - event: T, - args: Model.EventArgs[T], - ) { - if (this.freezed || this.disabled) { - return - } - - const eventArgs = args as Model.EventArgs['cell:change:*'] - const options = eventArgs.options || {} - if (options.dryrun) { - return - } - - if ( - (Util.isAddEvent(event) && this.options.ignoreAdd) || - (Util.isRemoveEvent(event) && this.options.ignoreRemove) || - (Util.isChangeEvent(event) && this.options.ignoreChange) - ) { - return - } - - // before - // ------ - const before = this.options.beforeAddCommand - if ( - before != null && - FunctionExt.call(before, this, event, args) === false - ) { - return - } - - if (event === 'cell:change:*') { - // eslint-disable-next-line - event = `cell:change:${eventArgs.key}` as T - } - - const cell = eventArgs.cell - const isModelChange = Model.isModel(cell) - let cmd: HistoryManager.Command - - if (this.batchCommands) { - // In most cases we are working with same object, doing - // same action etc. translate an object piece by piece. - cmd = this.batchCommands[Math.max(this.lastBatchIndex, 0)] - - // Check if we are start working with new object or performing different - // action with it. Note, that command is uninitialized when lastCmdIndex - // equals -1. In that case we are done, command we were looking for is - // already set - - const diffId = - (isModelChange && !cmd.modelChange) || cmd.data.id !== cell.id - const diffName = cmd.event !== event - - if (this.lastBatchIndex >= 0 && (diffId || diffName)) { - // Trying to find command first, which was performing same - // action with the object as we are doing now with cell. - const index = this.batchCommands.findIndex( - (cmd) => - ((isModelChange && cmd.modelChange) || cmd.data.id === cell.id) && - cmd.event === event, - ) - - if (index < 0 || Util.isAddEvent(event) || Util.isRemoveEvent(event)) { - cmd = this.createCommand({ batch: true }) - } else { - cmd = this.batchCommands[index] - this.batchCommands.splice(index, 1) - } - this.batchCommands.push(cmd) - this.lastBatchIndex = this.batchCommands.length - 1 - } - } else { - cmd = this.createCommand({ batch: false }) - } - - // add & remove - // ------------ - if (Util.isAddEvent(event) || Util.isRemoveEvent(event)) { - const data = cmd.data as HistoryManager.CreationData - cmd.event = event - cmd.options = options - data.id = cell.id - data.props = ObjectExt.cloneDeep(cell.toJSON()) - if (cell.isEdge()) { - data.edge = true - } else if (cell.isNode()) { - data.node = true - } - - return this.push(cmd, options) - } - - // change:* - // -------- - if (Util.isChangeEvent(event)) { - const key = (args as Model.EventArgs['cell:change:*']).key - const data = cmd.data as HistoryManager.ChangingData - - if (!cmd.batch || !cmd.event) { - // Do this only once. Set previous data and action (also - // serves as a flag so that we don't repeat this branche). - cmd.event = event - cmd.options = options - data.key = key as string - if (data.prev == null) { - data.prev = {} - } - data.prev[key] = ObjectExt.clone(cell.previous(key)) - - if (isModelChange) { - cmd.modelChange = true - } else { - data.id = cell.id - } - } - - if (data.next == null) { - data.next = {} - } - data.next[key] = ObjectExt.clone(cell.prop(key)) - return this.push(cmd, options) - } - - // others - // ------ - const afterAddCommand = this.options.afterAddCommand - if (afterAddCommand) { - FunctionExt.call(afterAddCommand, this, event, args, cmd) - } - this.push(cmd, options) - } - - /** - * Gather multiple changes into a single command. These commands could - * be reverted with single `undo()` call. From the moment the function - * is called every change made on model is not stored into the undoStack. - * Changes are temporarily kept until `storeBatchCommand()` is called. - */ - // eslint-disable-next-line - protected initBatchCommand(options: KeyValue) { - if (this.freezed) { - return - } - if (this.batchCommands) { - this.batchLevel += 1 - } else { - this.batchCommands = [this.createCommand({ batch: true })] - this.batchLevel = 0 - this.lastBatchIndex = -1 - } - } - - /** - * Store changes temporarily kept in the undoStack. You have to call this - * function as many times as `initBatchCommand()` been called. - */ - protected storeBatchCommand(options: KeyValue) { - if (this.freezed) { - return - } - - if (this.batchCommands && this.batchLevel <= 0) { - const cmds = this.filterBatchCommand(this.batchCommands) - if (cmds.length > 0) { - this.redoStack = [] - this.undoStack.push(cmds) - this.notify('add', cmds, options) - } - this.batchCommands = null - this.lastBatchIndex = -1 - this.batchLevel = 0 - } else if (this.batchCommands && this.batchLevel > 0) { - this.batchLevel -= 1 - } - } - - protected filterBatchCommand(batchCommands: HistoryManager.Command[]) { - let cmds = batchCommands.slice() - const result = [] - - while (cmds.length > 0) { - const cmd = cmds.shift()! - const evt = cmd.event - const id = cmd.data.id - - if (evt != null && (id != null || cmd.modelChange)) { - if (Util.isAddEvent(evt)) { - const index = cmds.findIndex( - (c) => Util.isRemoveEvent(c.event) && c.data.id === id, - ) - - if (index >= 0) { - cmds = cmds.filter((c, i) => index < i || c.data.id !== id) - continue - } - } else if (Util.isRemoveEvent(evt)) { - const index = cmds.findIndex( - (c) => Util.isAddEvent(c.event) && c.data.id === id, - ) - if (index >= 0) { - cmds.splice(index, 1) - continue - } - } else if (Util.isChangeEvent(evt)) { - const data = cmd.data as HistoryManager.ChangingData - - if (ObjectExt.isEqual(data.prev, data.next)) { - continue - } - } else { - // pass - } - - result.push(cmd) - } - } - - return result - } - - protected notify( - event: keyof HistoryManager.EventArgs, - cmd: HistoryManager.Commands | null, - options: KeyValue, - ) { - const cmds = cmd == null ? null : Array.isArray(cmd) ? cmd : [cmd] - this.emit(event, { cmds, options }) - this.emit('change', { cmds, options }) - } - - protected push(cmd: HistoryManager.Command, options: KeyValue) { - this.redoStack = [] - if (cmd.batch) { - this.lastBatchIndex = Math.max(this.lastBatchIndex, 0) - this.emit('batch', { cmd, options }) - } else { - this.undoStack.push(cmd) - this.notify('add', cmd, options) - } - } -} - -export namespace HistoryManager { - export type ModelEvents = keyof Model.EventArgs - - export interface CommonOptions { - enabled?: boolean - ignoreAdd?: boolean - ignoreRemove?: boolean - ignoreChange?: boolean - eventNames?: (keyof Model.EventArgs)[] - /** - * A function evaluated before any command is added. If the function - * returns `false`, the command does not get stored. This way you can - * control which commands do not get registered for undo/redo. - */ - beforeAddCommand?: ( - this: HistoryManager, - event: T, - args: Model.EventArgs[T], - ) => any - afterAddCommand?: ( - this: HistoryManager, - event: T, - args: Model.EventArgs[T], - cmd: Command, - ) => any - executeCommand?: ( - this: HistoryManager, - cmd: Command, - revert: boolean, - options: KeyValue, - ) => any - /** - * An array of options property names that passed in undo actions. - */ - revertOptionsList?: string[] - /** - * An array of options property names that passed in redo actions. - */ - applyOptionsList?: string[] - /** - * Determine whether to cancel an invalid command or not. - */ - cancelInvalid?: boolean - } - - export interface Options extends Partial { - graph: Graph - } - - interface Data { - id?: string - } - - export interface CreationData extends Data { - edge?: boolean - node?: boolean - props: Cell.Properties - } - - export interface ChangingData extends Data { - key: string - prev: KeyValue - next: KeyValue - } - - export interface Command { - batch: boolean - modelChange?: boolean - event?: ModelEvents - data: CreationData | ChangingData - options?: KeyValue - } - - export type Commands = HistoryManager.Command[] | HistoryManager.Command -} - -export namespace HistoryManager { - interface Args { - cmds: Command[] | T - options: KeyValue - } - - export interface EventArgs extends Validator.EventArgs { - /** - * Triggered when a command was undone. - */ - undo: Args - /** - * Triggered when a command were redone. - */ - redo: Args - /** - * Triggered when a command was canceled. - */ - cancel: Args - /** - * Triggered when command(s) were added to the stack. - */ - add: Args - /** - * Triggered when all commands were clean. - */ - clean: Args - /** - * Triggered when any change was made to stacks. - */ - change: Args - /** - * Triggered when a batch command received. - */ - batch: { cmd: Command; options: KeyValue } - } -} - -export namespace HistoryManager { - /** - * Runs a set of callbacks to determine if a command is valid. This is - * useful for checking if a certain action in your application does - * lead to an invalid state of the graph. - */ - export class Validator extends Basecoat { - protected readonly command: HistoryManager - - protected readonly cancelInvalid: boolean - - protected readonly map: { [event: string]: Validator.Callback[][] } - - constructor(options: Validator.Options) { - super() - this.map = {} - this.command = options.history - this.cancelInvalid = options.cancelInvalid !== false - this.command.on('add', this.onCommandAdded, this) - } - - protected onCommandAdded({ cmds }: HistoryManager.EventArgs['add']) { - return Array.isArray(cmds) - ? cmds.every((cmd) => this.isValidCommand(cmd)) - : this.isValidCommand(cmds) - } - - protected isValidCommand(cmd: HistoryManager.Command) { - if (cmd.options && cmd.options.validation === false) { - return true - } - - const callbacks = (cmd.event && this.map[cmd.event]) || [] - - let handoverErr: Error | null = null - - callbacks.forEach((routes) => { - let i = 0 - - const rollup = (err: Error | null) => { - const fn = routes[i] - i += 1 - - try { - if (fn) { - fn(err, cmd, rollup) - } else { - handoverErr = err - return - } - } catch (err) { - rollup(err) - } - } - - rollup(handoverErr) - }) - - if (handoverErr) { - if (this.cancelInvalid) { - this.command.cancel() - } - this.emit('invalid', { err: handoverErr }) - return false - } - - return true - } - - validate(events: string | string[], ...callbacks: Validator.Callback[]) { - const evts = Array.isArray(events) ? events : events.split(/\s+/) - - callbacks.forEach((callback) => { - if (typeof callback !== 'function') { - throw new Error(`${evts.join(' ')} requires callback functions.`) - } - }) - - evts.forEach((event) => { - if (this.map[event] == null) { - this.map[event] = [] - } - this.map[event].push(callbacks) - }) - - return this - } - - @Basecoat.dispose() - dispose() { - this.command.off('add', this.onCommandAdded, this) - } - } - - export namespace Validator { - export interface Options { - history: HistoryManager - /** - * To cancel (= undo + delete from redo stack) a command if is not valid. - */ - cancelInvalid?: boolean - } - - export type Callback = ( - err: Error | null, - cmd: HistoryManager.Command, - next: (err: Error | null) => any, - ) => any - - export interface EventArgs { - invalid: { err: Error } - } - } -} - -namespace Util { - export function isAddEvent(event?: HistoryManager.ModelEvents) { - return event === 'cell:added' - } - - export function isRemoveEvent(event?: HistoryManager.ModelEvents) { - return event === 'cell:removed' - } - - export function isChangeEvent(event?: HistoryManager.ModelEvents) { - return event != null && event.startsWith('cell:change:') - } - - export function getOptions( - options: HistoryManager.Options, - ): HistoryManager.CommonOptions { - const { graph, ...others } = options - const reservedNames: HistoryManager.ModelEvents[] = [ - 'cell:added', - 'cell:removed', - 'cell:change:*', - ] - - const batchEvents: HistoryManager.ModelEvents[] = [ - 'batch:start', - 'batch:stop', - ] - - const eventNames = options.eventNames - ? options.eventNames.filter( - (event) => - !( - Util.isChangeEvent(event) || - reservedNames.includes(event) || - batchEvents.includes(event) - ), - ) - : reservedNames - - return { - ...others, - eventNames, - applyOptionsList: options.applyOptionsList || ['propertyPath'], - revertOptionsList: options.revertOptionsList || ['propertyPath'], - } - } - - export function sortBatchCommands(cmds: HistoryManager.Command[]) { - const results: HistoryManager.Command[] = [] - for (let i = 0, ii = cmds.length; i < ii; i += 1) { - const cmd = cmds[i] - let index: number | null = null - - if (Util.isAddEvent(cmd.event)) { - const id = cmd.data.id - for (let j = 0; j < i; j += 1) { - if (cmds[j].data.id === id) { - index = j - break - } - } - } - - if (index !== null) { - results.splice(index, 0, cmd) - } else { - results.push(cmd) - } - } - return results - } -} diff --git a/packages/x6/src/graph/hook.ts b/packages/x6/src/graph/hook.ts deleted file mode 100644 index 9e91648ac69..00000000000 --- a/packages/x6/src/graph/hook.ts +++ /dev/null @@ -1,869 +0,0 @@ -import { FunctionExt, ObjectExt } from '../util' -import { Cell } from '../model/cell' -import { Node } from '../model/node' -import { Edge } from '../model/edge' -import { Model } from '../model/model' -import { View } from '../view/view' -import { Markup } from '../view/markup' -import { CellView } from '../view/cell' -import { NodeView } from '../view/node' -import { EdgeView } from '../view/edge' -import { Widget } from '../addon/common' -import { Knob } from '../addon/knob' -import { MiniMap } from '../addon/minimap' -import { Snapline } from '../addon/snapline' -import { Scroller } from '../addon/scroller' -import { Selection } from '../addon/selection' -import { Clipboard } from '../addon/clipboard' -import { Transform } from '../addon/transform' -import { HTML } from '../shape/standard/html' -import { Edge as StandardEdge } from '../shape/standard/edge' -import { Base } from './base' -import { Graph } from './graph' -import { Options } from './options' -import { Renderer } from './renderer' -import { GraphView } from './view' -import { DefsManager } from './defs' -import { GridManager } from './grid' -import { CoordManager } from './coord' -import { SnaplineManager } from './snapline' -import { ScrollerManager } from './scroller' -import { ClipboardManager } from './clipboard' -import { HighlightManager } from './highlight' -import { TransformManager } from './transform' -import { SelectionManager } from './selection' -import { BackgroundManager } from './background' -import { HistoryManager } from './history' -import { MiniMapManager } from './minimap' -import { Keyboard } from './keyboard' -import { MouseWheel } from './mousewheel' -import { PrintManager } from './print' -import { FormatManager } from './format' -import { PortManager } from '../model/port' -import { Rectangle } from '../geometry' -import { KnobManager } from './knob' -import { PanningManager } from './panning' -import { SizeManager } from './size' - -namespace Decorator { - export function hook(nullable?: boolean, hookName?: string | null) { - return ( - target: Hook, - methodName: string, - descriptor: PropertyDescriptor, - ) => { - const raw = descriptor.value - const name = hookName || methodName - - descriptor.value = function (this: Hook, ...args: any[]) { - const hook = (this.options as any)[name] - if (hook != null) { - this.getNativeValue = raw.bind(this, ...args) - const ret = FunctionExt.call(hook, this.graph, ...args) - this.getNativeValue = null - if (ret != null || (nullable === true && ret === null)) { - return ret - } - } - - return raw.call(this, ...args) - } - } - } - - export function after(hookName?: string | null) { - return ( - target: Hook, - methodName: string, - descriptor: PropertyDescriptor, - ) => { - const raw = descriptor.value - const name = hookName || methodName - - descriptor.value = function (this: Hook, ...args: any[]) { - let ret = raw.call(this, ...args) - const hook = (this.options as any)[name] - if (hook != null) { - ret = FunctionExt.call(hook, this.graph, ...args) && ret - } - return ret - } - } - } -} - -export class Hook extends Base implements Hook.IHook { - /** - * Get the native value of hooked method. - */ - public getNativeValue: (() => T | null) | null - - @Decorator.hook() - createModel() { - if (this.options.model) { - return this.options.model - } - const model = new Model() - model.graph = this.graph - return model - } - - @Decorator.hook() - createView() { - return new GraphView(this.graph) - } - - @Decorator.hook() - createRenderer() { - return new Renderer(this.graph) - } - - @Decorator.hook() - createDefsManager() { - return new DefsManager(this.graph) - } - - @Decorator.hook() - createGridManager() { - return new GridManager(this.graph) - } - - @Decorator.hook() - createCoordManager() { - return new CoordManager(this.graph) - } - - @Decorator.hook() - createKnobManager() { - return new KnobManager(this.graph) - } - - @Decorator.hook() - createTransform(node: Node, widgetOptions?: Widget.Options) { - const options = this.getTransformOptions(node) - if (options.resizable || options.rotatable) { - return new Transform({ - node, - graph: this.graph, - ...options, - ...widgetOptions, - }) - } - if (options.clearAll) { - Transform.removeInstances(this.graph) - } - - return null - } - - @Decorator.hook() - createKnob(node: Node, widgetOptions?: Widget.Options) { - const options = Options.parseOptionGroup( - this.graph, - node, - this.options.knob, - ) - - const localOptions = { - ...options, - ...widgetOptions, - } - - if (localOptions.clearAll) { - Knob.removeInstances(this.graph) - } - - localOptions.clearAll = false - - const knob = node.prop('knob') as Knob.Metadata | Knob.Metadata[] - const widgets: Knob[] = [] - const meta = Array.isArray(knob) ? knob : [knob] - - meta.forEach((knob, index) => { - if (knob) { - if (knob.enabled === false) { - return - } - - if ( - typeof knob.enabled === 'function' && - knob.enabled.call(this.graph, node) === false - ) { - return - } - } else { - return - } - - if (options.enabled) { - widgets.push( - new Knob({ - node, - index, - graph: this.graph, - ...localOptions, - }), - ) - } - }) - - return widgets - } - - protected getTransformOptions(node: Node) { - const resizing = Options.parseOptionGroup( - this.graph, - node, - this.options.resizing, - ) - - const rotating = Options.parseOptionGroup( - this.graph, - node, - this.options.rotating, - ) - - const transforming = Options.parseOptionGroup( - this.graph, - node, - this.options.transforming, - ) - - const options: Transform.Options = { - ...transforming, - - resizable: resizing.enabled, - minWidth: resizing.minWidth, - maxWidth: resizing.maxWidth, - minHeight: resizing.minHeight, - maxHeight: resizing.maxHeight, - orthogonalResizing: resizing.orthogonal, - restrictedResizing: - resizing.restrict != null ? resizing.restrict : resizing.restricted, - autoScrollOnResizing: resizing.autoScroll, - preserveAspectRatio: resizing.preserveAspectRatio, - allowReverse: resizing.allowReverse, - - rotatable: rotating.enabled, - rotateGrid: rotating.grid, - } - - return options - } - - @Decorator.hook() - createTransformManager() { - return new TransformManager(this.graph) - } - - @Decorator.hook() - createHighlightManager() { - return new HighlightManager(this.graph) - } - - @Decorator.hook() - createBackgroundManager() { - return new BackgroundManager(this.graph) - } - - @Decorator.hook() - createClipboard() { - return new Clipboard() - } - - @Decorator.hook() - createClipboardManager() { - return new ClipboardManager(this.graph) - } - - @Decorator.hook() - createSnapline() { - return new Snapline({ graph: this.graph, ...this.options.snapline }) - } - - @Decorator.hook() - createSnaplineManager() { - return new SnaplineManager(this.graph) - } - - @Decorator.hook() - createSelection() { - return new Selection({ graph: this.graph, ...this.options.selecting }) - } - - @Decorator.hook() - createSelectionManager() { - return new SelectionManager(this.graph) - } - - @Decorator.hook() - // eslint-disable-next-line - allowRubberband(e: JQuery.MouseDownEvent) { - return true - } - - @Decorator.hook() - createHistoryManager() { - return new HistoryManager({ graph: this.graph, ...this.options.history }) - } - - @Decorator.hook() - createScroller() { - if (this.options.scroller.enabled) { - return new Scroller({ graph: this.graph, ...this.options.scroller }) - } - return null - } - - @Decorator.hook() - createScrollerManager() { - return new ScrollerManager(this.graph) - } - - @Decorator.hook() - // eslint-disable-next-line - allowPanning(e: JQuery.MouseDownEvent) { - return true - } - - @Decorator.hook() - createMiniMap() { - const { enabled, ...options } = this.options.minimap - if (enabled) { - return new MiniMap({ - graph: this.graph, - ...options, - }) - } - return null - } - - @Decorator.hook() - createMiniMapManager() { - return new MiniMapManager(this.graph) - } - - @Decorator.hook() - createKeyboard() { - return new Keyboard({ graph: this.graph, ...this.options.keyboard }) - } - - @Decorator.hook() - createMouseWheel() { - return new MouseWheel({ graph: this.graph, ...this.options.mousewheel }) - } - - @Decorator.hook() - createPrintManager() { - return new PrintManager(this.graph) - } - - @Decorator.hook() - createFormatManager() { - return new FormatManager(this.graph) - } - - @Decorator.hook() - createPanningManager() { - return new PanningManager(this.graph) - } - - @Decorator.hook() - createSizeManager() { - return new SizeManager(this.graph) - } - - protected allowConnectToBlank(edge: Edge) { - const options = this.options.connecting - const allowBlank = - options.allowBlank != null ? options.allowBlank : options.dangling - - if (typeof allowBlank !== 'function') { - return !!allowBlank - } - - const edgeView = this.graph.findViewByCell(edge) as EdgeView - const sourceCell = edge.getSourceCell() - const targetCell = edge.getTargetCell() - const sourceView = this.graph.findViewByCell(sourceCell) - const targetView = this.graph.findViewByCell(targetCell) - return FunctionExt.call(allowBlank, this.graph, { - edge, - edgeView, - sourceCell, - targetCell, - sourceView, - targetView, - sourcePort: edge.getSourcePortId(), - targetPort: edge.getTargetPortId(), - sourceMagnet: edgeView.sourceMagnet, - targetMagnet: edgeView.targetMagnet, - }) - } - - validateEdge( - edge: Edge, - type: Edge.TerminalType, - initialTerminal: Edge.TerminalData, - ) { - if (!this.allowConnectToBlank(edge)) { - const sourceId = edge.getSourceCellId() - const targetId = edge.getTargetCellId() - if (!(sourceId && targetId)) { - return false - } - } - - const validate = this.options.connecting.validateEdge - if (validate) { - return FunctionExt.call(validate, this.graph, { - edge, - type, - previous: initialTerminal, - }) - } - - return true - } - - validateMagnet( - cellView: CellView, - magnet: Element, - e: JQuery.MouseDownEvent | JQuery.MouseEnterEvent, - ) { - if (magnet.getAttribute('magnet') !== 'passive') { - const validate = this.options.connecting.validateMagnet - if (validate) { - return FunctionExt.call(validate, this.graph, { - e, - magnet, - view: cellView, - cell: cellView.cell, - }) - } - return true - } - return false - } - - getDefaultEdge(sourceView: CellView, sourceMagnet: Element) { - let edge: Edge | undefined | null | void - - const create = this.options.connecting.createEdge - if (create) { - edge = FunctionExt.call(create, this.graph, { - sourceMagnet, - sourceView, - sourceCell: sourceView.cell, - }) - } - - if (edge == null) { - edge = new StandardEdge() - } - - return edge as Edge - } - - validateConnection( - sourceView: CellView | null | undefined, - sourceMagnet: Element | null | undefined, - targetView: CellView | null | undefined, - targetMagnet: Element | null | undefined, - terminalType: Edge.TerminalType, - edgeView?: EdgeView | null | undefined, - candidateTerminal?: Edge.TerminalCellData | null | undefined, - ) { - const options = this.options.connecting - const allowLoop = options.allowLoop - const allowNode = options.allowNode - const allowEdge = options.allowEdge - const allowPort = options.allowPort - const allowMulti = - options.allowMulti != null ? options.allowMulti : options.multi - const validate = options.validateConnection - - const edge = edgeView ? edgeView.cell : null - const terminalView = terminalType === 'target' ? targetView : sourceView - const terminalMagnet = - terminalType === 'target' ? targetMagnet : sourceMagnet - - let valid = true - const doValidate = ( - validate: (this: Graph, args: Options.ValidateConnectionArgs) => boolean, - ) => { - const sourcePort = - terminalType === 'source' - ? candidateTerminal - ? candidateTerminal.port - : null - : edge - ? edge.getSourcePortId() - : null - const targetPort = - terminalType === 'target' - ? candidateTerminal - ? candidateTerminal.port - : null - : edge - ? edge.getTargetPortId() - : null - return FunctionExt.call(validate, this.graph, { - edge, - edgeView, - sourceView, - targetView, - sourcePort, - targetPort, - sourceMagnet, - targetMagnet, - sourceCell: sourceView ? sourceView.cell : null, - targetCell: targetView ? targetView.cell : null, - type: terminalType, - }) - } - - if (allowLoop != null) { - if (typeof allowLoop === 'boolean') { - if (!allowLoop && sourceView === targetView) { - valid = false - } - } else { - valid = doValidate(allowLoop) - } - } - - if (valid && allowPort != null) { - if (typeof allowPort === 'boolean') { - if (!allowPort && terminalMagnet) { - valid = false - } - } else { - valid = doValidate(allowPort) - } - } - - if (valid && allowEdge != null) { - if (typeof allowEdge === 'boolean') { - if (!allowEdge && EdgeView.isEdgeView(terminalView)) { - valid = false - } - } else { - valid = doValidate(allowEdge) - } - } - - if (valid && allowNode != null) { - if (typeof allowNode === 'boolean') { - if (!allowNode && terminalView != null) { - if (NodeView.isNodeView(terminalView) && terminalMagnet == null) { - valid = false - } - } - } else { - valid = doValidate(allowNode) - } - } - - if (valid && allowMulti != null && edgeView) { - const edge = edgeView.cell - const source = - terminalType === 'source' - ? candidateTerminal - : (edge.getSource() as Edge.TerminalCellData) - const target = - terminalType === 'target' - ? candidateTerminal - : (edge.getTarget() as Edge.TerminalCellData) - const terminalCell = candidateTerminal - ? this.graph.getCellById(candidateTerminal.cell) - : null - - if (source && target && source.cell && target.cell && terminalCell) { - if (typeof allowMulti === 'function') { - valid = doValidate(allowMulti) - } else { - const connectedEdges = this.model.getConnectedEdges(terminalCell, { - outgoing: terminalType === 'source', - incoming: terminalType === 'target', - }) - if (connectedEdges.length) { - if (allowMulti === 'withPort') { - const exist = connectedEdges.some((link) => { - const s = link.getSource() as Edge.TerminalCellData - const t = link.getTarget() as Edge.TerminalCellData - return ( - s && - t && - s.cell === source.cell && - t.cell === target.cell && - s.port != null && - s.port === source.port && - t.port != null && - t.port === target.port - ) - }) - if (exist) { - valid = false - } - } else if (!allowMulti) { - const exist = connectedEdges.some((link) => { - const s = link.getSource() as Edge.TerminalCellData - const t = link.getTarget() as Edge.TerminalCellData - return ( - s && t && s.cell === source.cell && t.cell === target.cell - ) - }) - if (exist) { - valid = false - } - } - } - } - } - } - - if (valid && validate != null) { - valid = doValidate(validate) - } - - return valid - } - - getRestrictArea(view?: NodeView): Rectangle.RectangleLike | null { - const restrict = this.options.translating.restrict - const area = - typeof restrict === 'function' - ? FunctionExt.call(restrict, this.graph, view!) - : restrict - - if (typeof area === 'number') { - return this.graph.transform.getGraphArea().inflate(area) - } - - if (area === true) { - return this.graph.transform.getGraphArea() - } - - return area || null - } - - @Decorator.after() - onViewUpdated( - view: CellView, - flag: number, - options: Renderer.RequestViewUpdateOptions, - ) { - if (flag & Renderer.FLAG_INSERT || options.mounting) { - return - } - this.graph.renderer.requestConnectedEdgesUpdate(view, options) - } - - @Decorator.after() - onViewPostponed( - view: CellView, - flag: number, - options: Renderer.UpdateViewOptions, // eslint-disable-line - ) { - return this.graph.renderer.forcePostponedViewUpdate(view, flag) - } - - @Decorator.hook() - getCellView( - cell: Cell, // eslint-disable-line - ): null | undefined | typeof CellView | (new (...args: any[]) => CellView) { - return null - } - - @Decorator.hook(true) - createCellView(cell: Cell) { - const options = { graph: this.graph } - - const ctor = this.getCellView(cell) - if (ctor) { - return new ctor(cell, options) // eslint-disable-line new-cap - } - - const view = cell.view - if (view != null && typeof view === 'string') { - const def = CellView.registry.get(view) - if (def) { - return new def(cell, options) // eslint-disable-line new-cap - } - - return CellView.registry.onNotFound(view) - } - - if (cell.isNode()) { - return new NodeView(cell, options) - } - - if (cell.isEdge()) { - return new EdgeView(cell, options) - } - - return null - } - - @Decorator.hook() - getHTMLComponent(node: HTML): HTMLElement | string | null | undefined { - let ret = node.getHTML() - - if (typeof ret === 'string') { - ret = HTML.componentRegistry.get(ret) || ret - } - - if (ObjectExt.isPlainObject(ret)) { - ret = (ret as HTML.UpdatableComponent).render - } - - if (typeof ret === 'function') { - return FunctionExt.call(ret, this.graph, node) - } - - return ret as HTML.Elem - } - - @Decorator.hook() - shouldUpdateHTMLComponent(node: HTML): boolean { - let html = node.getHTML() - - if (typeof html === 'string') { - html = HTML.componentRegistry.get(html) || html - } - - if (ObjectExt.isPlainObject(html)) { - const shouldUpdate = (html as HTML.UpdatableComponent) - .shouldComponentUpdate - - if (typeof shouldUpdate === 'function') { - return FunctionExt.call(shouldUpdate, this.graph, node) - } - - return !!shouldUpdate - } - - return false - } - - @Decorator.hook() - onEdgeLabelRendered(args: Hook.OnEdgeLabelRenderedArgs) {} // eslint-disable-line - - @Decorator.hook() - onPortRendered(args: Hook.OnPortRenderedArgs) {} // eslint-disable-line - - @Decorator.hook() - onToolItemCreated(args: Hook.OnToolItemCreatedArgs) {} // eslint-disable-line -} - -export namespace Hook { - type CreateManager = (this: Graph) => T - type CreateManagerWidthNode = (this: Graph, node: Node) => T - type CreateManagerWidthOptions = ( - this: Graph, - options: Options, - ) => T - - export interface OnEdgeLabelRenderedArgs { - edge: Edge - label: Edge.Label - container: Element - selectors: Markup.Selectors - } - - export interface OnPortRenderedArgs { - node: Node - port: PortManager.Port - container: Element - selectors?: Markup.Selectors - labelContainer: Element - labelSelectors?: Markup.Selectors - contentContainer: Element - contentSelectors?: Markup.Selectors - } - - export interface OnToolItemCreatedArgs { - name: string - cell: Cell - view: CellView - tool: View - } - - export interface IHook { - createView: CreateManager - createModel: CreateManager - createRenderer: CreateManager - createDefsManager: CreateManager - createGridManager: CreateManager - createCoordManager: CreateManager - createHighlightManager: CreateManager - createBackgroundManager: CreateManager - createSizeManager: CreateManager - - createTransform: CreateManagerWidthNode - createTransformManager: CreateManager - - createClipboard: CreateManager - createClipboardManager: CreateManager - - createSnapline: CreateManager - createSnaplineManager: CreateManager - - createSelection: CreateManager - createSelectionManager: CreateManager - allowRubberband: (e: JQuery.MouseDownEvent) => boolean - - createHistoryManager: CreateManagerWidthOptions< - HistoryManager, - HistoryManager.Options - > - - createScroller: CreateManager - createScrollerManager: CreateManager - allowPanning: (e: JQuery.MouseDownEvent) => boolean - - createMiniMap: CreateManager - createMiniMapManager: CreateManager - - createKeyboard: CreateManager - createMouseWheel: CreateManager - createPrintManager: CreateManager - createFormatManager: CreateManager - createPanningManager: CreateManager - - createCellView(this: Graph, cell: Cell): CellView | null | undefined - - getCellView( - this: Graph, - cell: Cell, - ): null | undefined | typeof CellView | (new (...args: any[]) => CellView) - - getHTMLComponent( - this: Graph, - node: HTML, - ): HTMLElement | string | null | undefined - - shouldUpdateHTMLComponent(this: Graph, node: HTML): boolean - - onViewUpdated: ( - this: Graph, - view: CellView, - flag: number, - options: Renderer.RequestViewUpdateOptions, - ) => void - - onViewPostponed: ( - this: Graph, - view: CellView, - flag: number, - options: Renderer.UpdateViewOptions, - ) => boolean - - onEdgeLabelRendered(this: Graph, args: OnEdgeLabelRenderedArgs): void - - onPortRendered(this: Graph, args: OnPortRenderedArgs): void - - onToolItemCreated(this: Graph, args: OnToolItemCreatedArgs): void - } -} diff --git a/packages/x6/src/graph/index.ts b/packages/x6/src/graph/index.ts index b8ade72c095..f47fc44af6b 100644 --- a/packages/x6/src/graph/index.ts +++ b/packages/x6/src/graph/index.ts @@ -1 +1,5 @@ export * from './graph' +export * from './view' +export * from './events' +export * from './transform' +export * from './background' diff --git a/packages/x6/src/graph/keyboard.ts b/packages/x6/src/graph/keyboard.ts deleted file mode 100644 index d44f990962e..00000000000 --- a/packages/x6/src/graph/keyboard.ts +++ /dev/null @@ -1,187 +0,0 @@ -import Mousetrap from 'mousetrap' -import { Dom, FunctionExt } from '../util' -import { Disposable, IDisablable } from '../common' -import { Graph } from './graph' -import { EventArgs } from './events' - -export class Keyboard extends Disposable implements IDisablable { - public readonly target: HTMLElement | Document - public readonly container: HTMLElement - public readonly mousetrap: Mousetrap.MousetrapInstance - - protected get graph() { - return this.options.graph - } - - constructor(public readonly options: Keyboard.Options) { - super() - - const scroller = this.graph.scroller.widget - this.container = scroller ? scroller.container : this.graph.container - - if (options.global) { - this.target = document - } else { - this.target = this.container - if (!this.disabled) { - // ensure the container focusable - this.target.setAttribute('tabindex', '-1') - } - - // change to mouseup event,prevent page stalling caused by focus - this.graph.on('cell:mouseup', this.focus, this) - this.graph.on('blank:mouseup', this.focus, this) - } - - this.mousetrap = Keyboard.createMousetrap(this) - } - - get disabled() { - return this.options.enabled !== true - } - - enable() { - if (this.disabled) { - this.options.enabled = true - this.graph.options.keyboard.enabled = true - if (this.target instanceof HTMLElement) { - this.target.setAttribute('tabindex', '-1') - } - } - } - - disable() { - if (!this.disabled) { - this.options.enabled = false - this.graph.options.keyboard.enabled = false - if (this.target instanceof HTMLElement) { - this.target.removeAttribute('tabindex') - } - } - } - - on( - keys: string | string[], - callback: Keyboard.Handler, - action?: Keyboard.Action, - ) { - this.mousetrap.bind(this.getKeys(keys), callback, action) - } - - off(keys: string | string[], action?: Keyboard.Action) { - this.mousetrap.unbind(this.getKeys(keys), action) - } - - private focus(e: EventArgs['node:mouseup']) { - const isInputEvent = this.isInputEvent(e.e) - if (isInputEvent) { - return - } - const target = this.target as HTMLElement - target.focus({ - preventScroll: true, - }) - } - - private getKeys(keys: string | string[]) { - return (Array.isArray(keys) ? keys : [keys]).map((key) => - this.formatkey(key), - ) - } - - protected formatkey(key: string) { - const formated = key - .toLowerCase() - .replace(/\s/g, '') - .replace('delete', 'del') - .replace('cmd', 'command') - - const formatFn = this.options.format - if (formatFn) { - return FunctionExt.call(formatFn, this.graph, formated) - } - - return formated - } - - protected isGraphEvent(e: KeyboardEvent) { - const target = (e.srcElement || e.target) as Element - if (target) { - if (target === this.target || target === document.body) { - return true - } - - return Dom.contains(this.container, target) - } - - return false - } - - isInputEvent(e: KeyboardEvent | JQuery.MouseUpEvent) { - const target = e.target as Element - const tagName = target && target.tagName.toLowerCase() - return tagName === 'input' - } - - isEnabledForEvent(e: KeyboardEvent) { - const allowed = !this.disabled && this.isGraphEvent(e) - const isInputEvent = this.isInputEvent(e) - if (allowed) { - const code = e.keyCode || e.which - if (isInputEvent && (code === 8 || code === 46)) { - return false - } - if (this.options.guard) { - return FunctionExt.call(this.options.guard, this.graph, e) - } - } - return allowed - } - - @Disposable.dispose() - dispose() { - this.mousetrap.reset() - } -} - -export namespace Keyboard { - export type Action = 'keypress' | 'keydown' | 'keyup' - export type Handler = (e: KeyboardEvent) => void - - export interface Options { - graph: Graph - enabled?: boolean - - /** - * Specifies if keyboard event should bind on docuemnt or on container. - * - * Default is `false` that will bind keyboard event on the container. - */ - global?: boolean - - format?: (this: Graph, key: string) => string - guard?: (this: Graph, e: KeyboardEvent) => boolean - } -} - -export namespace Keyboard { - export function createMousetrap(keyboard: Keyboard) { - const mousetrap = new Mousetrap(keyboard.target as Element) - const stopCallback = mousetrap.stopCallback - mousetrap.stopCallback = ( - e: KeyboardEvent, - elem: HTMLElement, - combo: string, - ) => { - if (keyboard.isEnabledForEvent(e)) { - if (stopCallback) { - return stopCallback.call(mousetrap, e, elem, combo) - } - return false - } - return true - } - - return mousetrap - } -} diff --git a/packages/x6/src/graph/knob.ts b/packages/x6/src/graph/knob.ts deleted file mode 100644 index 18f9d5e7ea1..00000000000 --- a/packages/x6/src/graph/knob.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Node } from '../model/node' -import { Knob } from '../addon/knob' -import { Base } from './base' -import { EventArgs } from './events' - -export class KnobManager extends Base { - protected widgets: Map = new Map() - - protected get isSelectionEnabled() { - return this.options.selecting.enabled === true - } - - protected init() { - this.startListening() - } - - protected startListening() { - this.graph.on('node:mouseup', this.onNodeMouseUp, this) - this.graph.on('node:selected', this.onNodeSelected, this) - this.graph.on('node:unselected', this.onNodeUnSelected, this) - } - - protected stopListening() { - this.graph.off('node:mouseup', this.onNodeMouseUp, this) - this.graph.off('node:selected', this.onNodeSelected, this) - this.graph.off('node:unselected', this.onNodeUnSelected, this) - } - - protected onNodeMouseUp({ node }: EventArgs['node:mouseup']) { - if (!this.isSelectionEnabled) { - const widgets = this.graph.hook.createKnob(node, { clearAll: true }) - if (widgets) { - this.widgets.set(node, widgets) - } - } - } - - protected onNodeSelected({ node }: EventArgs['node:selected']) { - if (this.isSelectionEnabled) { - const widgets = this.graph.hook.createKnob(node, { clearAll: false }) - if (widgets) { - this.widgets.set(node, widgets) - } - } - } - - protected onNodeUnSelected({ node }: EventArgs['node:unselected']) { - if (this.isSelectionEnabled) { - const widgets = this.widgets.get(node) - if (widgets) { - widgets.forEach((widget) => widget.dispose()) - } - this.widgets.delete(node) - } - } -} diff --git a/packages/x6/src/graph/minimap.ts b/packages/x6/src/graph/minimap.ts deleted file mode 100644 index 297bb95ca99..00000000000 --- a/packages/x6/src/graph/minimap.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Base } from './base' -import { MiniMap } from '../addon/minimap' - -export class MiniMapManager extends Base { - public widget: MiniMap | null - - protected get widgetOptions() { - return this.options.minimap - } - - protected init() { - this.widget = this.graph.hook.createMiniMap() - } - - @Base.dispose() - dispose() { - if (this.widget) { - this.widget.dispose() - } - } -} - -export namespace MiniMapManager { - export interface Options - extends Omit { - enabled: boolean - container: HTMLElement - } -} diff --git a/packages/x6/src/graph/mousewheel.ts b/packages/x6/src/graph/mousewheel.ts index dcbd6231bbb..0088cb1d365 100644 --- a/packages/x6/src/graph/mousewheel.ts +++ b/packages/x6/src/graph/mousewheel.ts @@ -1,11 +1,9 @@ -import { Graph } from './graph' -import { ModifierKey } from '../types' -import { Dom, NumberExt } from '../util' -import { Disposable, IDisablable } from '../common' +import { ModifierKey, Dom, NumberExt, Disposable } from '@antv/x6-common' +import { Base } from './base' -export class MouseWheel extends Disposable implements IDisablable { - public readonly target: HTMLElement | Document - public readonly container: HTMLElement +export class MouseWheel extends Base { + public target: HTMLElement | Document + public container: HTMLElement protected cumulatedFactor = 1 protected currentScale: number | null @@ -13,74 +11,65 @@ export class MouseWheel extends Disposable implements IDisablable { private mousewheelHandle: Dom.MouseWheelHandle - protected get graph() { - return this.options.graph + protected get widgetOptions() { + return this.options.mousewheel } - constructor(public readonly options: MouseWheel.Options) { - super() - - const scroller = this.graph.scroller.widget - this.container = scroller ? scroller.container : this.graph.container - this.target = this.options.global ? document : this.container + protected init() { + this.container = this.graph.container + this.target = this.widgetOptions.global ? document : this.container this.mousewheelHandle = new Dom.MouseWheelHandle( this.target, this.onMouseWheel.bind(this), this.allowMouseWheel.bind(this), ) - if (this.options.enabled) { + if (this.widgetOptions.enabled) { this.enable(true) } } get disabled() { - return this.options.enabled !== true + return this.widgetOptions.enabled !== true } enable(force?: boolean) { if (this.disabled || force) { - this.options.enabled = true - this.graph.options.mousewheel.enabled = true + this.widgetOptions.enabled = true this.mousewheelHandle.enable() } } disable() { if (!this.disabled) { - this.options.enabled = false - this.graph.options.mousewheel.enabled = false + this.widgetOptions.enabled = false this.mousewheelHandle.disable() } } - protected allowMouseWheel(evt: JQueryMousewheel.JQueryMousewheelEventObject) { - const e = (evt.originalEvent || evt) as WheelEvent - const guard = this.options.guard + protected allowMouseWheel(e: WheelEvent) { + const guard = this.widgetOptions.guard return ( - (guard == null || guard.call(this.graph, e)) && - ModifierKey.isMatch(e, this.options.modifiers) + (guard == null || guard.call(e)) && + ModifierKey.isMatch(e, this.widgetOptions.modifiers) ) } - protected onMouseWheel(evt: JQueryMousewheel.JQueryMousewheelEventObject) { - const e = (evt.originalEvent || evt) as WheelEvent - const guard = this.options.guard + protected onMouseWheel(e: WheelEvent) { + const guard = this.widgetOptions.guard if ( - (guard == null || guard.call(this.graph, e)) && - ModifierKey.isMatch(e, this.options.modifiers) + (guard == null || guard.call(e)) && + ModifierKey.isMatch(e, this.widgetOptions.modifiers) ) { - const factor = this.options.factor || 1.2 + const factor = this.widgetOptions.factor || 1.2 if (this.currentScale == null) { - this.startPos = { x: evt.clientX, y: evt.clientY } - this.currentScale = this.graph.scroller.widget - ? this.graph.scroller.widget.zoom() - : this.graph.transform.getScale().sx + this.startPos = { x: e.clientX, y: e.clientY } + this.currentScale = this.graph.transform.getScale().sx } - const delta = evt.deltaY + const delta = e.deltaY if (delta < 0) { // zoomin // ------ @@ -115,27 +104,16 @@ export class MouseWheel extends Disposable implements IDisablable { this.currentScale, ) - const scroller = this.graph.scroller.widget const currentScale = this.currentScale! let targetScale = this.graph.transform.clampScale( currentScale * this.cumulatedFactor, ) - const minScale = this.options.minScale || Number.MIN_SAFE_INTEGER - const maxScale = this.options.maxScale || Number.MAX_SAFE_INTEGER + const minScale = this.widgetOptions.minScale || Number.MIN_SAFE_INTEGER + const maxScale = this.widgetOptions.maxScale || Number.MAX_SAFE_INTEGER targetScale = NumberExt.clamp(targetScale, minScale, maxScale) if (targetScale !== currentScale) { - if (scroller) { - if (this.options.zoomAtMousePosition) { - const origin = this.graph.coord.clientToLocalPoint(this.startPos) - scroller.zoom(targetScale, { - absolute: true, - center: origin.clone(), - }) - } else { - scroller.zoom(targetScale, { absolute: true }) - } - } else if (this.options.zoomAtMousePosition) { + if (this.widgetOptions.zoomAtMousePosition) { const origin = this.graph.coord.clientToGraphPoint(this.startPos) this.graph.zoom(targetScale, { absolute: true, @@ -158,14 +136,13 @@ export class MouseWheel extends Disposable implements IDisablable { export namespace MouseWheel { export interface Options { - graph: Graph enabled?: boolean global?: boolean factor?: number minScale?: number maxScale?: number modifiers?: string | ModifierKey[] | null - guard?: (this: Graph, e: WheelEvent) => boolean + guard?: (e: WheelEvent) => boolean zoomAtMousePosition?: boolean } } diff --git a/packages/x6/src/graph/options.ts b/packages/x6/src/graph/options.ts index b3380ee240c..8a1d5734652 100644 --- a/packages/x6/src/graph/options.ts +++ b/packages/x6/src/graph/options.ts @@ -1,38 +1,26 @@ -import { Util } from '../global' -import { ObjectExt } from '../util' -import { Rectangle } from '../geometry' -import { Nilable, KeyValue } from '../types' -import { Cell, Edge, Node, Model } from '../model' -import { CellView, NodeView, EdgeView } from '../view' -import { Edge as StandardEdge } from '../shape/standard/edge' +import { ObjectExt, Dom, Nilable } from '@antv/x6-common' +import { Rectangle } from '@antv/x6-geometry' +import { Config } from '../config' +import { Graph } from '../graph' +import { GridManager } from './grid' +import { BackgroundManager } from './background' +import { PanningManager } from './panning' +import { MouseWheel } from './mousewheel' +import { Edge as StandardEdge } from '../shape' +import { Model, Cell, Node, Edge } from '../model' +import { CellView, NodeView, EdgeView, Markup } from '../view' import { Router, Connector, NodeAnchor, EdgeAnchor, ConnectionPoint, - ConnectionStrategy, } from '../registry' -import { Widget } from '../addon/common' -import { Hook } from './hook' -import { Graph } from './graph' -import { GraphView } from './view' -import { GridManager } from './grid' -import { HistoryManager } from './history' -import { PanningManager } from './panning' -import { SnaplineManager } from './snapline' -import { ScrollerManager } from './scroller' -import { SelectionManager } from './selection' -import { ClipboardManager } from './clipboard' import { HighlightManager } from './highlight' -import { BackgroundManager } from './background' -import { MiniMapManager } from './minimap' -import { Keyboard } from './keyboard' -import { MouseWheel } from './mousewheel' -import { Renderer } from './renderer' +import { PortManager } from '../model/port' export namespace Options { - interface Common extends Partial { + interface Common { container: HTMLElement model?: Model @@ -49,127 +37,26 @@ export namespace Options { max?: number } - /** - * The sorting type to use when rendering the views in this graph. - * - * - `exact` - render views according to their z-index. Views with - * different z-index are rendered in order, and views with the - * same z-index are rendered in the order in which they were added. - * This is by far the slowest option, present mainly for backwards - * compatibility. - * - * - `approx` - render views according to their z-index. Views with - * different z-index are rendered in order, but the ordering of views - * with the same z-index is indeterminate. Similar in functionality - * to the `exact` option, but much faster. - * - * - `none` - render views in an indeterminate order. (Note that this - * setting disables `toFront`/`toBack` methods.) - */ - sorting: GraphView.SortType - - /** - * Specify if the grapg uses asynchronous rendering to display cells. - * This is very useful for adding a large number of cells into the graph. - * The rendering performance boost is significant and it doesn't block - * the UI. However, asynchronous rendering brings about some caveats: - * - * - The views of freshly added cells may not have yet been added to - * this graph's DOM. - * - Some views may have been removed from the DOM by - * `graph.options.checkView` function. - * - Views already present in the DOM may not have been updated to - * reflect changes made in this graph since the last render. - * - * For the methods that truly need a to refer to a CellView, one way to - * prevent inconsistencies is to rely on the 'render:done' graph event. - * This event signals that all scheduled updates are done and that the - * state of cell views is consistent with the state of the cell models. - * - * Alternatively, you may trigger a manual update immediately before a - * sensitive function call: - * - * - `graph.renderer.requireView()` - bring a single view into the DOM - * and update it. - * - `graph.renderer.dumpViews()` - bring all views into the DOM and - * update them. - * - `graph.renderer.updateViews()` - update all views. - */ - async: boolean - - frozen: boolean - - /** - * A callback function that is used to determine whether a given view - * should be shown in an `async` graph. If the function returns `true`, - * the view is attached to the DOM; if it returns `false`, the view is - * detached from the DOM. - */ - checkView?: Nilable - - /** - * When defined as a number, it denotes the required mousemove events - * before a new edge is created from a magnet. When defined as keyword - * 'onleave', the edge is created when the pointer leaves the magnet - * DOM element. - */ - magnetThreshold: number | 'onleave' - - /** - * Number of required mousemove events before the first mousemove - * event will be triggered. - */ - moveThreshold: number - - /** - * Allowed number of mousemove events after which the click event - * will be still triggered. - */ + moveThreshold: 0 clickThreshold: number - - /** - * Prevent the default context menu from being displayed. - */ - preventDefaultContextMenu: boolean - + magnetThreshold: number | 'onleave' preventDefaultDblClick: boolean - + preventDefaultContextMenu: boolean preventDefaultMouseDown: boolean - - /** - * Prevents default action when an empty graph area is clicked. - * Setting the option to `false` will make the graph pannable - * inside a container on touch devices. - * - * It defaults to `true`. - */ preventDefaultBlankAction: boolean - interacting: CellView.Interacting - /** - * Guard the graph from handling a UI event. Returns `true` if you want - * to prevent the graph from handling the event evt, `false` otherwise. - * This is an advanced option that can be useful if you have your own - * logic for handling events. - */ - guard: (e: JQuery.TriggeredEvent, view?: CellView | null) => boolean + virtual?: boolean + + guard: (e: Dom.EventObject, view?: CellView | null) => boolean + + onPortRendered?: (args: OnPortRenderedArgs) => void } export interface ManualBooleans { - rotating: boolean | Partial - resizing: boolean | Partial - embedding: boolean | Partial - selecting: boolean | Partial - snapline: boolean | Partial panning: boolean | Partial - clipboard: boolean | Partial - history: boolean | Partial - scroller: boolean | Partial - minimap: boolean | Partial - keyboard: boolean | Partial> - mousewheel: boolean | Partial> - knob: boolean | Partial + mousewheel: boolean | Partial + embedding: boolean | Partial } export interface Manual extends Partial, Partial { @@ -179,48 +66,23 @@ export namespace Options { | (Partial & GridManager.DrawGridOptions) connecting?: Partial translating?: Partial - transforming?: Partial highlighting?: Partial } export interface Definition extends Common { grid: GridManager.Options + panning: PanningManager.Options + mousewheel: MouseWheel.Options + embedding: Embedding connecting: Connecting - rotating: Rotating - resizing: Resizing translating: Translating - transforming: Transforming highlighting: Highlighting - embedding: Embedding - panning: PanningManager.Options - selecting: SelectionManager.Options - snapline: SnaplineManager.Options - clipboard: ClipboardManager.Options - history: HistoryManager.CommonOptions - scroller: ScrollerManager.Options - minimap: MiniMapManager.Options - keyboard: Omit - mousewheel: Omit - knob: Knob } } export namespace Options { type OptionItem = S | ((this: Graph, arg: T) => S) - export function parseOptionGroup< - K extends KeyValue, - S extends KeyValue = KeyValue, - T = any, - >(graph: Graph, arg: T, options: S): K { - const result: any = {} - Object.keys(options || {}).forEach((key) => { - const val = options[key] - result[key] = typeof val === 'function' ? val.call(graph, arg) : val - }) - return result - } - type NodeAnchorOptions = | string | NodeAnchor.NativeItem @@ -240,42 +102,6 @@ export namespace Options { */ snap: boolean | { radius: number } - /** - * @deprecated - * When set to `false`, edges can not be pinned to the graph meaning a - * source/target of a edge can be a point on the graph. - */ - dangling: - | boolean - | (( - this: Graph, - args: { - edge: Edge - sourceCell?: Cell | null - targetCell?: Cell | null - sourcePort?: string - targetPort?: string - }, - ) => boolean) - - /** - * @deprecated - * When set to `false`, an node may not have more than one edge with the - * same source and target node. - */ - multi: - | boolean - | (( - this: Graph, - args: { - edge: Edge - sourceCell?: Cell | null - targetCell?: Cell | null - sourcePort?: string - targetPort?: string - }, - ) => boolean) - /** * Specify whether connect to point on the graph is allowed. */ @@ -342,11 +168,6 @@ export namespace Options { router: string | Router.NativeItem | Router.ManaualItem connector: string | Connector.NativeItem | Connector.ManaualItem - strategy?: - | string - | ConnectionStrategy.NativeItem - | ConnectionPoint.ManaualItem - | null /** * Check whether to add a new edge to the graph when user clicks @@ -358,7 +179,7 @@ export namespace Options { cell: Cell view: CellView magnet: Element - e: JQuery.MouseDownEvent | JQuery.MouseEnterEvent + e: Dom.MouseDownEvent | Dom.MouseEnterEvent }, ) => boolean @@ -407,20 +228,6 @@ export namespace Options { targetMagnet?: Element | null } - export interface TransformingRaw extends Widget.Options {} - - export type Transforming = { - [K in keyof TransformingRaw]?: OptionItem - } - - export interface KnobRaw extends Widget.Options { - enabled?: boolean - } - - export type Knob = { - [K in keyof KnobRaw]?: OptionItem - } - export interface Translating { /** * Restrict the translation (movement) of nodes by a given bounding box. @@ -432,39 +239,6 @@ export namespace Options { | OptionItem } - export interface RotatingRaw { - enabled?: boolean - grid?: number - } - - export type Rotating = { - [K in keyof RotatingRaw]?: OptionItem - } - - export interface ResizingRaw { - enabled?: boolean - minWidth?: number - maxWidth?: number - minHeight?: number - maxHeight?: number - orthogonal?: boolean - restrict?: boolean | number - /** - * **Deprecation Notice:** resizing option `restricted` is deprecated and - * will be moved in next major release. Use `restrict` instead. - * - * @deprecated - */ - restricted?: boolean | number - autoScroll?: boolean - preserveAspectRatio?: boolean - allowReverse?: boolean - } - - export type Resizing = { - [K in keyof ResizingRaw]?: OptionItem - } - export interface Embedding { enabled?: boolean @@ -534,23 +308,7 @@ export namespace Options { export namespace Options { export function get(options: Partial) { - const { - grid, - panning, - selecting, - embedding, - snapline, - resizing, - rotating, - knob, - clipboard, - history, - scroller, - minimap, - keyboard, - mousewheel, - ...others - } = options + const { grid, panning, mousewheel, embedding, ...others } = options // size // ---- @@ -586,18 +344,8 @@ export namespace Options { // ------- const booleas: (keyof Options.ManualBooleans)[] = [ 'panning', - 'selecting', - 'embedding', - 'snapline', - 'resizing', - 'rotating', - 'knob', - 'clipboard', - 'history', - 'scroller', - 'minimap', - 'keyboard', 'mousewheel', + 'embedding', ] booleas.forEach((key) => { @@ -612,34 +360,47 @@ export namespace Options { } }) - // background - // ---------- - if ( - result.background && - result.scroller.enabled && - result.scroller.background == null - ) { - result.scroller.background = result.background - delete result.background - } - return result } } +export namespace Options { + export interface OnPortRenderedArgs { + node: Node + port: PortManager.Port + container: Element + selectors?: Markup.Selectors + labelContainer?: Element + labelSelectors?: Markup.Selectors | null + contentContainer: Element + contentSelectors?: Markup.Selectors + } +} + export namespace Options { export const defaults: Partial = { x: 0, y: 0, - grid: { - size: 10, - visible: false, - }, scaling: { min: 0.01, max: 16, }, + grid: { + size: 10, + visible: false, + }, background: false, + + panning: { + enabled: false, + eventTypes: ['leftMouseDown'], + }, + mousewheel: { + enabled: false, + factor: 1.2, + zoomAtMousePosition: true, + }, + highlighting: { default: { name: 'stroke', @@ -650,24 +411,18 @@ export namespace Options { nodeAvailable: { name: 'className', args: { - className: Util.prefix('available-node'), + className: Config.prefix('available-node'), }, }, magnetAvailable: { name: 'className', args: { - className: Util.prefix('available-magnet'), + className: Config.prefix('available-magnet'), }, }, }, connecting: { snap: false, - multi: true, - // TODO: Unannotation the next line when the `multi` option was removed in the next major version. - // allowMulti: true, - dangling: true, - // TODO: Unannotation the next line when the `dangling` option was removed in the next major version. - // allowBlank: true, allowLoop: true, allowNode: true, allowEdge: false, @@ -677,7 +432,6 @@ export namespace Options { anchor: 'center', edgeAnchor: 'ratio', connectionPoint: 'boundary', - strategy: null, router: 'normal', connector: 'normal', @@ -690,84 +444,15 @@ export namespace Options { return new StandardEdge() }, }, - transforming: { - clearAll: true, - clearOnBlankMouseDown: true, - }, - resizing: { - enabled: false, - minWidth: 0, - minHeight: 0, - maxWidth: Number.MAX_SAFE_INTEGER, - maxHeight: Number.MAX_SAFE_INTEGER, - orthogonal: true, - restricted: false, - autoScroll: true, - preserveAspectRatio: false, - allowReverse: true, - }, - rotating: { - enabled: false, - grid: 15, - }, translating: { restrict: false, }, - knob: { - enabled: false, - clearAll: true, - clearOnBlankMouseDown: true, - }, embedding: { enabled: false, findParent: 'bbox', frontOnly: true, validate: () => true, }, - selecting: { - enabled: false, - rubberband: false, - rubberNode: true, - rubberEdge: false, // next version will set to true - pointerEvents: 'auto', - multiple: true, - movable: true, - strict: false, - useCellGeometry: false, - selectCellOnMoved: false, - selectNodeOnMoved: false, - selectEdgeOnMoved: false, - content: null, - handles: null, - }, - panning: { - enabled: false, - eventTypes: ['leftMouseDown'], - }, - snapline: { - enabled: false, - }, - clipboard: { - enabled: false, - }, - history: { - enabled: false, - }, - scroller: { - enabled: false, - }, - keyboard: { - enabled: false, - }, - mousewheel: { - enabled: false, - factor: 1.2, - zoomAtMousePosition: true, - }, - - async: false, - frozen: false, - sorting: 'exact', moveThreshold: 0, clickThreshold: 0, diff --git a/packages/x6/src/graph/panning.ts b/packages/x6/src/graph/panning.ts index 28061881e34..191afc1657f 100644 --- a/packages/x6/src/graph/panning.ts +++ b/packages/x6/src/graph/panning.ts @@ -1,5 +1,4 @@ -import { ModifierKey } from '../types' -import { Dom } from '../util' +import { ModifierKey, Dom } from '@antv/x6-common' import { Base } from './base' export class PanningManager extends Base { @@ -33,7 +32,7 @@ export class PanningManager extends Base { } if (eventTypes.includes('rightMouseDown')) { this.onRightMouseDown = this.onRightMouseDown.bind(this) - this.view.$(this.graph.container).on('mousedown', this.onRightMouseDown) + Dom.Event.on(this.graph.container, 'mousedown', this.onRightMouseDown) } if (eventTypes.includes('mouseWheel')) { this.mousewheelHandle = new Dom.MouseWheelHandle( @@ -56,7 +55,7 @@ export class PanningManager extends Base { this.graph.off('edge:unhandled:mousedown', this.preparePanning, this) } if (eventTypes.includes('rightMouseDown')) { - this.view.$(this.graph.container).off('mousedown', this.onRightMouseDown) + Dom.Event.off(this.graph.container, 'mousedown', this.onRightMouseDown) } if (eventTypes.includes('mouseWheel')) { if (this.mousewheelHandle) { @@ -65,38 +64,35 @@ export class PanningManager extends Base { } } - protected preparePanning({ e }: { e: JQuery.MouseDownEvent }) { - if ( - this.allowPanning(e, true) || - (this.allowPanning(e) && !this.graph.selection.allowRubberband(e, true)) - ) { + protected preparePanning({ e }: { e: Dom.MouseDownEvent }) { + // todo 暂时删除 selection 的判断 + if (this.allowPanning(e, true)) { this.startPanning(e) } } - allowPanning(e: JQuery.MouseDownEvent, strict?: boolean) { + allowPanning(e: Dom.MouseDownEvent, strict?: boolean) { return ( this.pannable && - ModifierKey.isMatch(e, this.widgetOptions.modifiers, strict) && - this.graph.hook.allowPanning(e) + ModifierKey.isMatch(e, this.widgetOptions.modifiers, strict) ) } - protected startPanning(evt: JQuery.MouseDownEvent) { + protected startPanning(evt: Dom.MouseDownEvent) { const e = this.view.normalizeEvent(evt) this.clientX = e.clientX this.clientY = e.clientY this.panning = true this.updateClassName() - this.view.$(document.body).on({ + Dom.Event.on(document.body, { 'mousemove.panning touchmove.panning': this.pan.bind(this), 'mouseup.panning touchend.panning': this.stopPanning.bind(this), 'mouseleave.panning': this.stopPanning.bind(this), }) - this.view.$(window).on('mouseup.panning', this.stopPanning.bind(this)) + Dom.Event.on(window as any, 'mouseup.panning', this.stopPanning.bind(this)) } - protected pan(evt: JQuery.MouseMoveEvent) { + protected pan(evt: Dom.MouseMoveEvent) { const e = this.view.normalizeEvent(evt) const dx = e.clientX - this.clientX const dy = e.clientY - this.clientY @@ -106,11 +102,11 @@ export class PanningManager extends Base { } // eslint-disable-next-line - protected stopPanning(e: JQuery.MouseUpEvent) { + protected stopPanning(e: Dom.MouseUpEvent) { this.panning = false this.updateClassName() - this.view.$(document.body).off('.panning') - this.view.$(window).off('.panning') + Dom.Event.off(document.body, '.panning') + Dom.Event.off(window as any, '.panning') } protected updateClassName() { @@ -131,21 +127,17 @@ export class PanningManager extends Base { } } - protected onRightMouseDown(e: JQuery.MouseDownEvent) { + protected onRightMouseDown(e: Dom.MouseDownEvent) { if (e.button === 2 && this.allowPanning(e, true)) { this.startPanning(e) } } - protected allowMouseWheel(e: JQueryMousewheel.JQueryMousewheelEventObject) { + protected allowMouseWheel(e: WheelEvent) { return this.pannable && !e.ctrlKey } - protected onMouseWheel( - e: JQueryMousewheel.JQueryMousewheelEventObject, - deltaX: number, - deltaY: number, - ) { + protected onMouseWheel(e: WheelEvent, deltaX: number, deltaY: number) { if (!e.ctrlKey) { this.graph.translateBy(-deltaX, -deltaY) } diff --git a/packages/x6/src/graph/print.less b/packages/x6/src/graph/print.less deleted file mode 100644 index 841fe00b534..00000000000 --- a/packages/x6/src/graph/print.less +++ /dev/null @@ -1,50 +0,0 @@ -@import '../style/index'; - -@print-prefix-cls: ~'@{x6-prefix}-graph-print'; -@printing-prefix-cls: ~'@{x6-prefix}-graph-printing'; - -.@{print-prefix-cls} { - position: relative; - - & &-ready { - display: none; - } - - & &-preview { - overflow: hidden !important; - background: #fff !important; - } -} - -@media print { - html, - html > body.@{printing-prefix-cls} { - position: relative !important; - width: 100% !important; - height: 100% !important; - margin: 0 !important; - padding: 0 !important; - } - - html > body.@{printing-prefix-cls} > * { - display: none !important; - } - - html > body.@{printing-prefix-cls} > .@{print-prefix-cls} { - display: block !important; - } - - .@{print-prefix-cls} { - top: 0 !important; - left: 0 !important; - margin: 0 !important; - padding: 0 !important; - overflow: hidden !important; - page-break-after: always; - background: #fff !important; - - & &-ready { - display: none; - } - } -} diff --git a/packages/x6/src/graph/print.ts b/packages/x6/src/graph/print.ts deleted file mode 100644 index 832a039c776..00000000000 --- a/packages/x6/src/graph/print.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { Size, KeyValue } from '../types' -import { Rectangle } from '../geometry' -import { NumberExt, JQuery, Dom, Unit, Vector } from '../util' -import { Base } from './base' - -export class PrintManager extends Base { - show(options: Partial = {}) { - const localOptions = { - ...PrintManager.defaultOptions, - ...options, - } as PrintManager.Options - - const $pages = this.createPrintPages(localOptions) - localOptions.ready( - $pages, - ($pages) => this.showPrintWindow($pages, localOptions), - { - sheetSize: this.getSheetSize(localOptions), - }, - ) - } - - protected get className() { - return this.view.prefixClassName('graph-print') - } - - protected showPrintWindow( - $pages: JQuery[] | false, - options: PrintManager.Options, - ) { - if ($pages) { - const $body = JQuery(document.body) - const $container = JQuery(this.view.container) - const bodyClassName = this.view.prefixClassName('graph-printing') - $body.addClass(bodyClassName) - const $detached = $container.children().detach() - $pages.forEach(($page) => { - $page - .removeClass(`${this.className}-preview`) - .addClass(`${this.className}-ready`) - .appendTo($body) - }) - - let ret = false - const cb = () => { - if (!ret) { - ret = true - $body.removeClass(bodyClassName) - $pages.forEach(($page) => $page.remove()) - $container.append($detached) - JQuery(`#${this.styleSheetId}`).remove() - this.graph.trigger('after:print', options) - JQuery(window).off('afterprint', cb) - } - } - - JQuery(window).one('afterprint', cb) - setTimeout(cb, 200) - window.print() - } - } - - protected createPrintPage( - pageArea: Rectangle.RectangleLike, - options: PrintManager.Options, - ) { - this.graph.trigger('before:print', options) - - const $page = JQuery('
').addClass(this.className) - const $wrap = JQuery('
') - .addClass(this.view.prefixClassName('graph-print-inner')) - .css('position', 'relative') - - if (options.size) { - $page.addClass(`${this.className}-size-${options.size}`) - } - - const vSVG = Vector.create(this.view.svg).clone() - const vStage = vSVG.findOne( - `.${this.view.prefixClassName('graph-svg-stage')}`, - )! - - $wrap.append(vSVG.node) - - const sheetSize = this.getSheetSize(options) - const graphArea = this.graph.transform.getGraphArea() - const s = this.graph.transform.getScale() - const ts = this.graph.translate() - const matrix = Dom.createSVGMatrix().translate(ts.tx / s.sx, ts.ty / s.sy) - const info = this.getPageInfo(graphArea, pageArea, sheetSize) - const scale = info.scale - const bbox = info.bbox - - $wrap.css({ - left: 0, - top: 0, - }) - - vSVG.attr({ - width: bbox.width * scale, - height: bbox.height * scale, - style: 'position:relative', - viewBox: [bbox.x, bbox.y, bbox.width, bbox.height].join(' '), - }) - - vStage.attr('transform', Dom.matrixToTransformString(matrix)) - $page.append($wrap) - $page.addClass(`${this.className}-preview`) - - return { - $page, - sheetSize, - } - } - - protected createPrintPages(options: PrintManager.Options) { - let ret - const area = this.getPrintArea(options) - const $pages = [] - - if (options.page) { - const pageSize = this.getPageSize(area, options.page) - const pageAreas = this.getPageAreas(area, pageSize) - pageAreas.forEach((pageArea) => { - ret = this.createPrintPage(pageArea, options) - $pages.push(ret.$page) - }) - } else { - ret = this.createPrintPage(area, options) - $pages.push(ret.$page) - } - - if (ret) { - const size = { - width: ret.sheetSize.cssWidth, - height: ret.sheetSize.cssHeight, - } - this.updatePrintStyle(size, options) - } - - return $pages - } - - protected get styleSheetId() { - return this.view.prefixClassName('graph-print-style') - } - - protected updatePrintStyle( - size: KeyValue, - options: PrintManager.Options, - ) { - const sizeCSS = Object.keys(size).reduce( - (memo, key) => `${memo} ${key}:${size[key]};`, - '', - ) - - const margin = NumberExt.normalizeSides(options.margin) - const marginUnit = options.marginUnit || '' - const sheetUnit = options.sheetUnit || '' - - const css = ` - @media print { - .${this.className}.${this.className}-ready { - ${sizeCSS} - } - - @page { - margin: - ${[ - margin.top + marginUnit, - margin.right + marginUnit, - margin.bottom + marginUnit, - margin.left + marginUnit, - ].join(' ')}; - size: ${options.sheet.width + sheetUnit} ${ - options.sheet.height + sheetUnit - }; - - .${this.className}.${this.className}-preview { - ${sizeCSS} - } - }` - - const id = this.styleSheetId - const $style = JQuery(`#${id}`) - if ($style.length) { - $style.html(css) - } else { - JQuery('head').append( - `''`, // lgtm[js/html-constructed-from-input] - ) - } - } - - protected getPrintArea(options: PrintManager.Options) { - let area = options.area - if (!area) { - const padding = NumberExt.normalizeSides(options.padding) - area = this.graph.getContentArea().moveAndExpand({ - x: -padding.left, - y: -padding.top, - width: padding.left + padding.right, - height: padding.top + padding.bottom, - }) - } - return area - } - - protected getPageSize( - area: Rectangle.RectangleLike, - poster: PrintManager.Page, - ): Size { - if (typeof poster === 'object') { - const raw = poster as any - const page = { - width: raw.width, - height: raw.height, - } - - if (page.width == null) { - page.width = Math.ceil(area.width / (raw.columns || 1)) - } - - if (page.height == null) { - page.height = Math.ceil(area.height / (raw.rows || 1)) - } - - return page - } - - return { - width: area.width, - height: area.height, - } - } - - protected getPageAreas(area: Rectangle.RectangleLike, pageSize: Size) { - const pages = [] - const width = pageSize.width - const height = pageSize.height - - for (let w = 0, n = 0; w < area.height && n < 200; w += height, n += 1) { - for (let h = 0, m = 0; h < area.width && m < 200; h += width, m += 1) { - pages.push(new Rectangle(area.x + h, area.y + w, width, height)) - } - } - - return pages - } - - protected getSheetSize( - options: PrintManager.Options, - ): PrintManager.SheetSize { - const sheet = options.sheet - const margin = NumberExt.normalizeSides(options.margin) - const marginUnit = options.marginUnit || '' - const sheetUnit = options.sheetUnit || '' - - const cssWidth = - // eslint-disable-next-line - `calc(${sheet.width}${sheetUnit} - ${ - margin.left + margin.right - }${marginUnit})` - - const cssHeight = - // eslint-disable-next-line - `calc(${sheet.height}${sheetUnit} - ${ - margin.top + margin.bottom - }${marginUnit})` - - const ret = Unit.measure(cssWidth, cssHeight) - return { - cssWidth, - cssHeight, - width: ret.width, - height: ret.height, - } - } - - protected getPageInfo( - graphArea: Rectangle.RectangleLike, - pageArea: Rectangle.RectangleLike, - sheetSize: Size, - ) { - const bbox = new Rectangle( - pageArea.x - graphArea.x, - pageArea.y - graphArea.y, - pageArea.width, - pageArea.height, - ) - const pageRatio = bbox.width / bbox.height - const graphRatio = sheetSize.width / sheetSize.height - - return { - bbox, - scale: - graphRatio < pageRatio - ? sheetSize.width / bbox.width - : sheetSize.height / bbox.height, - fitHorizontal: graphRatio < pageRatio, - } - } - - @Base.dispose() - dispose() {} -} - -export namespace PrintManager { - export type Page = - | boolean - | Size - | { - rows: number - columns: number - } - - export interface Options { - size?: string - sheet: Size - sheetUnit?: Unit - area?: Rectangle.RectangleLike - page?: Page - margin?: NumberExt.SideOptions - marginUnit?: Unit - padding?: NumberExt.SideOptions - ready: ( - $pages: JQuery[], - readyToPrint: ($pages: JQuery[] | false) => any, - options: { - sheetSize: SheetSize - }, - ) => any - } - - export interface SheetSize extends Size { - cssWidth: string - cssHeight: string - } - - export const defaultOptions: Options = { - page: false, - sheet: { - width: 210, - height: 297, - }, - sheetUnit: 'mm', - margin: 0.4, - marginUnit: 'in', - padding: 5, - ready: ($pages, readyToPrint) => readyToPrint($pages), - } -} diff --git a/packages/x6/src/graph/renderer.ts b/packages/x6/src/graph/renderer.ts deleted file mode 100644 index faf18a19634..00000000000 --- a/packages/x6/src/graph/renderer.ts +++ /dev/null @@ -1,1323 +0,0 @@ -import { KeyValue } from '../types' -import { Dom, FunctionExt } from '../util' -import { Point, Rectangle } from '../geometry' -import { Cell, Edge, Model } from '../model' -import { View, CellView, EdgeView } from '../view' -import { FlagManager } from '../view/flag' -import { Graph } from './graph' -import { Base } from './base' - -export class Renderer extends Base { - protected views: KeyValue - protected zPivots: KeyValue - protected updates: Renderer.Updates - - protected init() { - this.resetUpdates() - this.startListening() - - // Renders existing cells in the model. - this.resetViews(this.model.getCells()) - - // Starts rendering loop. - if (!this.isFrozen() && this.isAsync()) { - this.updateViewsAsync() - } - } - - protected startListening() { - this.model.on('sorted', this.onSortModel, this) - this.model.on('reseted', this.onModelReseted, this) - this.model.on('batch:stop', this.onBatchStop, this) - this.model.on('cell:added', this.onCellAdded, this) - this.model.on('cell:removed', this.onCellRemoved, this) - this.model.on('cell:change:zIndex', this.onCellZIndexChanged, this) - this.model.on('cell:change:visible', this.onCellVisibleChanged, this) - } - - protected stopListening() { - this.model.off('sorted', this.onSortModel, this) - this.model.off('reseted', this.onModelReseted, this) - this.model.off('batch:stop', this.onBatchStop, this) - this.model.off('cell:added', this.onCellAdded, this) - this.model.off('cell:removed', this.onCellRemoved, this) - this.model.off('cell:change:zIndex', this.onCellZIndexChanged, this) - this.model.off('cell:change:visible', this.onCellVisibleChanged, this) - } - - protected resetUpdates() { - this.updates = { - priorities: [{}, {}, {}], - - mounted: {}, - mountedCids: [], - - unmounted: {}, - unmountedCids: [], - - count: 0, - sort: false, - frozen: false, - freezeKey: null, - - animationId: null, - } - } - - protected onSortModel() { - if (this.model.hasActiveBatch(Renderer.SORT_DELAYING_BATCHES)) { - return - } - - this.sortViews() - } - - protected onModelReseted({ options }: Model.EventArgs['reseted']) { - this.removeZPivots() - this.resetViews(this.model.getCells(), options) - } - - protected onBatchStop({ name, data }: Model.EventArgs['batch:stop']) { - if (this.isFrozen()) { - return - } - - const model = this.model - if (!this.isAsync()) { - const updateDelayingBatches = Renderer.UPDATE_DELAYING_BATCHES - if ( - updateDelayingBatches.includes(name as Model.BatchName) && - !model.hasActiveBatch(updateDelayingBatches) - ) { - this.updateViews(data) - } - } - - const sortDelayingBatches = Renderer.SORT_DELAYING_BATCHES - if ( - sortDelayingBatches.includes(name as Model.BatchName) && - !model.hasActiveBatch(sortDelayingBatches) - ) { - this.sortViews() - } - } - - protected onCellAdded({ cell, options }: Model.EventArgs['cell:added']) { - const position = options.position - if (this.isAsync() || typeof position !== 'number') { - this.renderView(cell, options) - } else { - if (options.maxPosition === position) { - this.freeze({ key: 'addCells' }) - } - this.renderView(cell, options) - if (position === 0) { - this.unfreeze({ key: 'addCells' }) - } - } - } - - protected onCellRemoved({ cell, options }: Model.EventArgs['cell:removed']) { - const view = this.findViewByCell(cell) - if (view) { - this.requestViewUpdate(view, Renderer.FLAG_REMOVE, view.priority, options) - } - } - - protected onCellZIndexChanged({ - cell, - options, - }: Model.EventArgs['cell:change:zIndex']) { - if (this.options.sorting === 'approx') { - const view = this.findViewByCell(cell) - if (view) { - this.requestViewUpdate( - view, - Renderer.FLAG_INSERT, - view.priority, - options, - ) - } - } - } - - protected onCellVisibleChanged({ - cell, - current: visible, - options, - }: Model.EventArgs['cell:change:visible']) { - // Hide connected edges before cell - if (!visible) { - this.processEdgeOnTerminalVisibleChanged(cell, false) - } - - const view = this.findViewByCell(cell) - if (!visible && view) { - this.removeView(cell) - } else if (visible && view == null) { - this.renderView(cell, options) - } - - // Show connected edges after cell rendered - if (visible) { - this.processEdgeOnTerminalVisibleChanged(cell, true) - } - } - - protected processEdgeOnTerminalVisibleChanged(node: Cell, visible: boolean) { - const getOpposite = (edge: Edge, currentTerminal: Cell) => { - const sourceId = edge.getSourceCellId() - if (sourceId !== currentTerminal.id) { - return edge.getSourceCell() - } - - const targetId = edge.getTargetCellId() - if (targetId !== currentTerminal.id) { - return edge.getTargetCell() - } - - return null - } - - this.model.getConnectedEdges(node).forEach((edge) => { - const opposite = getOpposite(edge, node) - if (opposite == null || opposite.isVisible()) { - visible ? edge.show() : edge.hide() - } - }) - } - - protected isEdgeTerminalVisible(edge: Edge, terminal: Edge.TerminalType) { - const cellId = - terminal === 'source' ? edge.getSourceCellId() : edge.getTargetCellId() - const cell = cellId ? this.model.getCell(cellId) : null - if (cell && !cell.isVisible()) { - return false - } - return true - } - - requestConnectedEdgesUpdate( - view: CellView, - options: Renderer.RequestViewUpdateOptions = {}, - ) { - if (CellView.isCellView(view)) { - const cell = view.cell - const edges = this.model.getConnectedEdges(cell) - for (let j = 0, n = edges.length; j < n; j += 1) { - const edge = edges[j] - const edgeView = this.findViewByCell(edge) - if (!edgeView) { - continue - } - - const flagLabels: FlagManager.Action[] = ['update'] - if (edge.getTargetCell() === cell) { - flagLabels.push('target') - } - if (edge.getSourceCell() === cell) { - flagLabels.push('source') - } - - this.scheduleViewUpdate( - edgeView, - edgeView.getFlag(flagLabels), - edgeView.priority, - options, - ) - } - } - } - - forcePostponedViewUpdate(view: CellView, flag: number) { - if (!view || !CellView.isCellView(view)) { - return false - } - - const cell = view.cell - if (cell.isNode()) { - return false - } - - const edgeView = view as EdgeView - - if (cell.isEdge() && (flag & view.getFlag(['source', 'target'])) === 0) { - // EdgeView is waiting for the source/target cellView to be rendered. - // This can happen when the cells are not in the viewport. - let sourceFlag = 0 - const sourceView = this.findViewByCell(cell.getSourceCell()) - if (sourceView && !this.isViewMounted(sourceView)) { - sourceFlag = this.dumpView(sourceView) - edgeView.updateTerminalMagnet('source') - } - let targetFlag = 0 - const targetView = this.findViewByCell(cell.getTargetCell()) - if (targetView && !this.isViewMounted(targetView)) { - targetFlag = this.dumpView(targetView) - edgeView.updateTerminalMagnet('target') - } - - if (sourceFlag === 0 && targetFlag === 0) { - // If leftover flag is 0, all view updates were done. - return !this.dumpView(edgeView) - } - } - - return false - } - - scheduleViewUpdate( - view: View, - flag: number, - priority: number, - options: Renderer.RequestViewUpdateOptions = {}, - ) { - const cid = view.cid - const updates = this.updates - let cache = updates.priorities[priority] - if (!cache) { - cache = updates.priorities[priority] = {} - } - - const currentFlag = cache[cid] || 0 - if ((currentFlag & flag) === flag) { - return - } - - if (!currentFlag) { - updates.count += 1 - } - - if (flag & Renderer.FLAG_REMOVE && currentFlag & Renderer.FLAG_INSERT) { - // When a view is removed we need to remove the - // insert flag as this is a reinsert. - cache[cid] ^= Renderer.FLAG_INSERT - } else if ( - flag & Renderer.FLAG_INSERT && - currentFlag & Renderer.FLAG_REMOVE - ) { - // When a view is added we need to remove the remove - // flag as this is view was previously removed. - cache[cid] ^= Renderer.FLAG_REMOVE - } - - cache[cid] |= flag - - this.graph.hook.onViewUpdated(view as CellView, flag, options) - } - - requestViewUpdate( - view: CellView, - flag: number, - priority: number, - options: Renderer.RequestViewUpdateOptions = {}, - ) { - this.scheduleViewUpdate(view, flag, priority, options) - - const isAsync = this.isAsync() - if ( - this.isFrozen() || - (isAsync && options.async !== false) || - this.model.hasActiveBatch(Renderer.UPDATE_DELAYING_BATCHES) - ) { - return - } - - const stats = this.updateViews(options) - if (isAsync) { - this.graph.trigger('render:done', { stats, options }) - } - } - - /** - * Adds view into the DOM and update it. - */ - dumpView(view: CellView, options: any = {}) { - if (view == null) { - return 0 - } - - const cid = view.cid - const updates = this.updates - const cache = updates.priorities[view.priority] - const flag = this.registerMountedView(view) | cache[cid] - delete cache[cid] - - if (!flag) { - return 0 - } - - return this.updateView(view, flag, options) - } - - /** - * Adds all views into the DOM and update them. - */ - dumpViews(options: Renderer.UpdateViewOptions = {}) { - this.checkView(options) - this.updateViews(options) - } - - /** - * Ensure the view associated with the cell is attached - * to the DOM and updated. - */ - requireView(cell: Cell, options: any = {}) { - const view = this.findViewByCell(cell) - if (view == null) { - return null - } - this.dumpView(view, options) - return view - } - - updateView(view: View, flag: number, options: any = {}) { - if (view == null) { - return 0 - } - - if (CellView.isCellView(view)) { - if (flag & Renderer.FLAG_REMOVE) { - this.removeView(view.cell as any) - return 0 - } - - if (flag & Renderer.FLAG_INSERT) { - this.insertView(view) - flag ^= Renderer.FLAG_INSERT // eslint-disable-line - } - } - - if (!flag) { - return 0 - } - - return view.confirmUpdate(flag, options) - } - - updateViews(options: Renderer.UpdateViewOptions = {}) { - let result: ReturnType - let batchCount = 0 - let updatedCount = 0 - let priority = Renderer.MIN_PRIORITY - - do { - result = this.updateViewsBatch(options) - batchCount += 1 - updatedCount += result.updatedCount - priority = Math.min(result.priority, priority) - } while (!result.empty) - - return { - priority, - batchCount, - updatedCount, - } - } - - protected updateViewsBatch(options: Renderer.UpdateViewOptions = {}) { - const updates = this.updates - const priorities = updates.priorities - const batchSize = options.batchSize || Renderer.UPDATE_BATCH_SIZE - - let empty = true - let priority = Renderer.MIN_PRIORITY - let mountedCount = 0 - let unmountedCount = 0 - let updatedCount = 0 - let postponedCount = 0 - - let checkView = options.checkView || this.options.checkView - if (typeof checkView !== 'function') { - checkView = null - } - - // eslint-disable-next-line - main: for (let p = 0, n = priorities.length; p < n; p += 1) { - const cache = priorities[p] - - // eslint-disable-next-line - for (const cid in cache) { - if (updatedCount >= batchSize) { - empty = false // goto next batch - break main // eslint-disable-line no-labels - } - - const view = View.views[cid] - if (!view) { - delete cache[cid] - continue - } - - let currentFlag = cache[cid] - // Do not check a view for viewport if we are about to remove the view. - if ((currentFlag & Renderer.FLAG_REMOVE) === 0) { - const isUnmounted = cid in updates.unmounted - if ( - checkView && - !FunctionExt.call(checkView, this.graph, { - view: view as CellView, - unmounted: isUnmounted, - }) - ) { - // Unmount view - if (!isUnmounted) { - this.registerUnmountedView(view) - view.unmount() - } - - updates.unmounted[cid] |= currentFlag - delete cache[cid] - unmountedCount += 1 - continue - } - - // Mount view - if (isUnmounted) { - currentFlag |= Renderer.FLAG_INSERT - mountedCount += 1 - } - currentFlag |= this.registerMountedView(view) - } - - const cellView = view as CellView - let leftoverFlag = this.updateView(view, currentFlag, options) - if (leftoverFlag > 0) { - const cell = cellView.cell - if (cell && cell.isEdge()) { - // remove edge view when source cell is invisible - if ( - cellView.hasAction(leftoverFlag, 'source') && - !this.isEdgeTerminalVisible(cell, 'source') - ) { - leftoverFlag = cellView.removeAction(leftoverFlag, 'source') - leftoverFlag |= Renderer.FLAG_REMOVE - } - - // remove edge view when target cell is invisible - if ( - cellView.hasAction(leftoverFlag, 'target') && - !this.isEdgeTerminalVisible(cell, 'target') - ) { - leftoverFlag = cellView.removeAction(leftoverFlag, 'target') - leftoverFlag |= Renderer.FLAG_REMOVE - } - } - } - - if (leftoverFlag > 0) { - // update has not finished - cache[cid] = leftoverFlag - if ( - !this.graph.hook.onViewPostponed(cellView, leftoverFlag, options) || - cache[cid] - ) { - postponedCount += 1 - empty = false - continue - } - } - - if (priority > p) { - priority = p - } - - updatedCount += 1 - delete cache[cid] - } - } - - return { - empty, - priority, - mountedCount, - unmountedCount, - updatedCount, - postponedCount, - } - } - - protected updateViewsAsync( - options: Renderer.UpdateViewsAsyncOptions = {}, - data: { - processed: number - priority: number - } = { - processed: 0, - priority: Renderer.MIN_PRIORITY, - }, - ) { - const updates = this.updates - const animationId = updates.animationId - if (animationId) { - Dom.cancelAnimationFrame(animationId) - if (data.processed === 0) { - const beforeFn = options.before - if (typeof beforeFn === 'function') { - FunctionExt.call(beforeFn, this.graph, this.graph) - } - } - - const stats = this.updateViewsBatch(options) - const checkout = this.checkViewImpl({ - checkView: options.checkView, - mountedBatchSize: Renderer.MOUNT_BATCH_SIZE - stats.mountedCount, - unmountedBatchSize: Renderer.MOUNT_BATCH_SIZE - stats.unmountedCount, - }) - - let processed = data.processed - const total = updates.count - const mountedCount = checkout.mountedCount - const unmountedCount = checkout.unmountedCount - - if (stats.updatedCount > 0) { - // Some updates have been just processed - processed += stats.updatedCount + stats.unmountedCount - data.priority = Math.min(stats.priority, data.priority) - if (stats.empty && mountedCount === 0) { - stats.priority = data.priority - stats.mountedCount += mountedCount - stats.unmountedCount += unmountedCount - this.graph.trigger('render:done', { stats, options }) - data.processed = 0 - updates.count = 0 - } else { - data.processed = processed - } - } - - // Progress callback - const progressFn = options.progress - if (total && typeof progressFn === 'function') { - FunctionExt.call(progressFn, this.graph, { - total, - done: stats.empty, - current: processed, - }) - } - - // The current frame could have been canceled in a callback - if (updates.animationId !== animationId) { - return - } - } - - updates.animationId = Dom.requestAnimationFrame(() => { - this.updateViewsAsync(options, data) - }) - } - - protected registerMountedView(view: View) { - const cid = view.cid - const updates = this.updates - - if (cid in updates.mounted) { - return 0 - } - - updates.mounted[cid] = true - updates.mountedCids.push(cid) - const flag = updates.unmounted[cid] || 0 - delete updates.unmounted[cid] - return flag - } - - protected registerUnmountedView(view: View) { - const cid = view.cid - const updates = this.updates - - if (cid in updates.unmounted) { - return 0 - } - - updates.unmounted[cid] |= Renderer.FLAG_INSERT - - const flag = updates.unmounted[cid] - updates.unmountedCids.push(cid) - delete updates.mounted[cid] - return flag - } - - isViewMounted(view: CellView) { - if (view == null) { - return false - } - - const cid = view.cid - return cid in this.updates.mounted - } - - getMountedViews() { - return Object.keys(this.updates.mounted).map((cid) => CellView.views[cid]) - } - - getUnmountedViews() { - return Object.keys(this.updates.unmounted).map((cid) => CellView.views[cid]) - } - - protected checkMountedViews( - viewportFn?: Renderer.CheckViewFn | null, - batchSize?: number, - ) { - let unmountCount = 0 - if (typeof viewportFn !== 'function') { - return unmountCount - } - - const updates = this.updates - const mounted = updates.mounted - const mountedCids = updates.mountedCids - const size = - batchSize == null - ? mountedCids.length - : Math.min(mountedCids.length, batchSize) - - for (let i = 0; i < size; i += 1) { - const cid = mountedCids[i] - if (!(cid in mounted)) { - continue - } - - const view = CellView.views[cid] - if (view == null) { - continue - } - - const shouldMount = FunctionExt.call(viewportFn, this.graph, { - view: view as CellView, - unmounted: true, - }) - - if (shouldMount) { - // Push at the end of all mounted ids - mountedCids.push(cid) - continue - } - - unmountCount += 1 - const flag = this.registerUnmountedView(view) - if (flag) { - view.unmount() - } - } - - // Get rid of views, that have been unmounted - mountedCids.splice(0, size) - return unmountCount - } - - protected checkUnmountedViews( - checkView?: Renderer.CheckViewFn | null, - batchSize?: number, - ) { - let mountCount = 0 - if (typeof checkView !== 'function') { - checkView = null // eslint-disable-line - } - - const updates = this.updates - const unmounted = updates.unmounted - const unmountedCids = updates.unmountedCids - const size = - batchSize == null - ? unmountedCids.length - : Math.min(unmountedCids.length, batchSize) - - for (let i = 0; i < size; i += 1) { - const cid = unmountedCids[i] - if (!(cid in unmounted)) { - continue - } - - const view = CellView.views[cid] as CellView - if (view == null) { - continue - } - - if ( - checkView && - !FunctionExt.call(checkView, this.graph, { view, unmounted: false }) - ) { - unmountedCids.push(cid) - continue - } - - mountCount += 1 - const flag = this.registerMountedView(view) - if (flag) { - this.scheduleViewUpdate(view, flag, view.priority, { - mounting: true, - }) - } - } - - // Get rid of views, that have been mounted - unmountedCids.splice(0, size) - - return mountCount - } - - protected checkViewImpl( - options: Renderer.CheckViewOptions & { - mountedBatchSize?: number - unmountedBatchSize?: number - } = { - mountedBatchSize: Number.MAX_SAFE_INTEGER, - unmountedBatchSize: Number.MAX_SAFE_INTEGER, - }, - ) { - const checkView = options.checkView || this.options.checkView - const unmountedCount = this.checkMountedViews( - checkView, - options.unmountedBatchSize, - ) - - const mountedCount = this.checkUnmountedViews( - checkView, - // Do not check views, that have been just unmounted - // and pushed at the end of the cids array - unmountedCount > 0 - ? Math.min( - this.updates.unmountedCids.length - unmountedCount, - options.mountedBatchSize as number, - ) - : options.mountedBatchSize, - ) - - return { mountedCount, unmountedCount } - } - - /** - * Determine every view in the graph should be attached/detached. - */ - protected checkView(options: Renderer.CheckViewOptions = {}) { - return this.checkViewImpl(options) - } - - isFrozen() { - return !!this.options.frozen - } - - /** - * Freeze the graph then the graph does not automatically re-render upon - * changes in the graph. This is useful when adding large numbers of cells. - */ - freeze(options: Renderer.FreezeOptions = {}) { - const key = options.key - const updates = this.updates - const frozen = this.options.frozen - const freezeKey = updates.freezeKey - - if (key && key !== freezeKey) { - if (frozen && freezeKey) { - // key passed, but the graph is already freezed with another key - return - } - updates.frozen = frozen - updates.freezeKey = key - } - - this.options.frozen = true - - const animationId = updates.animationId - updates.animationId = null - if (this.isAsync() && animationId != null) { - Dom.cancelAnimationFrame(animationId) - } - this.graph.trigger('freeze', { key }) - } - - unfreeze(options: Renderer.UnfreezeOptions = {}) { - const key = options.key - const updates = this.updates - const freezeKey = updates.freezeKey - // key passed, but the graph is already freezed with another key - if (key && freezeKey && key !== freezeKey) { - return - } - - updates.freezeKey = null - // key passed, but the graph is already freezed - if (key && key === freezeKey && updates.frozen) { - return - } - - const callback = () => { - this.options.frozen = updates.frozen = false - - if (updates.sort) { - this.sortViews() - updates.sort = false - } - - const afterFn = options.after - if (afterFn) { - FunctionExt.call(afterFn, this.graph, this.graph) - } - - this.graph.trigger('unfreeze', { key }) - } - - if (this.isAsync()) { - this.freeze() - const onProgress = options.progress - this.updateViewsAsync({ - ...options, - progress: ({ done, current, total }) => { - if (onProgress) { - FunctionExt.call(onProgress, this.graph, { done, current, total }) - } - - // sort views after async render - if (done) { - callback() - } - }, - }) - } else { - this.updateViews(options) - callback() - } - } - - isAsync() { - return !!this.options.async - } - - setAsync(async: boolean) { - this.options.async = async - } - - protected onRemove() { - this.freeze() - this.removeViews() - } - - protected resetViews(cells: Cell[] = [], options: any = {}) { - this.resetUpdates() - this.removeViews() - this.freeze({ key: 'reset' }) - for (let i = 0, n = cells.length; i < n; i += 1) { - this.renderView(cells[i], options) - } - this.unfreeze({ key: 'reset' }) - this.sortViews() - } - - protected removeView(cell: Cell) { - const view = this.views[cell.id] - if (view) { - const cid = view.cid - const updates = this.updates - const mounted = updates.mounted - const unmounted = updates.unmounted - view.remove() - delete this.views[cell.id] - delete mounted[cid] - delete unmounted[cid] - } - return view - } - - protected removeViews() { - if (this.views) { - Object.keys(this.views).forEach((id) => { - const view = this.views[id] - if (view) { - this.removeView(view.cell) - } - }) - } - this.views = {} - } - - protected renderView(cell: Cell, options: any = {}) { - const id = cell.id - const views = this.views - let flag = 0 - let view = views[id] - - if (!cell.isVisible()) { - return - } - - if (cell.isEdge()) { - if ( - !this.isEdgeTerminalVisible(cell, 'source') || - !this.isEdgeTerminalVisible(cell, 'target') - ) { - return - } - } - - if (view) { - flag = Renderer.FLAG_INSERT - } else { - const tmp = this.graph.hook.createCellView(cell) - if (tmp) { - view = views[cell.id] = tmp - view.graph = this.graph - flag = this.registerUnmountedView(view) | view.getBootstrapFlag() - } - } - - if (view) { - this.requestViewUpdate(view, flag, view.priority, options) - } - } - - protected isExactSorting() { - return this.options.sorting === 'exact' - } - - sortViews() { - if (!this.isExactSorting()) { - return - } - - if (this.isFrozen()) { - // sort views once unfrozen - this.updates.sort = true - return - } - - this.sortViewsExact() - } - - protected sortElements( - elems: Element[], - comparator: (a: Element, b: Element) => number, - ) { - // Highly inspired by the jquery.sortElements plugin by Padolsey. - // See http://james.padolsey.com/javascript/sorting-elements-with-jquery/. - - const placements = elems.map((elem) => { - const parentNode = elem.parentNode! - // Since the element itself will change position, we have - // to have some way of storing it's original position in - // the DOM. The easiest way is to have a 'flag' node: - const nextSibling = parentNode.insertBefore( - document.createTextNode(''), - elem.nextSibling, - ) - - return (targetNode: Element) => { - if (parentNode === targetNode) { - throw new Error( - "You can't sort elements if any one is a descendant of another.", - ) - } - - // Insert before flag - parentNode.insertBefore(targetNode, nextSibling) - // Remove flag - parentNode.removeChild(nextSibling) - } - }) - - elems.sort(comparator).forEach((elem, index) => placements[index](elem)) - } - - sortViewsExact() { - // const elems = this.view.stage.querySelectorAll('[data-cell-id]') - // const length = elems.length - // const cells = [] - // for (let i = 0; i < length; i++) { - // const cell = this.model.getCell(elems[i].getAttribute('data-cell-id') || '') - // cells.push({ - // id: cell.id, - // zIndex: cell.getZIndex() || 0, - // elem: elems[i], - // }) - // } - // const sortedCells = [...cells].sort((cell1, cell2) => cell1.zIndex - cell2.zIndex) - // const moves = ArrayExt.diff(cells, sortedCells, 'zIndex').moves - - // if (moves && moves.length) { - // moves.forEach((move) => { - // if (move.type) { - // const elem = move.item.elem as Element - // const parentNode = elem.parentNode - // const index = move.index - // if (parentNode) { - // if (index === length - 1) { - // parentNode.appendChild(elem) - // } else if (index < length - 1) { - // parentNode.insertBefore(elem, elems[index + 1]) - // } - // } - // } - // }) - // } - - // Run insertion sort algorithm in order to efficiently sort DOM - // elements according to their associated cell `zIndex` attribute. - const elems = this.view - .$(this.view.stage) - .children('[data-cell-id]') - .toArray() as Element[] - const model = this.model - this.sortElements(elems, (a, b) => { - const cellA = model.getCell(a.getAttribute('data-cell-id') || '') - const cellB = model.getCell(b.getAttribute('data-cell-id') || '') - const z1 = cellA.getZIndex() || 0 - const z2 = cellB.getZIndex() || 0 - return z1 === z2 ? 0 : z1 < z2 ? -1 : 1 - }) - } - - protected addZPivot(zIndex = 0) { - if (this.zPivots == null) { - this.zPivots = {} - } - - const pivots = this.zPivots - let pivot = pivots[zIndex] - if (pivot) { - return pivot - } - - pivot = pivots[zIndex] = document.createComment(`z-index:${zIndex + 1}`) - let neighborZ = -Infinity - // eslint-disable-next-line - for (const key in pivots) { - const currentZ = +key - if (currentZ < zIndex && currentZ > neighborZ) { - neighborZ = currentZ - if (neighborZ === zIndex - 1) { - continue - } - } - } - - const layer = this.view.stage - if (neighborZ !== -Infinity) { - const neighborPivot = pivots[neighborZ] - layer.insertBefore(pivot, neighborPivot.nextSibling) - } else { - layer.insertBefore(pivot, layer.firstChild) - } - return pivot - } - - protected removeZPivots() { - if (this.zPivots) { - Object.keys(this.zPivots).forEach((z) => { - const elem = this.zPivots[z] - if (elem && elem.parentNode) { - elem.parentNode.removeChild(elem) - } - }) - } - this.zPivots = {} - } - - insertView(view: CellView) { - const stage = this.view.stage - switch (this.options.sorting) { - case 'approx': { - const zIndex = view.cell.getZIndex() - const pivot = this.addZPivot(zIndex) - stage.insertBefore(view.container, pivot) - break - } - case 'exact': - default: - stage.appendChild(view.container) - break - } - } - - findViewByCell(cellId: string | number): CellView | null - findViewByCell(cell: Cell | null): CellView | null - findViewByCell( - cell: Cell | string | number | null | undefined, - ): CellView | null { - if (cell == null) { - return null - } - const id = Cell.isCell(cell) ? cell.id : cell - return this.views[id] - } - - findViewByElem(elem: string | JQuery | Element | undefined | null) { - if (elem == null) { - return null - } - - const target = - typeof elem === 'string' - ? this.view.stage.querySelector(elem) - : elem instanceof Element - ? elem - : elem[0] - - if (target) { - const id = this.view.findAttr('data-cell-id', target) - if (id) { - return this.views[id] - } - } - - return null - } - - findViewsFromPoint(p: Point.PointLike) { - const ref = { x: p.x, y: p.y } - return this.model - .getCells() - .map((cell) => this.findViewByCell(cell)) - .filter((view) => { - if (view != null) { - return Dom.getBBox(view.container as SVGElement, { - target: this.view.stage, - }).containsPoint(ref) - } - return false - }) as CellView[] - } - - findEdgeViewsInArea( - rect: Rectangle.RectangleLike, - options: Renderer.FindViewsInAreaOptions = {}, - ) { - const area = Rectangle.create(rect) - return this.model - .getEdges() - .map((edge) => this.findViewByCell(edge)) - .filter((view) => { - if (view) { - const bbox = Dom.getBBox(view.container as SVGElement, { - target: this.view.stage, - }) - if (bbox.width === 0) { - bbox.inflate(1, 0) - } else if (bbox.height === 0) { - bbox.inflate(0, 1) - } - return options.strict - ? area.containsRect(bbox) - : area.isIntersectWithRect(bbox) - } - return false - }) as CellView[] - } - - findViewsInArea( - rect: Rectangle.RectangleLike, - options: Renderer.FindViewsInAreaOptions = {}, - ) { - const area = Rectangle.create(rect) - return this.model - .getNodes() - .map((node) => this.findViewByCell(node)) - .filter((view) => { - if (view) { - const bbox = Dom.getBBox(view.container as SVGElement, { - target: this.view.stage, - }) - return options.strict - ? area.containsRect(bbox) - : area.isIntersectWithRect(bbox) - } - return false - }) as CellView[] - } - - @Base.dispose() - dispose() { - this.resetUpdates() - this.stopListening() - } -} - -export namespace Renderer { - export interface Updates { - priorities: KeyValue[] - mounted: KeyValue - unmounted: KeyValue - mountedCids: string[] - unmountedCids: string[] - animationId: number | null - count: number - sort: boolean - - /** - * The last frozen state of graph. - */ - frozen: boolean - /** - * The current freeze key of graph. - */ - freezeKey: string | null - } - - export type CheckViewFn = ( - this: Graph, - args: { - view: CellView - unmounted: boolean - }, - ) => boolean - - export interface CheckViewOptions { - /** - * Callback function to determine whether a given view - * should be added to the DOM. - */ - checkView?: CheckViewFn - } - - export interface UpdateViewOptions extends CheckViewOptions { - /** - * For async graph, how many views should there be per - * one asynchronous process? - */ - batchSize?: number - } - - export interface RequestViewUpdateOptions - extends UpdateViewOptions, - Cell.SetOptions { - async?: boolean - } - - export interface UpdateViewsAsyncOptions extends UpdateViewOptions { - before?: (this: Graph, graph: Graph) => void - after?: (this: Graph, graph: Graph) => void - /** - * Callback function that is called whenever a batch is - * finished processing. - */ - progress?: ( - this: Graph, - args: { done: boolean; current: number; total: number }, - ) => void - } - - export interface FreezeOptions { - key?: string - } - - export interface UnfreezeOptions - extends FreezeOptions, - UpdateViewsAsyncOptions {} - - export interface FindViewsInAreaOptions { - strict?: boolean - } -} - -export namespace Renderer { - export const FLAG_INSERT = 1 << 30 - export const FLAG_REMOVE = 1 << 29 - export const MOUNT_BATCH_SIZE = 1000 - export const UPDATE_BATCH_SIZE = 1000 - export const MIN_PRIORITY = 2 - export const SORT_DELAYING_BATCHES: Model.BatchName[] = [ - 'add', - 'to-front', - 'to-back', - ] - export const UPDATE_DELAYING_BATCHES: Model.BatchName[] = ['translate'] -} diff --git a/packages/x6/src/graph/scroller.ts b/packages/x6/src/graph/scroller.ts deleted file mode 100644 index 6fab40fc80a..00000000000 --- a/packages/x6/src/graph/scroller.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { Dom } from '../util' -import { ModifierKey } from '../types' -import { Scroller } from '../addon/scroller' -import { Base } from './base' - -export class ScrollerManager extends Base { - public widget: Scroller | null - - protected get widgetOptions() { - return this.options.scroller - } - - get pannable() { - if (this.widgetOptions) { - if (typeof this.widgetOptions.pannable === 'object') { - return this.widgetOptions.pannable.enabled - } - return !!this.widgetOptions.pannable - } - return false - } - - protected init() { - this.widget = this.graph.hook.createScroller() - this.startListening() - this.updateClassName() - if (this.widget) { - this.widget.center() - } - } - - protected startListening() { - let eventTypes = [] - const pannable = this.widgetOptions.pannable - if (typeof pannable === 'object') { - eventTypes = pannable.eventTypes || [] - } else { - eventTypes = ['leftMouseDown'] - } - if (eventTypes.includes('leftMouseDown')) { - this.graph.on('blank:mousedown', this.preparePanning, this) - this.graph.on('node:unhandled:mousedown', this.preparePanning, this) - this.graph.on('edge:unhandled:mousedown', this.preparePanning, this) - } - if (eventTypes.includes('rightMouseDown')) { - this.onRightMouseDown = this.onRightMouseDown.bind(this) - this.view.$(this.widget!.container).on('mousedown', this.onRightMouseDown) - } - } - - protected stopListening() { - let eventTypes = [] - const pannable = this.widgetOptions.pannable - if (typeof pannable === 'object') { - eventTypes = pannable.eventTypes || [] - } else { - eventTypes = ['leftMouseDown'] - } - if (eventTypes.includes('leftMouseDown')) { - this.graph.off('blank:mousedown', this.preparePanning, this) - this.graph.off('node:unhandled:mousedown', this.preparePanning, this) - this.graph.off('edge:unhandled:mousedown', this.preparePanning, this) - } - if (eventTypes.includes('rightMouseDown')) { - this.view - .$(this.widget!.container) - .off('mousedown', this.onRightMouseDown) - } - } - - protected onRightMouseDown(e: JQuery.MouseDownEvent) { - if (e.button === 2 && this.allowPanning(e, true) && this.widget) { - this.updateClassName(true) - this.widget.startPanning(e) - this.widget.once('pan:stop', () => this.updateClassName(false)) - } - } - - protected preparePanning({ e }: { e: JQuery.MouseDownEvent }) { - if (this.widget) { - if ( - this.allowPanning(e, true) || - (this.allowPanning(e) && !this.graph.selection.allowRubberband(e, true)) - ) { - this.updateClassName(true) - this.widget.startPanning(e) - this.widget.once('pan:stop', () => this.updateClassName(false)) - } - } - } - - allowPanning(e: JQuery.MouseDownEvent, strict?: boolean) { - return ( - this.widget && - this.pannable && - ModifierKey.isMatch(e, this.widgetOptions.modifiers, strict) && - this.graph.hook.allowPanning(e) - ) - } - - protected updateClassName(isPanning?: boolean) { - if (this.widget == null) { - return - } - const container = this.widget.container! - const pannable = this.view.prefixClassName('graph-scroller-pannable') - if (this.pannable) { - Dom.addClass(container, pannable) - container.dataset.panning = (!!isPanning).toString() // Use dataset to control scroller panning style to avoid reflow caused by changing classList - } else { - Dom.removeClass(container, pannable) - } - } - - enablePanning() { - if (!this.pannable) { - this.widgetOptions.pannable = true - this.updateClassName() - - // if ( - // ModifierKey.equals( - // this.graph.options.scroller.modifiers, - // this.graph.options.selecting.modifiers, - // ) - // ) { - // this.graph.selection.disableRubberband() - // } - } - } - - disablePanning() { - if (this.pannable) { - this.widgetOptions.pannable = false - this.updateClassName() - } - } - - lock() { - if (this.widget) { - this.widget.lock() - } - } - - unlock() { - if (this.widget) { - this.widget.unlock() - } - } - - update() { - if (this.widget) { - this.widget.update() - } - } - - enableAutoResize() { - if (this.widget) { - this.widget.enableAutoResize() - } - } - - disableAutoResize() { - if (this.widget) { - this.widget.disableAutoResize() - } - } - - resize(width?: number, height?: number) { - if (this.widget) { - this.widget.resize(width, height) - } - } - - @Base.dispose() - dispose() { - if (this.widget) { - this.widget.dispose() - } - this.stopListening() - } -} - -export namespace ScrollerManager { - type EventType = 'leftMouseDown' | 'rightMouseDown' - export interface Options extends Scroller.CommonOptions { - enabled?: boolean - pannable?: boolean | { enabled: boolean; eventTypes: EventType[] } - /** - * alt, ctrl, shift, meta - */ - modifiers?: string | ModifierKey[] | null - } -} diff --git a/packages/x6/src/graph/selection.ts b/packages/x6/src/graph/selection.ts deleted file mode 100644 index a9462db0152..00000000000 --- a/packages/x6/src/graph/selection.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { ModifierKey } from '../types' -import { Selection } from '../addon/selection' -import { Cell } from '../model/cell' -import { EventArgs } from './events' -import { Base } from './base' - -export class SelectionManager extends Base { - public widget: Selection - private movedMap = new WeakMap() - private unselectMap = new WeakMap() - - protected get widgetOptions() { - return this.options.selecting - } - - get rubberbandDisabled() { - return ( - this.widgetOptions.enabled !== true || - this.widgetOptions.rubberband !== true - ) - } - - public get disabled() { - return this.widgetOptions.enabled !== true - } - - public get length() { - return this.widget.length - } - - public get cells() { - return this.widget.cells - } - - protected init() { - this.widget = this.graph.hook.createSelection() - this.startListening() - } - - protected startListening() { - this.graph.on('blank:mousedown', this.onBlankMouseDown, this) - this.graph.on('blank:click', this.onBlankClick, this) - this.graph.on('cell:mousemove', this.onCellMouseMove, this) - this.graph.on('cell:mouseup', this.onCellMouseUp, this) - this.widget.on('box:mousedown', this.onBoxMouseDown, this) - } - - protected stopListening() { - this.graph.off('blank:mousedown', this.onBlankMouseDown, this) - this.graph.off('blank:click', this.onBlankClick, this) - this.graph.off('cell:mousemove', this.onCellMouseMove, this) - this.graph.off('cell:mouseup', this.onCellMouseUp, this) - this.widget.off('box:mousedown', this.onBoxMouseDown, this) - } - - protected onBlankMouseDown({ e }: EventArgs['blank:mousedown']) { - if ( - this.allowRubberband(e, true) || - (this.allowRubberband(e) && - !this.graph.scroller.allowPanning(e, true) && - !this.graph.panning.allowPanning(e, true)) - ) { - this.startRubberband(e) - } - } - - protected onBlankClick() { - this.clean() - } - - allowRubberband(e: JQuery.MouseDownEvent, strict?: boolean) { - return ( - !this.rubberbandDisabled && - ModifierKey.isMatch(e, this.widgetOptions.modifiers, strict) && - this.graph.hook.allowRubberband(e) - ) - } - - protected onCellMouseMove({ cell }: EventArgs['cell:mousemove']) { - this.movedMap.set(cell, true) - } - - protected onCellMouseUp({ e, cell }: EventArgs['cell:mouseup']) { - const options = this.widgetOptions - let disabled = this.disabled - if (!disabled && this.movedMap.has(cell)) { - disabled = options.selectCellOnMoved === false - - if (!disabled) { - disabled = options.selectNodeOnMoved === false && cell.isNode() - } - - if (!disabled) { - disabled = options.selectEdgeOnMoved === false && cell.isEdge() - } - } - - if (!disabled) { - if (options.multiple === false || (!e.ctrlKey && !e.metaKey)) { - this.reset(cell) - } else if (this.unselectMap.has(cell)) { - this.unselectMap.delete(cell) - } else if (this.isSelected(cell)) { - this.unselect(cell) - } else { - this.select(cell) - } - } - - this.movedMap.delete(cell) - } - - protected onBoxMouseDown({ e, cell }: Selection.EventArgs['box:mousedown']) { - if (!this.disabled) { - if (this.widgetOptions.multiple !== false && (e.ctrlKey || e.metaKey)) { - this.unselect(cell) - this.unselectMap.set(cell, true) - } - } - } - - isEmpty() { - return this.length <= 0 - } - - isSelected(cell: Cell | string) { - return this.widget.isSelected(cell) - } - - protected getCells(cells: Cell | string | (Cell | string)[]) { - return (Array.isArray(cells) ? cells : [cells]) - .map((cell) => - typeof cell === 'string' ? this.graph.getCellById(cell) : cell, - ) - .filter((cell) => cell != null) - } - - select( - cells: Cell | string | (Cell | string)[], - options: Selection.AddOptions = {}, - ) { - const selected = this.getCells(cells) - if (selected.length) { - if (this.isMultiple()) { - this.widget.select(selected, options) - } else { - this.reset(selected.slice(0, 1), options) - } - } - return this - } - - unselect( - cells: Cell | string | (Cell | string)[], - options: Selection.RemoveOptions = {}, - ) { - this.widget.unselect(this.getCells(cells), options) - return this - } - - reset( - cells?: Cell | string | (Cell | string)[], - options: Selection.SetOptions = {}, - ) { - this.widget.reset(cells ? this.getCells(cells) : [], options) - return this - } - - clean(options: Selection.SetOptions = {}) { - this.widget.clean(options) - return this - } - - enable() { - if (this.disabled) { - this.widgetOptions.enabled = true - } - return this - } - - disable() { - if (!this.disabled) { - this.widgetOptions.enabled = false - } - return this - } - - startRubberband(e: JQuery.MouseDownEvent) { - if (!this.rubberbandDisabled) { - this.widget.startSelecting(e) - } - return this - } - - enableRubberband() { - if (this.rubberbandDisabled) { - this.widgetOptions.rubberband = true - // if ( - // ModifierKey.equals( - // this.graph.options.scroller.modifiers, - // this.graph.options.selecting.modifiers, - // ) - // ) { - // this.graph.scroller.disablePanning() - // } - } - return this - } - - disableRubberband() { - if (!this.rubberbandDisabled) { - this.widgetOptions.rubberband = false - } - return this - } - - isMultiple() { - return this.widgetOptions.multiple !== false - } - - enableMultiple() { - this.widgetOptions.multiple = true - return this - } - - disableMultiple() { - this.widgetOptions.multiple = false - return this - } - - setModifiers(modifiers?: string | ModifierKey[] | null) { - this.widgetOptions.modifiers = modifiers - return this - } - - setContent(content?: Selection.Content) { - this.widget.setContent(content) - return this - } - - setFilter(filter?: Selection.Filter) { - this.widget.setFilter(filter) - return this - } - - @Base.dispose() - dispose() { - this.stopListening() - this.widget.dispose() - } -} - -export namespace SelectionManager { - export interface Options extends Selection.CommonOptions { - enabled?: boolean - rubberband?: boolean - modifiers?: string | ModifierKey[] | null - multiple?: boolean - selectCellOnMoved?: boolean - selectNodeOnMoved?: boolean - selectEdgeOnMoved?: boolean - } - - export type Filter = Selection.Filter - export type Content = Selection.Content - - export type SetOptions = Selection.SetOptions - export type AddOptions = Selection.AddOptions - export type RemoveOptions = Selection.RemoveOptions -} diff --git a/packages/x6/src/graph/size.ts b/packages/x6/src/graph/size.ts deleted file mode 100644 index 5297b2d806b..00000000000 --- a/packages/x6/src/graph/size.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Base } from './base' -import { SizeSensor } from '../util' - -export class SizeManager extends Base { - protected hasScroller() { - return this.graph.scroller.widget != null - } - - protected getContainer() { - return this.hasScroller() - ? this.graph.scroller.widget!.container! - : this.graph.container - } - - protected init() { - const autoResize = this.options.autoResize - if (autoResize) { - const target = - typeof autoResize === 'boolean' - ? this.getContainer() - : (autoResize as Element) - - SizeSensor.bind(target, () => { - const container = this.getContainer() - // container is border-box - const width = container.offsetWidth - const height = container.offsetHeight - this.resize(width, height) - }) - } - } - - resize(width?: number, height?: number) { - if (this.hasScroller()) { - this.resizeScroller(width, height) - } else { - this.resizeGraph(width, height) - } - } - - resizeGraph(width?: number, height?: number) { - this.graph.transform.resize(width, height) - } - - resizeScroller(width?: number, height?: number) { - this.graph.scroller.resize(width, height) - } - - resizePage(width?: number, height?: number) { - const instance = this.graph.scroller.widget - if (instance) { - instance.updatePageSize(width, height) - } - } - - @Base.dispose() - dispose() { - SizeSensor.clear(this.getContainer()) - } -} diff --git a/packages/x6/src/graph/snapline.ts b/packages/x6/src/graph/snapline.ts deleted file mode 100644 index 6c39cf87f69..00000000000 --- a/packages/x6/src/graph/snapline.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Snapline } from '../addon/snapline' -import { Base } from './base' - -export class SnaplineManager extends Base { - public readonly widget: Snapline = this.graph.hook.createSnapline() - - @Base.dispose() - dispose() { - this.widget.dispose() - } -} - -export namespace SnaplineManager { - export type Filter = Snapline.Filter - - export interface Options extends Snapline.Options {} -} diff --git a/packages/x6/src/graph/transform.ts b/packages/x6/src/graph/transform.ts index d6168497e02..d4d34a96f91 100644 --- a/packages/x6/src/graph/transform.ts +++ b/packages/x6/src/graph/transform.ts @@ -1,14 +1,10 @@ -import { Dom, NumberExt } from '../util' -import { Point, Rectangle } from '../geometry' -import { Transform } from '../addon/transform' -import { Node } from '../model/node' -import { Cell } from '../model/cell' -import { EventArgs } from './events' +import { Dom, NumberExt } from '@antv/x6-common' +import { Point, Rectangle } from '@antv/x6-geometry' import { Base } from './base' +import { Util } from '../util' +import { Cell } from '../model' export class TransformManager extends Base { - protected widgets: Map = new Map() - protected viewportMatrix: DOMMatrix | null protected viewportTransformString: string | null @@ -21,55 +17,14 @@ export class TransformManager extends Base { return this.graph.view.viewport } - protected get isSelectionEnabled() { - return this.options.selecting.enabled === true + protected get stage() { + return this.graph.view.stage } protected init() { - this.startListening() this.resize() } - protected startListening() { - this.graph.on('node:mouseup', this.onNodeMouseUp, this) - this.graph.on('node:selected', this.onNodeSelected, this) - this.graph.on('node:unselected', this.onNodeUnSelected, this) - } - - protected stopListening() { - this.graph.off('node:mouseup', this.onNodeMouseUp, this) - this.graph.off('node:selected', this.onNodeSelected, this) - this.graph.off('node:unselected', this.onNodeUnSelected, this) - } - - protected onNodeMouseUp({ node }: EventArgs['node:mouseup']) { - if (!this.isSelectionEnabled) { - const widget = this.graph.hook.createTransform(node, { clearAll: true }) - if (widget) { - this.widgets.set(node, widget) - } - } - } - - protected onNodeSelected({ node }: EventArgs['node:selected']) { - if (this.isSelectionEnabled) { - const widget = this.graph.hook.createTransform(node, { clearAll: false }) - if (widget) { - this.widgets.set(node, widget) - } - } - } - - protected onNodeUnSelected({ node }: EventArgs['node:unselected']) { - if (this.isSelectionEnabled) { - const widget = this.widgets.get(node) - if (widget) { - widget.dispose() - } - this.widgets.delete(node) - } - } - /** * Returns the current transformation matrix of the graph. */ @@ -225,7 +180,7 @@ export class TransformManager extends Base { rotate(angle: number, cx?: number, cy?: number) { if (cx == null || cy == null) { - const bbox = Dom.getBBox(this.graph.view.stage) + const bbox = Util.getBBox(this.stage) cx = bbox.width / 2 // eslint-disable-line cy = bbox.height / 2 // eslint-disable-line } @@ -446,7 +401,7 @@ export class TransformManager extends Base { return this.model.getAllCellsBBox() || new Rectangle() } - return Dom.getBBox(this.graph.view.stage) + return Util.getBBox(this.stage) } getContentBBox(options: TransformManager.GetContentAreaOptions = {}) { @@ -584,13 +539,6 @@ export class TransformManager extends Base { const rect = this.graph.getContentArea(options) return this.positionRect(rect, pos) } - - @TransformManager.dispose() - dispose() { - this.widgets.forEach((widget) => widget.dispose()) - this.widgets.clear() - this.stopListening() - } } export namespace TransformManager { @@ -646,4 +594,10 @@ export namespace TransformManager { | 'bottom' | 'bottom-left' | 'left' + + export interface CenterOptions { + padding?: NumberExt.SideOptions + } + + export type PositionContentOptions = GetContentAreaOptions & CenterOptions } diff --git a/packages/x6/src/graph/view.ts b/packages/x6/src/graph/view.ts index f573d036f96..fce74233254 100644 --- a/packages/x6/src/graph/view.ts +++ b/packages/x6/src/graph/view.ts @@ -1,9 +1,8 @@ -import JQuery from 'jquery' -import { Dom, FunctionExt } from '../util' +import { Dom, FunctionExt } from '@antv/x6-common' import { Cell } from '../model' -import { Config } from '../global' +import { Config } from '../config' import { View, Markup, CellView } from '../view' -import { Graph } from './graph' +import { Graph } from '../graph' export class GraphView extends View { public readonly container: HTMLElement @@ -19,10 +18,6 @@ export class GraphView extends View { private restore: () => void - protected get model() { - return this.graph.model - } - protected get options() { return this.graph.options } @@ -43,9 +38,8 @@ export class GraphView extends View { this.container = this.options.container this.restore = GraphView.snapshoot(this.container) - this.$(this.container) - .addClass(this.prefixClassName('graph')) - .append(fragment) + Dom.addClass(this.container, this.prefixClassName('graph')) + Dom.append(this.container, fragment) this.delegateEvents() } @@ -60,7 +54,7 @@ export class GraphView extends View { * Guard the specified event. If the event is not interesting, it * returns `true`, otherwise returns `false`. */ - guard(e: JQuery.TriggeredEvent, view?: CellView | null) { + guard(e: Dom.EventObject, view?: CellView | null) { // handled as `contextmenu` type if (e.type === 'mousedown' && e.button === 2) { return true @@ -81,7 +75,7 @@ export class GraphView extends View { if ( this.svg === e.target || this.container === e.target || - JQuery.contains(this.svg, e.target) + this.svg.contains(e.target) ) { return false } @@ -90,10 +84,10 @@ export class GraphView extends View { } protected findView(elem: Element) { - return this.graph.renderer.findViewByElem(elem) + return this.graph.findViewByElem(elem) } - protected onDblClick(evt: JQuery.DoubleClickEvent) { + protected onDblClick(evt: Dom.DoubleClickEvent) { if (this.options.preventDefaultDblClick) { evt.preventDefault() } @@ -118,7 +112,7 @@ export class GraphView extends View { } } - protected onClick(evt: JQuery.ClickEvent) { + protected onClick(evt: Dom.ClickEvent) { if (this.getMouseMovedCount(evt) <= this.options.clickThreshold) { const e = this.normalizeEvent(evt) const view = this.findView(e.target) @@ -139,7 +133,7 @@ export class GraphView extends View { } } - protected onContextMenu(evt: JQuery.ContextMenuEvent) { + protected onContextMenu(evt: Dom.ContextMenuEvent) { if (this.options.preventDefaultContextMenu) { evt.preventDefault() } @@ -163,7 +157,7 @@ export class GraphView extends View { } } - delegateDragEvents(e: JQuery.MouseDownEvent, view: CellView | null) { + delegateDragEvents(e: Dom.MouseDownEvent, view: CellView | null) { if (e.data == null) { e.data = {} } @@ -180,12 +174,12 @@ export class GraphView extends View { this.undelegateEvents() } - getMouseMovedCount(e: JQuery.TriggeredEvent) { + getMouseMovedCount(e: Dom.EventObject) { const data = this.getEventData(e) return data.mouseMovedCount || 0 } - protected onMouseDown(evt: JQuery.MouseDownEvent) { + protected onMouseDown(evt: Dom.MouseDownEvent) { const e = this.normalizeEvent(evt) const view = this.findView(e.target) if (this.guard(e, view)) { @@ -215,7 +209,7 @@ export class GraphView extends View { this.delegateDragEvents(e, view) } - protected onMouseMove(evt: JQuery.MouseMoveEvent) { + protected onMouseMove(evt: Dom.MouseMoveEvent) { const data = this.getEventData(evt) const startPosition = data.startPosition @@ -253,7 +247,7 @@ export class GraphView extends View { this.setEventData(e, data) } - protected onMouseUp(e: JQuery.MouseUpEvent) { + protected onMouseUp(e: Dom.MouseUpEvent) { this.undelegateDocumentEvents() const normalized = this.normalizeEvent(e) @@ -274,12 +268,11 @@ export class GraphView extends View { } if (!e.isPropagationStopped()) { - this.onClick( - JQuery.Event(e as any, { - type: 'click', - data: e.data, - }) as JQuery.ClickEvent, - ) + const ev = new Dom.EventObject(e as any, { + type: 'click', + data: e.data, + }) as Dom.ClickEvent + this.onClick(ev) } e.stopImmediatePropagation() @@ -287,7 +280,7 @@ export class GraphView extends View { this.delegateEvents() } - protected onMouseOver(evt: JQuery.MouseOverEvent) { + protected onMouseOver(evt: Dom.MouseOverEvent) { const e = this.normalizeEvent(evt) const view = this.findView(e.target) if (this.guard(e, view)) { @@ -305,7 +298,7 @@ export class GraphView extends View { } } - protected onMouseOut(evt: JQuery.MouseOutEvent) { + protected onMouseOut(evt: Dom.MouseOutEvent) { const e = this.normalizeEvent(evt) const view = this.findView(e.target) @@ -323,16 +316,14 @@ export class GraphView extends View { } } - protected onMouseEnter(evt: JQuery.MouseEnterEvent) { + protected onMouseEnter(evt: Dom.MouseEnterEvent) { const e = this.normalizeEvent(evt) const view = this.findView(e.target) if (this.guard(e, view)) { return } - const relatedView = this.graph.renderer.findViewByElem( - e.relatedTarget as Element, - ) + const relatedView = this.graph.findViewByElem(e.relatedTarget as Element) if (view) { if (relatedView === view) { // mouse moved from tool to view @@ -347,16 +338,14 @@ export class GraphView extends View { } } - protected onMouseLeave(evt: JQuery.MouseLeaveEvent) { + protected onMouseLeave(evt: Dom.MouseLeaveEvent) { const e = this.normalizeEvent(evt) const view = this.findView(e.target) if (this.guard(e, view)) { return } - const relatedView = this.graph.renderer.findViewByElem( - e.relatedTarget as Element, - ) + const relatedView = this.graph.findViewByElem(e.relatedTarget as Element) if (view) { if (relatedView === view) { @@ -372,7 +361,7 @@ export class GraphView extends View { } } - protected onMouseWheel(evt: JQuery.TriggeredEvent) { + protected onMouseWheel(evt: Dom.EventObject) { const e = this.normalizeEvent(evt) const view = this.findView(e.target) if (this.guard(e, view)) { @@ -401,7 +390,7 @@ export class GraphView extends View { } } - protected onCustomEvent(evt: JQuery.MouseDownEvent) { + protected onCustomEvent(evt: Dom.MouseDownEvent) { const elem = evt.currentTarget const event = elem.getAttribute('event') || elem.getAttribute('data-event') if (event) { @@ -421,7 +410,7 @@ export class GraphView extends View { } } - protected handleMagnetEvent( + protected handleMagnetEvent( evt: T, handler: ( this: Graph, @@ -458,19 +447,19 @@ export class GraphView extends View { } } - protected onMagnetMouseDown(e: JQuery.MouseDownEvent) { + protected onMagnetMouseDown(e: Dom.MouseDownEvent) { this.handleMagnetEvent(e, (view, e, magnet, x, y) => { view.onMagnetMouseDown(e, magnet, x, y) }) } - protected onMagnetDblClick(e: JQuery.DoubleClickEvent) { + protected onMagnetDblClick(e: Dom.DoubleClickEvent) { this.handleMagnetEvent(e, (view, e, magnet, x, y) => { view.onMagnetDblClick(e, magnet, x, y) }) } - protected onMagnetContextMenu(e: JQuery.ContextMenuEvent) { + protected onMagnetContextMenu(e: Dom.ContextMenuEvent) { if (this.options.preventDefaultContextMenu) { e.preventDefault() } @@ -479,7 +468,7 @@ export class GraphView extends View { }) } - protected onLabelMouseDown(evt: JQuery.MouseDownEvent) { + protected onLabelMouseDown(evt: Dom.MouseDownEvent) { const labelNode = evt.currentTarget const view = this.findView(labelNode) if (view) { diff --git a/packages/x6-next/src/graph/virtual-render.ts b/packages/x6/src/graph/virtual-render.ts similarity index 100% rename from packages/x6-next/src/graph/virtual-render.ts rename to packages/x6/src/graph/virtual-render.ts diff --git a/packages/x6/src/index.less b/packages/x6/src/index.less index f038d4064a3..f3259a1a009 100644 --- a/packages/x6/src/index.less +++ b/packages/x6/src/index.less @@ -1,17 +1,4 @@ @import './style/index'; -// addons -@import './addon/common/handle'; -@import './addon/dnd/index'; -@import './addon/halo/index'; -@import './addon/minimap/index'; -@import './addon/scroller/index'; -@import './addon/selection/index'; -@import './addon/snapline/index'; -@import './addon/stencil/index'; -@import './addon/transform/index'; -@import './addon/knob/index'; -@import './graph/print'; - // tools @import './registry/tool/editor'; diff --git a/packages/x6/src/index.ts b/packages/x6/src/index.ts index c82278e0a52..95db87f8c63 100644 --- a/packages/x6/src/index.ts +++ b/packages/x6/src/index.ts @@ -1,19 +1,10 @@ -import { Shape } from './shape' -import * as Addon from './addon' +import * as Shape from './shape' import * as Registry from './registry' -// start track -// ----------- -import './global/track' - -export * from './util' -export * from './common' -export * from './geometry' - export * from './model' export * from './view' export * from './graph' +export * from './config' +export * from './util' -export { Shape, Addon, Registry } - -export * from './global' +export { Shape, Registry } diff --git a/packages/x6/src/layout/force-directed.ts b/packages/x6/src/layout/force-directed.ts deleted file mode 100644 index a88e9e26622..00000000000 --- a/packages/x6/src/layout/force-directed.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { Point } from '../geometry' -import { Events } from '../common' -import { KeyValue } from '../types' -import { NumberExt } from '../util' -import { Cell } from '../model/cell' -import { Node } from '../model/node' -import { Edge } from '../model/edge' -import { Model } from '../model/model' - -export class ForceDirected extends Events { - t: number - - energy: number - - progress: number - - public readonly options: ForceDirected.Options - - protected edges: Edge[] - - protected nodes: Node[] - - protected nodeData: KeyValue - - protected edgeData: KeyValue - - get model() { - return this.options.model - } - - constructor(options: ForceDirected.Options) { - super() - - this.options = { - charge: 10, - edgeDistance: 10, - edgeStrength: 1, - ...options, - } - - this.nodes = this.model.getNodes() - this.edges = this.model.getEdges() - - this.t = 1 - this.energy = Infinity - this.progress = 0 - } - - start() { - const x = this.options.x - const y = this.options.y - const width = this.options.width - const height = this.options.height - - this.nodeData = {} - this.edgeData = {} - - this.nodes.forEach((node) => { - const posX = NumberExt.random(x, x + width) - const posY = NumberExt.random(y, y + height) - - node.position(posX, posY, { forceDirected: true }) - - this.nodeData[node.id] = { - charge: node.prop('charge') || this.options.charge, - weight: node.prop('weight') || 1, - x: posX, - y: posY, - px: posX, - py: posY, - fx: 0, - fy: 0, - } - }) - - this.edges.forEach((edge) => { - this.edgeData[edge.id] = { - source: edge.getSourceCell()!, - target: edge.getTargetCell()!, - strength: edge.prop('strength') || this.options.edgeStrength, - distance: edge.prop('distance') || this.options.edgeDistance, - } - }) - } - - step() { - if (0.99 * this.t < 0.005) { - return this.notifyEnd() - } - - this.energy = 0 - - let xBefore = 0 - let yBefore = 0 - let xAfter = 0 - let yAfter = 0 - - const nodeCount = this.nodes.length - const edgeCount = this.edges.length - - for (let i = 0; i < nodeCount - 1; i += 1) { - const v = this.nodeData[this.nodes[i].id] - xBefore += v.x - yBefore += v.y - - for (let j = i + 1; j < nodeCount; j += 1) { - const u = this.nodeData[this.nodes[j].id] - const dx = u.x - v.x - const dy = u.y - v.y - const distanceSquared = dx * dx + dy * dy - // const distance = Math.sqrt(distanceSquared) - const fr = (this.t * v.charge) / distanceSquared - const fx = fr * dx - const fy = fr * dy - - v.fx -= fx - v.fy -= fy - u.fx += fx - u.fy += fy - - this.energy += fx * fx + fy * fy - } - } - - // Add the last node positions as it couldn't be done in the loops above. - const last = this.nodeData[this.nodes[nodeCount - 1].id] - xBefore += last.x - yBefore += last.y - - // Calculate attractive forces. - for (let i = 0; i < edgeCount; i += 1) { - const a = this.edgeData[this.edges[i].id] - const v = this.nodeData[a.source.id] - const u = this.nodeData[a.target.id] - - const dx = u.x - v.x - const dy = u.y - v.y - const distanceSquared = dx * dx + dy * dy - const distance = Math.sqrt(distanceSquared) - const fa = (this.t * a.strength * (distance - a.distance)) / distance - const fx = fa * dx - const fy = fa * dy - - const k = v.weight / (v.weight + u.weight) - - v.x += fx * (1 - k) - v.y += fy * (1 - k) - u.x -= fx * k - u.y -= fy * k - - this.energy += fx * fx + fy * fy - } - - const x = this.options.x - const y = this.options.y - const w = this.options.width - const h = this.options.height - const gravityCenter = this.options.gravityCenter - - const gravity = 0.1 - const energyBefore = this.energy - - // Set positions on nodes. - for (let i = 0; i < nodeCount; i += 1) { - const node = this.nodes[i] - const data = this.nodeData[node.id] - const pos = { - x: data.x, - y: data.y, - } - - if (gravityCenter) { - pos.x += (gravityCenter.x - pos.x) * this.t * gravity - pos.y += (gravityCenter.y - pos.y) * this.t * gravity - } - - pos.x += data.fx - pos.y += data.fy - - // Make sure positions don't go out of the graph area. - pos.x = Math.max(x, Math.min(x + w, pos.x)) - pos.y = Math.max(y, Math.min(x + h, pos.y)) - - // Position Verlet integration. - const friction = 0.9 - pos.x += friction * (data.px - pos.x) - pos.y += friction * (data.py - pos.y) - - data.px = pos.x - data.py = pos.y - - data.fx = 0 - data.fy = 0 - - data.x = pos.x - data.y = pos.y - - xAfter += data.x - yAfter += data.y - - node.setPosition(pos, { forceDirected: true }) - } - - this.t = this.cool(this.t, this.energy, energyBefore) - - // If the global distance hasn't change much, the layout converged - // and therefore trigger the `end` event. - const gdx = xBefore - xAfter - const gdy = yBefore - yAfter - const gd = Math.sqrt(gdx * gdx + gdy * gdy) - if (gd < 1) { - this.notifyEnd() - } - } - - protected cool(t: number, energy: number, energyBefore: number) { - if (energy < energyBefore) { - this.progress += 1 - if (this.progress >= 5) { - this.progress = 0 - return t / 0.99 // Warm up. - } - } else { - this.progress = 0 - return t * 0.99 // Cool down. - } - - return t // Keep the same temperature. - } - - protected notifyEnd() { - this.trigger('end') - } -} - -export namespace ForceDirected { - export interface Options { - model: Model - x: number - y: number - width: number - height: number - charge?: number - edgeDistance?: number - edgeStrength?: number - gravityCenter?: Point.PointLike - } - - export interface NodeData { - x: number - y: number - px: number - py: number - fx: number - fy: number - charge: number - weight: number - } - - export interface EdgeData { - source: Cell - target: Cell - strength: number - distance: number - } -} diff --git a/packages/x6/src/layout/grid.ts b/packages/x6/src/layout/grid.ts deleted file mode 100644 index 34cee877e1e..00000000000 --- a/packages/x6/src/layout/grid.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { Node } from '../model/node' -import { Model } from '../model/model' - -export function grid(cells: Node[] | Model, options: GridLayout.Options = {}) { - const model = Model.isModel(cells) - ? cells - : new Model().resetCells(cells, { - sort: false, - dryrun: true, - }) - - const nodes = model.getNodes() - const columns = options.columns || 1 - const rows = Math.ceil(nodes.length / columns) - const dx = options.dx || 0 - const dy = options.dy || 0 - const centre = options.center !== false - const resizeToFit = options.resizeToFit === true - const marginX = options.marginX || 0 - const marginY = options.marginY || 0 - const columnWidths: number[] = [] - - let columnWidth = options.columnWidth - - if (columnWidth === 'compact') { - for (let j = 0; j < columns; j += 1) { - const items = GridLayout.getNodesInColumn(nodes, j, columns) - columnWidths.push(GridLayout.getMaxDim(items, 'width') + dx) - } - } else { - if (columnWidth == null || columnWidth === 'auto') { - columnWidth = GridLayout.getMaxDim(nodes, 'width') + dx - } - - for (let i = 0; i < columns; i += 1) { - columnWidths.push(columnWidth) - } - } - - const columnLefts = GridLayout.accumulate(columnWidths, marginX) - - const rowHeights: number[] = [] - let rowHeight = options.rowHeight - if (rowHeight === 'compact') { - for (let i = 0; i < rows; i += 1) { - const items = GridLayout.getNodesInRow(nodes, i, columns) - rowHeights.push(GridLayout.getMaxDim(items, 'height') + dy) - } - } else { - if (rowHeight == null || rowHeight === 'auto') { - rowHeight = GridLayout.getMaxDim(nodes, 'height') + dy - } - - for (let i = 0; i < rows; i += 1) { - rowHeights.push(rowHeight) - } - } - const rowTops = GridLayout.accumulate(rowHeights, marginY) - - model.startBatch('layout') - - nodes.forEach((node, index) => { - const rowIndex = index % columns - const columnIndex = Math.floor(index / columns) - const columnWidth = columnWidths[rowIndex] - const rowHeight = rowHeights[columnIndex] - - let cx = 0 - let cy = 0 - let size = node.getSize() - - if (resizeToFit) { - let width = columnWidth - 2 * dx - let height = rowHeight - 2 * dy - const calcHeight = size.height * (size.width ? width / size.width : 1) - const calcWidth = size.width * (size.height ? height / size.height : 1) - if (rowHeight < calcHeight) { - width = calcWidth - } else { - height = calcHeight - } - size = { - width, - height, - } - node.setSize(size, options) - } - - if (centre) { - cx = (columnWidth - size.width) / 2 - cy = (rowHeight - size.height) / 2 - } - - node.position( - columnLefts[rowIndex] + dx + cx, - rowTops[columnIndex] + dy + cy, - options, - ) - }) - - model.stopBatch('layout') -} - -namespace GridLayout { - export interface Options extends Node.SetPositionOptions { - columns?: number - columnWidth?: number | 'auto' | 'compact' - rowHeight?: number | 'auto' | 'compact' - dx?: number - dy?: number - marginX?: number - marginY?: number - /** - * Positions the elements in the center of a grid cell. - * - * Default: true - */ - center?: boolean - /** - * Resizes the elements to fit a grid cell, preserving the aspect ratio. - * - * Default: false - */ - resizeToFit?: boolean - } - - export function getMaxDim(nodes: Node[], name: 'width' | 'height') { - return nodes.reduce((memo, node) => Math.max(node.getSize()[name], memo), 0) - } - - export function getNodesInRow( - nodes: Node[], - rowIndex: number, - columnCount: number, - ) { - const res: Node[] = [] - for (let i = columnCount * rowIndex, ii = i + columnCount; i < ii; i += 1) { - res.push(nodes[i]) - } - return res - } - - export function getNodesInColumn( - nodes: Node[], - columnIndex: number, - columnCount: number, - ) { - const res: Node[] = [] - for (let i = columnIndex, ii = nodes.length; i < ii; i += columnCount) { - res.push(nodes[i]) - } - return res - } - - export function accumulate(items: number[], start: number) { - return items.reduce( - (memo, item, i) => { - memo.push(memo[i] + item) - return memo - }, - [start || 0], - ) - } -} diff --git a/packages/x6/src/layout/index.ts b/packages/x6/src/layout/index.ts deleted file mode 100644 index f486bbc9340..00000000000 --- a/packages/x6/src/layout/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import * as Layout from './main' - -export { Layout } diff --git a/packages/x6/src/layout/main.ts b/packages/x6/src/layout/main.ts deleted file mode 100644 index bbcfe043cd9..00000000000 --- a/packages/x6/src/layout/main.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './grid' -export * from './force-directed' diff --git a/packages/x6/src/model/animation.ts b/packages/x6/src/model/animation.ts index 044d111e28c..4d126aa196e 100644 --- a/packages/x6/src/model/animation.ts +++ b/packages/x6/src/model/animation.ts @@ -1,6 +1,4 @@ -import { KeyValue } from '../types' -import { ObjectExt, Dom } from '../util' -import { Timing, Interp } from '../common' +import { ObjectExt, Dom, KeyValue, Timing, Interp } from '@antv/x6-common' import { Cell } from './cell' export class Animation { @@ -59,8 +57,6 @@ export class Animation { } if (progress === 1) { - // TODO: remove in the next major version - this.cell.notify('transition:end', this.getArgs(key)) this.cell.notify('transition:complete', this.getArgs(key)) options.complete && options.complete(this.getArgs(key)) @@ -75,8 +71,6 @@ export class Animation { this.cache[key] = { startValue, targetValue, options: localOptions } this.ids[key] = Dom.requestAnimationFrame(iterate) - // TODO: remove in the next major version - this.cell.notify('transition:begin', this.getArgs(key)) this.cell.notify('transition:start', this.getArgs(key)) options.start && options.start(this.getArgs(key)) }, options.delay) diff --git a/packages/x6/src/model/cell.ts b/packages/x6/src/model/cell.ts index cbf98c0ba4f..8f82e32c545 100644 --- a/packages/x6/src/model/cell.ts +++ b/packages/x6/src/model/cell.ts @@ -1,20 +1,25 @@ /* eslint-disable no-underscore-dangle */ +import { + ArrayExt, + StringExt, + ObjectExt, + FunctionExt, + KeyValue, + Size, + Basecoat, +} from '@antv/x6-common' +import { Rectangle, Point } from '@antv/x6-geometry' import { NonUndefined } from 'utility-types' -import { ArrayExt, StringExt, ObjectExt, FunctionExt } from '../util' -import { Rectangle, Point } from '../geometry' -import { KeyValue, Size } from '../types' -import { Knob } from '../addon/knob' -import { Basecoat } from '../common' import { Attr } from '../registry' -import { Markup, CellView } from '../view' -import { Graph } from '../graph' import { Model } from './model' -import { Animation } from './animation' import { PortManager } from './port' import { Store } from './store' -import { Node } from './node' import { Edge } from './edge' +import { Animation } from './animation' +import { CellView, Markup } from '../view' +import { Node } from './node' +import { Graph } from '../graph' export class Cell< Properties extends Cell.Properties = Cell.Properties, @@ -939,9 +944,8 @@ export class Cell< } addTo(model: Model, options?: Cell.SetOptions): this - addTo(graph: Graph, options?: Cell.SetOptions): this addTo(parent: Cell, options?: Cell.SetOptions): this - addTo(target: Model | Graph | Cell, options: Cell.SetOptions = {}) { + addTo(target: Model | Cell, options: Cell.SetOptions = {}) { if (Cell.isCell(target)) { target.addChild(this, options) } else { @@ -1392,7 +1396,7 @@ export class Cell< } findView(graph: Graph): CellView | null { - return graph.renderer.findViewByCell(this) + return graph.findViewByCell(this) } // #endregion @@ -1458,7 +1462,6 @@ export namespace Cell { zIndex?: number visible?: boolean data?: any - knob?: Knob.Metadata | Knob.Metadata[] } export interface Defaults extends Common {} @@ -1613,8 +1616,6 @@ export namespace Cell { 'change:vertices': EdgeChangeArgs 'change:labels': EdgeChangeArgs 'change:defaultLabel': EdgeChangeArgs - 'change:toolMarkup': EdgeChangeArgs - 'change:doubleToolMarkup': EdgeChangeArgs 'change:vertexMarkup': EdgeChangeArgs 'change:arrowheadMarkup': EdgeChangeArgs 'vertexs:added': { diff --git a/packages/x6/src/model/collection.ts b/packages/x6/src/model/collection.ts index a09f89c8c17..de2160f44e0 100644 --- a/packages/x6/src/model/collection.ts +++ b/packages/x6/src/model/collection.ts @@ -1,5 +1,4 @@ -import { ArrayExt } from '../util' -import { Basecoat } from '../common' +import { ArrayExt, Basecoat } from '@antv/x6-common' import { Cell } from './cell' import { Node } from './node' import { Edge } from './edge' @@ -419,8 +418,6 @@ export namespace Collection { 'cell:change:vertices': Cell.EventArgs['change:vertices'] 'cell:change:labels': Cell.EventArgs['change:labels'] 'cell:change:defaultLabel': Cell.EventArgs['change:defaultLabel'] - 'cell:change:toolMarkup': Cell.EventArgs['change:toolMarkup'] - 'cell:change:doubleToolMarkup': Cell.EventArgs['change:doubleToolMarkup'] 'cell:change:vertexMarkup': Cell.EventArgs['change:vertexMarkup'] 'cell:change:arrowheadMarkup': Cell.EventArgs['change:arrowheadMarkup'] 'cell:vertexs:added': Cell.EventArgs['vertexs:added'] @@ -527,10 +524,6 @@ export namespace Collection { 'edge:change:labels': EdgeEventCommonArgs & Cell.EventArgs['change:labels'] 'edge:change:defaultLabel': EdgeEventCommonArgs & Cell.EventArgs['change:defaultLabel'] - 'edge:change:toolMarkup': EdgeEventCommonArgs & - Cell.EventArgs['change:toolMarkup'] - 'edge:change:doubleToolMarkup': EdgeEventCommonArgs & - Cell.EventArgs['change:doubleToolMarkup'] 'edge:change:vertexMarkup': EdgeEventCommonArgs & Cell.EventArgs['change:vertexMarkup'] 'edge:change:arrowheadMarkup': EdgeEventCommonArgs & diff --git a/packages/x6/src/model/edge.ts b/packages/x6/src/model/edge.ts index 37a5c55d597..b9d7dc434e7 100644 --- a/packages/x6/src/model/edge.ts +++ b/packages/x6/src/model/edge.ts @@ -1,15 +1,12 @@ -import { Size, KeyValue } from '../types' -import { ObjectExt, StringExt } from '../util' -import { Point, Polyline } from '../geometry' +import { ObjectExt, StringExt, Registry, Size, KeyValue } from '@antv/x6-common' +import { Point, Polyline } from '@antv/x6-geometry' import { - Registry, Attr, Router, Connector, EdgeAnchor, NodeAnchor, ConnectionPoint, - ConnectionStrategy, } from '../registry' import { Markup } from '../view/markup' import { ShareRegistry } from './registry' @@ -430,45 +427,6 @@ export class Edge< // #endregion - // #region strategy - - get strategy() { - return this.getStrategy() - } - - set strategy(data: Edge.StrategyData | undefined) { - if (data == null) { - this.removeStrategy() - } else { - this.setStrategy(data) - } - } - - getStrategy() { - return this.store.get('strategy') - } - - setStrategy(name: string, args?: KeyValue, options?: Edge.SetOptions): this - setStrategy(strategy: Edge.StrategyData, options?: Edge.SetOptions): this - setStrategy( - name?: string | Edge.StrategyData, - args?: KeyValue | Edge.SetOptions, - options?: Edge.SetOptions, - ) { - if (typeof name === 'object') { - this.store.set('strategy', name, args) - } else { - this.store.set('strategy', { name, args }, options) - } - return this - } - - removeStrategy(options: Edge.SetOptions = {}) { - return this.store.remove('strategy', options) - } - - // #endregion - // #region labels getDefaultLabel(): Edge.Label { @@ -607,28 +565,6 @@ export class Edge< // #endregion // #region vertices - - get vertexMarkup() { - return this.getVertexMarkup() - } - - set vertexMarkup(markup: Markup) { - this.setVertexMarkup(markup) - } - - getDefaultVertexMarkup() { - return this.store.get('defaultVertexMarkup') || Markup.getEdgeVertexMarkup() - } - - getVertexMarkup() { - return this.store.get('vertexMarkup') || this.getDefaultVertexMarkup() - } - - setVertexMarkup(markup?: Markup, options: Edge.SetOptions = {}) { - this.store.set('vertexMarkup', Markup.clone(markup), options) - return this - } - get vertices() { return this.getVertices() } @@ -753,82 +689,6 @@ export class Edge< // #endregion - // #region toolMarkup - - get toolMarkup() { - return this.getToolMarkup() - } - - set toolMarkup(markup: Markup) { - this.setToolMarkup(markup) - } - - getDefaultToolMarkup() { - return this.store.get('defaultToolMarkup') || Markup.getEdgeToolMarkup() - } - - getToolMarkup() { - return this.store.get('toolMarkup') || this.getDefaultToolMarkup() - } - - setToolMarkup(markup?: Markup, options: Edge.SetOptions = {}) { - this.store.set('toolMarkup', markup, options) - return this - } - - get doubleToolMarkup() { - return this.getDoubleToolMarkup() - } - - set doubleToolMarkup(markup: Markup | undefined) { - this.setDoubleToolMarkup(markup) - } - - getDefaultDoubleToolMarkup() { - return this.store.get('defaultDoubleToolMarkup') - } - - getDoubleToolMarkup() { - return ( - this.store.get('doubleToolMarkup') || this.getDefaultDoubleToolMarkup() - ) - } - - setDoubleToolMarkup(markup?: Markup, options: Edge.SetOptions = {}) { - this.store.set('doubleToolMarkup', markup, options) - return this - } - - // #endregion - - // #region arrowheadMarkup - - get arrowheadMarkup() { - return this.getArrowheadMarkup() - } - - set arrowheadMarkup(markup: Markup) { - this.setArrowheadMarkup(markup) - } - - getDefaultArrowheadMarkup() { - return ( - this.store.get('defaultArrowheadMarkup') || - Markup.getEdgeArrowheadMarkup() - ) - } - - getArrowheadMarkup() { - return this.store.get('arrowheadMarkup') || this.getDefaultArrowheadMarkup() - } - - setArrowheadMarkup(markup?: Markup, options: Edge.SetOptions = {}) { - this.store.set('arrowheadMarkup', markup, options) - return this - } - - // #endregion - // #region transform /** @@ -993,9 +853,6 @@ export class Edge< export namespace Edge { export type RouterData = Router.NativeItem | Router.ManaualItem export type ConnectorData = Connector.NativeItem | Connector.ManaualItem - export type StrategyData = - | ConnectionStrategy.NativeItem - | ConnectionStrategy.ManaualItem } export namespace Edge { @@ -1004,20 +861,10 @@ export namespace Edge { target?: TerminalData router?: RouterData connector?: ConnectorData - strategy?: StrategyData labels?: Label[] | string[] defaultLabel?: Label vertices?: (Point.PointLike | Point.PointData)[] - toolMarkup?: Markup - doubleToolMarkup?: Markup - vertexMarkup?: Markup - arrowheadMarkup?: Markup - defaultMarkup?: Markup - defaultToolMarkup?: Markup - defaultDoubleToolMarkup?: Markup - defaultVertexMarkup?: Markup - defaultArrowheadMarkup?: Markup } interface TerminalOptions { diff --git a/packages/x6/src/model/layer.ts b/packages/x6/src/model/layer.ts deleted file mode 100644 index 38e88ae46a4..00000000000 --- a/packages/x6/src/model/layer.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Cell } from './cell' - -export class Layer extends Cell { - isLayer() { - return true - } -} diff --git a/packages/x6/src/model/model.ts b/packages/x6/src/model/model.ts index 981182529fc..bf9935b3f91 100644 --- a/packages/x6/src/model/model.ts +++ b/packages/x6/src/model/model.ts @@ -1,18 +1,16 @@ -import { KeyValue } from '../types' -import { FunctionExt } from '../util' -import { Basecoat, Dijkstra } from '../common' -import { Point, Rectangle } from '../geometry' -import { Graph } from '../graph' +import { FunctionExt, Dijkstra, KeyValue, Basecoat } from '@antv/x6-common' +import { Point, Rectangle } from '@antv/x6-geometry' import { Cell } from './cell' import { Edge } from './edge' import { Node } from './node' import { Collection } from './collection' +import { Graph } from '../graph' export class Model extends Basecoat { public readonly collection: Collection protected readonly batches: KeyValue = {} protected readonly addings: WeakMap = new WeakMap() - public graph: Graph | null + public graph: Graph protected nodes: KeyValue = {} protected edges: KeyValue = {} protected outgoings: KeyValue = {} diff --git a/packages/x6/src/model/node.ts b/packages/x6/src/model/node.ts index e21c02629ce..7b7741ed8c4 100644 --- a/packages/x6/src/model/node.ts +++ b/packages/x6/src/model/node.ts @@ -1,8 +1,14 @@ +import { Point, Rectangle, Angle } from '@antv/x6-geometry' +import { + StringExt, + ObjectExt, + NumberExt, + Registry, + Size, + KeyValue, + Interp, +} from '@antv/x6-common' import { DeepPartial, Omit } from 'utility-types' -import { Size, KeyValue } from '../types' -import { Registry } from '../registry' -import { Point, Rectangle, Angle } from '../geometry' -import { StringExt, ObjectExt, NumberExt } from '../util' import { Markup } from '../view/markup' import { Cell } from './cell' import { Edge } from './edge' @@ -10,7 +16,6 @@ import { Store } from './store' import { ShareRegistry } from './registry' import { PortManager } from './port' import { Animation } from './animation' -import { Interp } from '../common' export class Node< Properties extends Node.Properties = Node.Properties, diff --git a/packages/x6/src/model/port.ts b/packages/x6/src/model/port.ts index 189d182a95f..01d8b4954e8 100644 --- a/packages/x6/src/model/port.ts +++ b/packages/x6/src/model/port.ts @@ -1,8 +1,7 @@ -import { Attr, PortLayout, PortLabelLayout } from '../registry' -import { JSONObject, ObjectExt } from '../util' -import { Point, Rectangle } from '../geometry' -import { Size, KeyValue } from '../types' +import { JSONObject, ObjectExt, Size, KeyValue } from '@antv/x6-common' +import { Point, Rectangle } from '@antv/x6-geometry' import { Markup } from '../view' +import { Attr, PortLayout, PortLabelLayout } from '../registry' export class PortManager { ports: PortManager.Port[] diff --git a/packages/x6/src/model/registry.ts b/packages/x6/src/model/registry.ts index adc58f05930..f9b19f7c4f0 100644 --- a/packages/x6/src/model/registry.ts +++ b/packages/x6/src/model/registry.ts @@ -1,4 +1,4 @@ -import { Registry } from '../registry' +import { Registry } from '@antv/x6-common' export namespace ShareRegistry { let edgeRegistry: Registry diff --git a/packages/x6/src/model/store.ts b/packages/x6/src/model/store.ts index 0b7136eff84..6a34b5e53ac 100644 --- a/packages/x6/src/model/store.ts +++ b/packages/x6/src/model/store.ts @@ -1,7 +1,5 @@ +import { ObjectExt, KeyValue, Basecoat } from '@antv/x6-common' import { Assign, NonUndefined } from 'utility-types' -import { KeyValue } from '../types' -import { Basecoat } from '../common' -import { ObjectExt } from '../util' export class Store extends Basecoat> { protected data: D diff --git a/packages/x6/src/registry/attr/align.ts b/packages/x6/src/registry/attr/align.ts index b7c2cbf3de9..db20fdb306a 100644 --- a/packages/x6/src/registry/attr/align.ts +++ b/packages/x6/src/registry/attr/align.ts @@ -1,5 +1,5 @@ -import { NumberExt } from '../../util' -import { Point } from '../../geometry' +import { NumberExt } from '@antv/x6-common' +import { Point } from '@antv/x6-geometry' import { Attr } from './index' // `x-align` when set to `middle` causes centering of the subelement around its new x coordinate. diff --git a/packages/x6/src/registry/attr/fill.ts b/packages/x6/src/registry/attr/fill.ts index cf7dd1b070b..5a8a13f3657 100644 --- a/packages/x6/src/registry/attr/fill.ts +++ b/packages/x6/src/registry/attr/fill.ts @@ -1,4 +1,4 @@ -import { ObjectExt } from '../../util' +import { ObjectExt } from '@antv/x6-common' import { Attr } from './index' export const fill: Attr.Definition = { diff --git a/packages/x6/src/registry/attr/filter.ts b/packages/x6/src/registry/attr/filter.ts index e5457900151..c966e1d20d7 100644 --- a/packages/x6/src/registry/attr/filter.ts +++ b/packages/x6/src/registry/attr/filter.ts @@ -1,4 +1,4 @@ -import { ObjectExt } from '../../util' +import { ObjectExt } from '@antv/x6-common' import { Attr } from './index' export const filter: Attr.Definition = { diff --git a/packages/x6/src/registry/attr/html.ts b/packages/x6/src/registry/attr/html.ts index 27454f7f523..bbf9e973a51 100644 --- a/packages/x6/src/registry/attr/html.ts +++ b/packages/x6/src/registry/attr/html.ts @@ -1,7 +1,7 @@ import { Attr } from './index' export const html: Attr.Definition = { - set(html, { view, elem }) { - view.$(elem).html(`${html}`) + set(html, { elem }) { + elem.innerHTML = `${html}` }, } diff --git a/packages/x6/src/registry/attr/index.ts b/packages/x6/src/registry/attr/index.ts index 493d95af486..9f149966d17 100644 --- a/packages/x6/src/registry/attr/index.ts +++ b/packages/x6/src/registry/attr/index.ts @@ -1,8 +1,7 @@ -import { Rectangle, Point } from '../../geometry' -import { JSONObject, FunctionExt } from '../../util' +import { Rectangle, Point } from '@antv/x6-geometry' +import { JSONObject, FunctionExt, Registry } from '@antv/x6-common' import { Cell } from '../../model' import { CellView } from '../../view' -import { Registry } from '../registry' import { raw } from './raw' import * as attrs from './main' diff --git a/packages/x6/src/registry/attr/marker.ts b/packages/x6/src/registry/attr/marker.ts index f84f2162db0..21e86c33ab0 100644 --- a/packages/x6/src/registry/attr/marker.ts +++ b/packages/x6/src/registry/attr/marker.ts @@ -1,6 +1,5 @@ -import { KeyValue } from '../../types' +import { ObjectExt, JSONObject, KeyValue } from '@antv/x6-common' import { CellView } from '../../view' -import { ObjectExt, JSONObject } from '../../util' import { Marker } from '../marker' import { Attr } from './index' diff --git a/packages/x6/src/registry/attr/ref.ts b/packages/x6/src/registry/attr/ref.ts index 5f6c52dfed1..ed38c0186e5 100644 --- a/packages/x6/src/registry/attr/ref.ts +++ b/packages/x6/src/registry/attr/ref.ts @@ -1,5 +1,5 @@ -import { Point, Path, Polyline, Rectangle } from '../../geometry' -import { NumberExt, FunctionExt } from '../../util' +import { Point, Path, Polyline, Rectangle } from '@antv/x6-geometry' +import { NumberExt, FunctionExt, Dom } from '@antv/x6-common' import { Attr } from './index' export const ref: Attr.Definition = { @@ -190,9 +190,8 @@ function shapeWrapper( const cacheName = 'x6-shape' const resetOffset = options && options.resetOffset - return function (value, { view, elem, refBBox }) { - const $elem = view.$(elem) - let cache = $elem.data(cacheName) + return function (value, { elem, refBBox }) { + let cache = Dom.data(elem, cacheName) if (!cache || cache.value !== value) { // only recalculate if value has changed const cachedShape = shapeConstructor(value) @@ -201,7 +200,7 @@ function shapeWrapper( shape: cachedShape, shapeBBox: cachedShape.bbox(), } - $elem.data(cacheName, cache) + Dom.data(elem, cacheName, cache) } const shape = cache.shape.clone() diff --git a/packages/x6/src/registry/attr/stroke.ts b/packages/x6/src/registry/attr/stroke.ts index b67d7130818..dc366c88288 100644 --- a/packages/x6/src/registry/attr/stroke.ts +++ b/packages/x6/src/registry/attr/stroke.ts @@ -1,5 +1,4 @@ -import { ObjectExt } from '../../util' -import { DefsManager } from '../../graph/defs' +import { ObjectExt } from '@antv/x6-common' import { EdgeView } from '../../view/edge' import { Attr } from './index' @@ -7,7 +6,7 @@ export const stroke: Attr.Definition = { qualify: ObjectExt.isPlainObject, set(stroke: any, { view }) { const cell = view.cell - const options = { ...stroke } as DefsManager.GradientOptions + const options = { ...stroke } if (cell.isEdge() && options.type === 'linearGradient') { const edgeView = view as EdgeView diff --git a/packages/x6/src/registry/attr/style.ts b/packages/x6/src/registry/attr/style.ts index ff2fca95d1a..c20e7ac440a 100644 --- a/packages/x6/src/registry/attr/style.ts +++ b/packages/x6/src/registry/attr/style.ts @@ -1,9 +1,9 @@ -import { ObjectExt } from '../../util' +import { ObjectExt, Dom } from '@antv/x6-common' import { Attr } from './index' export const style: Attr.Definition = { qualify: ObjectExt.isPlainObject, - set(styles, { view, elem }) { - view.$(elem).css(styles as JQuery.PlainObject) + set(styles, { elem }) { + Dom.css(elem, styles as Record) }, } diff --git a/packages/x6/src/registry/attr/text.ts b/packages/x6/src/registry/attr/text.ts index 9fb33981a79..db6b7752c0f 100644 --- a/packages/x6/src/registry/attr/text.ts +++ b/packages/x6/src/registry/attr/text.ts @@ -5,7 +5,7 @@ import { Dom, FunctionExt, Text, -} from '../../util' +} from '@antv/x6-common' import { Attr } from './index' export const text: Attr.Definition = { @@ -14,8 +14,7 @@ export const text: Attr.Definition = { }, set(text, { view, elem, attrs }) { const cacheName = 'x6-text' - const $elem = view.$(elem) - const cache = $elem.data(cacheName) + const cache = Dom.data(elem, cacheName) const json = (str: any) => { try { return JSON.parse(str) as T @@ -63,7 +62,7 @@ export const text: Attr.Definition = { } Dom.text(elem as SVGElement, `${text}`, options) - $elem.data(cacheName, textHash) + Dom.data(elem, cacheName, textHash) } }, } @@ -111,10 +110,10 @@ export const textWrap: Attr.Definition = { lineHeight: attrs.lineHeight, }, { - svgDocument: view.graph.view.svg, + // svgDocument: view.graph.view.svg, ellipsis: info.ellipsis as string, - hyphen: info.hyphen as string, - breakWord: info.breakWord as boolean, + // hyphen: info.hyphen as string, + // breakWord: info.breakWord as boolean, }, ) } else { diff --git a/packages/x6/src/registry/attr/title.ts b/packages/x6/src/registry/attr/title.ts index 52aa6f1b21a..94040573c9a 100644 --- a/packages/x6/src/registry/attr/title.ts +++ b/packages/x6/src/registry/attr/title.ts @@ -1,3 +1,4 @@ +import { Dom } from '@antv/x6-common' import { Attr } from './index' export const title: Attr.Definition = { @@ -5,13 +6,12 @@ export const title: Attr.Definition = { // HTMLElement title is specified via an attribute (i.e. not an element) return elem instanceof SVGElement }, - set(val, { view, elem }) { + set(val, { elem }) { const cacheName = 'x6-title' const title = `${val}` - const $elem = view.$(elem) - const cache = $elem.data(cacheName) + const cache = Dom.data(elem, cacheName) if (cache == null || cache !== title) { - $elem.data(cacheName, title) + Dom.data(elem, cacheName, title) // Generally SVGTitleElement should be the first child // element of its parent. const firstChild = elem.firstChild as Element diff --git a/packages/x6/src/registry/background/index.ts b/packages/x6/src/registry/background/index.ts index e476d4c4651..cb2b3f2b35e 100644 --- a/packages/x6/src/registry/background/index.ts +++ b/packages/x6/src/registry/background/index.ts @@ -1,22 +1,22 @@ +/* eslint-disable @typescript-eslint/ban-types */ + import { ValuesType } from 'utility-types' -import { KeyValue } from '../../types' -import { Property } from '../../types/csstype' -import { Registry } from '../registry' +import { KeyValue, Registry } from '@antv/x6-common' import * as patterns from './main' export namespace Background { export interface Options { color?: string image?: string - position?: Property.BackgroundPosition<{ + position?: Background.BackgroundPosition<{ x: number y: number }> - size?: Property.BackgroundSize<{ + size?: Background.BackgroundSize<{ width: number height: number }> - repeat?: Property.BackgroundRepeat + repeat?: Background.BackgroundRepeat opacity?: number } @@ -62,3 +62,39 @@ export namespace Background { registry.register(presets, true) } + +export namespace Background { + type Globals = '-moz-initial' | 'inherit' | 'initial' | 'revert' | 'unset' + type BgPosition = + | TLength + | 'bottom' + | 'center' + | 'left' + | 'right' + | 'top' + | (string & {}) + type BgSize = TLength | 'auto' | 'contain' | 'cover' | (string & {}) + type RepeatStyle = + | 'no-repeat' + | 'repeat' + | 'repeat-x' + | 'repeat-y' + | 'round' + | 'space' + | (string & {}) + export type BackgroundPosition = + | Globals + | BgPosition + | (string & {}) + export type BackgroundSize = + | Globals + | BgSize + | (string & {}) + export type BackgroundRepeat = Globals | RepeatStyle | (string & {}) + export interface Padding { + left: number + top: number + right: number + bottom: number + } +} diff --git a/packages/x6/src/registry/background/watermark.ts b/packages/x6/src/registry/background/watermark.ts index c6cbf83dd4c..f51dcb8cba1 100644 --- a/packages/x6/src/registry/background/watermark.ts +++ b/packages/x6/src/registry/background/watermark.ts @@ -1,5 +1,5 @@ +import { Angle } from '@antv/x6-geometry' import { Background } from './index' -import { Angle } from '../../geometry' export interface WatermarkOptions extends Background.CommonOptions { angle?: number diff --git a/packages/x6/src/registry/connection-point/anchor.ts b/packages/x6/src/registry/connection-point/anchor.ts index 0606a6b91ce..a2c4243620a 100644 --- a/packages/x6/src/registry/connection-point/anchor.ts +++ b/packages/x6/src/registry/connection-point/anchor.ts @@ -1,4 +1,4 @@ -import { Line } from '../../geometry' +import { Line } from '@antv/x6-geometry' import { ConnectionPoint } from './index' import { offset } from './util' diff --git a/packages/x6/src/registry/connection-point/boundary.ts b/packages/x6/src/registry/connection-point/boundary.ts index 68dccf2715e..d0ac477c7c6 100644 --- a/packages/x6/src/registry/connection-point/boundary.ts +++ b/packages/x6/src/registry/connection-point/boundary.ts @@ -1,7 +1,8 @@ -import { ObjectExt, Dom } from '../../util' -import { Path, Rectangle, Ellipse, Segment } from '../../geometry' +import { ObjectExt, Dom } from '@antv/x6-common' +import { Path, Rectangle, Ellipse, Segment } from '@antv/x6-geometry' import { offset, getStrokeWidth, findShapeNode } from './util' import { ConnectionPoint } from './index' +import { Util } from '../../util' export interface BoundaryOptions extends ConnectionPoint.StrokedOptions { selector?: string | string[] @@ -54,7 +55,7 @@ export const boundary: ConnectionPoint.Definition = function ( .multiply(rotateMatrix) .multiply(magnetMatrix) const localMatrix = targetMatrix.inverse() - const localLine = Dom.transformLine(line, localMatrix) + const localLine = Util.transformLine(line, localMatrix) const localRef = localLine.start.clone() const data = view.getDataOfElement(node) as BoundaryCache @@ -107,7 +108,7 @@ export const boundary: ConnectionPoint.Definition = function ( } const cp = intersection - ? Dom.transformPoint(intersection, targetMatrix) + ? Util.transformPoint(intersection, targetMatrix) : anchor let cpOffset = options.offset || 0 if (options.stroked !== false) { diff --git a/packages/x6/src/registry/connection-point/index.ts b/packages/x6/src/registry/connection-point/index.ts index 9d5f46e87f9..cb20632e7df 100644 --- a/packages/x6/src/registry/connection-point/index.ts +++ b/packages/x6/src/registry/connection-point/index.ts @@ -1,8 +1,7 @@ -import { KeyValue } from '../../types' -import { Point, Line } from '../../geometry' +import { Point, Line } from '@antv/x6-geometry' +import { Registry, KeyValue } from '@antv/x6-common' import { Edge } from '../../model/edge' import { CellView } from '../../view/cell' -import { Registry } from '../registry' import * as connectionPoints from './main' export namespace ConnectionPoint { diff --git a/packages/x6/src/registry/connection-point/rect.ts b/packages/x6/src/registry/connection-point/rect.ts index 9c92f1078ad..8b93163e9e3 100644 --- a/packages/x6/src/registry/connection-point/rect.ts +++ b/packages/x6/src/registry/connection-point/rect.ts @@ -1,7 +1,7 @@ +import { FunctionExt } from '@antv/x6-common' import { bbox } from './bbox' import { offset, getStrokeWidth } from './util' import { ConnectionPoint } from './index' -import { FunctionExt } from '../../util' export interface RectangleOptions extends ConnectionPoint.StrokedOptions {} diff --git a/packages/x6/src/registry/connection-point/util.ts b/packages/x6/src/registry/connection-point/util.ts index 429fa467104..8962784d3df 100644 --- a/packages/x6/src/registry/connection-point/util.ts +++ b/packages/x6/src/registry/connection-point/util.ts @@ -1,4 +1,4 @@ -import { Point, Line } from '../../geometry' +import { Point, Line } from '@antv/x6-geometry' export function offset( p1: Point, diff --git a/packages/x6/src/registry/connection-strategy/index.ts b/packages/x6/src/registry/connection-strategy/index.ts index b2fb2f162c7..21bd428de85 100644 --- a/packages/x6/src/registry/connection-strategy/index.ts +++ b/packages/x6/src/registry/connection-strategy/index.ts @@ -1,10 +1,9 @@ -import { Point } from '../../geometry' -import { KeyValue } from '../../types' +import { Point } from '@antv/x6-geometry' +import { Registry, KeyValue } from '@antv/x6-common' import { Edge } from '../../model' -import { Graph } from '../../graph' import { CellView } from '../../view' -import { Registry } from '../registry' import * as strategies from './main' +import { Graph } from '../../graph' export namespace ConnectionStrategy { export type Definition = ( diff --git a/packages/x6/src/registry/connection-strategy/pin.ts b/packages/x6/src/registry/connection-strategy/pin.ts index 8a909f37c3c..42eea3cdb6f 100644 --- a/packages/x6/src/registry/connection-strategy/pin.ts +++ b/packages/x6/src/registry/connection-strategy/pin.ts @@ -1,4 +1,4 @@ -import { Point } from '../../geometry' +import { Point } from '@antv/x6-geometry' import { Node, Edge } from '../../model' import { EdgeView, NodeView } from '../../view' import { ConnectionStrategy } from './index' diff --git a/packages/x6/src/registry/connector/index.ts b/packages/x6/src/registry/connector/index.ts index aabae9c24a4..c0010ffa07d 100644 --- a/packages/x6/src/registry/connector/index.ts +++ b/packages/x6/src/registry/connector/index.ts @@ -1,7 +1,6 @@ -import { KeyValue } from '../../types' -import { Point, Path } from '../../geometry' +import { Point, Path } from '@antv/x6-geometry' +import { Registry, KeyValue } from '@antv/x6-common' import { EdgeView } from '../../view' -import { Registry } from '../registry' import * as connectors from './main' export namespace Connector { diff --git a/packages/x6/src/registry/connector/jumpover.ts b/packages/x6/src/registry/connector/jumpover.ts index 75f260f00c9..1f99d465ad3 100644 --- a/packages/x6/src/registry/connector/jumpover.ts +++ b/packages/x6/src/registry/connector/jumpover.ts @@ -1,6 +1,6 @@ /* eslint-disable no-underscore-dangle */ -import { Point, Line, Path } from '../../geometry' +import { Point, Line, Path } from '@antv/x6-geometry' import { Edge } from '../../model' import { EdgeView } from '../../view' import { Connector } from './index' @@ -330,7 +330,7 @@ export const jumpover: Connector.Definition = // find views for all links const linkViews = edges.map((edge) => { - return graph.renderer.findViewByCell(edge) as EdgeView + return graph.findViewByCell(edge) as EdgeView }) // create lines for this link diff --git a/packages/x6/src/registry/connector/loop.ts b/packages/x6/src/registry/connector/loop.ts index 46da92b912d..c01cf6e5c71 100644 --- a/packages/x6/src/registry/connector/loop.ts +++ b/packages/x6/src/registry/connector/loop.ts @@ -1,4 +1,4 @@ -import { Path, Point } from '../../geometry' +import { Path, Point } from '@antv/x6-geometry' import { Connector } from './index' export interface LoopConnectorOptions extends Connector.BaseOptions { diff --git a/packages/x6/src/registry/connector/normal.ts b/packages/x6/src/registry/connector/normal.ts index e3de5be90fb..b21ff7163be 100644 --- a/packages/x6/src/registry/connector/normal.ts +++ b/packages/x6/src/registry/connector/normal.ts @@ -1,4 +1,4 @@ -import { Polyline, Path } from '../../geometry' +import { Polyline, Path } from '@antv/x6-geometry' import { Connector } from './index' export const normal: Connector.Definition = function ( diff --git a/packages/x6/src/registry/connector/rounded.ts b/packages/x6/src/registry/connector/rounded.ts index 2317b8f0a1a..aa94518a58b 100644 --- a/packages/x6/src/registry/connector/rounded.ts +++ b/packages/x6/src/registry/connector/rounded.ts @@ -1,4 +1,4 @@ -import { Point, Path } from '../../geometry' +import { Point, Path } from '@antv/x6-geometry' import { Connector } from './index' export interface RoundedConnectorOptions extends Connector.BaseOptions { diff --git a/packages/x6/src/registry/connector/smooth.ts b/packages/x6/src/registry/connector/smooth.ts index 89c60fab2a0..0aa67df31d2 100644 --- a/packages/x6/src/registry/connector/smooth.ts +++ b/packages/x6/src/registry/connector/smooth.ts @@ -1,4 +1,4 @@ -import { Curve, Path } from '../../geometry' +import { Curve, Path } from '@antv/x6-geometry' import { Connector } from './index' export interface SmoothConnectorOptions extends Connector.BaseOptions { diff --git a/packages/x6/src/registry/edge-anchor/closest.ts b/packages/x6/src/registry/edge-anchor/closest.ts index 9b1041887c7..bf03fc0e53b 100644 --- a/packages/x6/src/registry/edge-anchor/closest.ts +++ b/packages/x6/src/registry/edge-anchor/closest.ts @@ -1,4 +1,4 @@ -import { Point } from '../../geometry' +import { Point } from '@antv/x6-geometry' import { ResolveOptions, resolve } from '../node-anchor/util' import { EdgeAnchor } from './index' diff --git a/packages/x6/src/registry/edge-anchor/index.ts b/packages/x6/src/registry/edge-anchor/index.ts index 2db6dbb803c..3a9a2c6489d 100644 --- a/packages/x6/src/registry/edge-anchor/index.ts +++ b/packages/x6/src/registry/edge-anchor/index.ts @@ -1,8 +1,7 @@ -import { KeyValue } from '../../types' -import { Point } from '../../geometry' +import { Point } from '@antv/x6-geometry' +import { Registry, KeyValue } from '@antv/x6-common' import { Edge } from '../../model/edge' import { EdgeView } from '../../view' -import { Registry } from '../registry' import * as anchors from './main' export namespace EdgeAnchor { diff --git a/packages/x6/src/registry/edge-anchor/orth.ts b/packages/x6/src/registry/edge-anchor/orth.ts index fd64ec8d7b0..d37c2ac8534 100644 --- a/packages/x6/src/registry/edge-anchor/orth.ts +++ b/packages/x6/src/registry/edge-anchor/orth.ts @@ -1,8 +1,8 @@ -import { Line, Point } from '../../geometry' +import { Line, Point } from '@antv/x6-geometry' +import { FunctionExt } from '@antv/x6-common' import { ResolveOptions, resolve, getPointAtEdge } from '../node-anchor/util' import { getClosestPoint } from './closest' import { EdgeAnchor } from './index' -import { FunctionExt } from '../../util' export interface OrthEndpointOptions extends ResolveOptions { fallbackAt?: number | string diff --git a/packages/x6/src/registry/filter/index.ts b/packages/x6/src/registry/filter/index.ts index dc69fca4f20..6fbad5a147c 100644 --- a/packages/x6/src/registry/filter/index.ts +++ b/packages/x6/src/registry/filter/index.ts @@ -1,6 +1,5 @@ import { NonUndefined } from 'utility-types' -import { KeyValue } from '../../types' -import { Registry } from '../registry' +import { KeyValue, Registry } from '@antv/x6-common' import * as filters from './main' export namespace Filter { diff --git a/packages/x6/src/registry/grid/dot.ts b/packages/x6/src/registry/grid/dot.ts index 77fc4685918..192b165db16 100644 --- a/packages/x6/src/registry/grid/dot.ts +++ b/packages/x6/src/registry/grid/dot.ts @@ -1,4 +1,4 @@ -import { Dom } from '../../util' +import { Dom } from '@antv/x6-common' import { Grid } from './index' export interface DotOptions extends Grid.Options {} diff --git a/packages/x6/src/registry/grid/double-mesh.ts b/packages/x6/src/registry/grid/double-mesh.ts index a2c1ddc4280..4df5b85c5c0 100644 --- a/packages/x6/src/registry/grid/double-mesh.ts +++ b/packages/x6/src/registry/grid/double-mesh.ts @@ -1,4 +1,4 @@ -import { Dom } from '../../util' +import { Dom } from '@antv/x6-common' import { Grid } from './index' export interface DoubleMeshOptions extends Grid.Options { diff --git a/packages/x6/src/registry/grid/fixed-dot.ts b/packages/x6/src/registry/grid/fixed-dot.ts index fb5c27fcb3c..98a8b48812b 100644 --- a/packages/x6/src/registry/grid/fixed-dot.ts +++ b/packages/x6/src/registry/grid/fixed-dot.ts @@ -1,4 +1,4 @@ -import { Dom } from '../../util' +import { Dom } from '@antv/x6-common' import { Grid } from './index' export interface FixedDotOptions extends Grid.Options {} diff --git a/packages/x6/src/registry/grid/index.ts b/packages/x6/src/registry/grid/index.ts index ae32a4ea3d1..1390f8340e2 100644 --- a/packages/x6/src/registry/grid/index.ts +++ b/packages/x6/src/registry/grid/index.ts @@ -1,6 +1,4 @@ -import { Dom, Vector } from '../../util' -import { KeyValue } from '../../types' -import { Registry } from '../registry' +import { Dom, Vector, Registry, KeyValue } from '@antv/x6-common' import * as patterns from './main' export class Grid { diff --git a/packages/x6/src/registry/grid/mesh.ts b/packages/x6/src/registry/grid/mesh.ts index 1e4c53a2199..60b2e84fae6 100644 --- a/packages/x6/src/registry/grid/mesh.ts +++ b/packages/x6/src/registry/grid/mesh.ts @@ -1,4 +1,4 @@ -import { Dom } from '../../util' +import { Dom } from '@antv/x6-common' import { Grid } from './index' export interface MeshOptions extends Grid.Options {} diff --git a/packages/x6/src/registry/highlighter/class.ts b/packages/x6/src/registry/highlighter/class.ts index 81e3dbe5881..1b14d0843a8 100644 --- a/packages/x6/src/registry/highlighter/class.ts +++ b/packages/x6/src/registry/highlighter/class.ts @@ -1,12 +1,12 @@ -import { Dom } from '../../util' -import { Util } from '../../global' +import { Dom } from '@antv/x6-common' +import { Config } from '../../config' import { Highlighter } from './index' export interface ClassHighlighterOptions { className?: string } -const defaultClassName = Util.prefix('highlighted') +const defaultClassName = Config.prefix('highlighted') export const className: Highlighter.Definition = { highlight(cellView, magnet, options) { diff --git a/packages/x6/src/registry/highlighter/index.ts b/packages/x6/src/registry/highlighter/index.ts index e4fa3e0333e..48a250fd5e0 100644 --- a/packages/x6/src/registry/highlighter/index.ts +++ b/packages/x6/src/registry/highlighter/index.ts @@ -1,6 +1,5 @@ -import { KeyValue } from '../../types' +import { Registry, KeyValue } from '@antv/x6-common' import { CellView } from '../../view' -import { Registry } from '../registry' import * as highlighters from './main' export namespace Highlighter { diff --git a/packages/x6/src/registry/highlighter/opacity.ts b/packages/x6/src/registry/highlighter/opacity.ts index b78a4c37fef..bbf3bafd9f3 100644 --- a/packages/x6/src/registry/highlighter/opacity.ts +++ b/packages/x6/src/registry/highlighter/opacity.ts @@ -1,10 +1,10 @@ -import { Dom } from '../../util' -import { Util } from '../../global' +import { Dom } from '@antv/x6-common' +import { Config } from '../../config' import { Highlighter } from './index' export interface OpacityHighlighterOptions {} -const className = Util.prefix('highlight-opacity') +const className = Config.prefix('highlight-opacity') export const opacity: Highlighter.Definition = { highlight(cellView, magnet) { diff --git a/packages/x6/src/registry/highlighter/stroke.ts b/packages/x6/src/registry/highlighter/stroke.ts index 91d5fdaf7a5..899b50059aa 100644 --- a/packages/x6/src/registry/highlighter/stroke.ts +++ b/packages/x6/src/registry/highlighter/stroke.ts @@ -1,8 +1,9 @@ +import { ObjectExt, Dom, Vector } from '@antv/x6-common' import { Attr } from '../attr' -import { ObjectExt, Dom, Vector } from '../../util' -import { Util } from '../../global' +import { Config } from '../../config' import { EdgeView } from '../../view' import { Highlighter } from './index' +import { Util } from '../../util' export interface StrokeHighlighterOptions { padding?: number @@ -40,7 +41,7 @@ export const stroke: Highlighter.Definition = { } catch (error) { // Failed to get path data from magnet element. // Draw a rectangle around the entire cell view instead. - magnetBBox = magnetVel.bbox(true /* without transforms */) + magnetBBox = Util.bbox(magnetVel.node, true) pathData = Dom.rectToPathData({ ...options, ...magnetBBox }) } @@ -66,13 +67,13 @@ export const stroke: Highlighter.Definition = { const padding = options.padding if (padding) { if (magnetBBox == null) { - magnetBBox = magnetVel.bbox(true) + magnetBBox = Util.bbox(magnetVel.node, true) } const cx = magnetBBox.x + magnetBBox.width / 2 const cy = magnetBBox.y + magnetBBox.height / 2 - magnetBBox = Dom.transformRectangle(magnetBBox, highlightMatrix) + magnetBBox = Util.transformRectangle(magnetBBox, highlightMatrix) const width = Math.max(magnetBBox.width, 1) const height = Math.max(magnetBBox.height, 1) @@ -94,7 +95,7 @@ export const stroke: Highlighter.Definition = { Dom.transform(path, highlightMatrix) } - Dom.addClass(path, Util.prefix('highlight-stroke')) + Dom.addClass(path, Config.prefix('highlight-stroke')) const cell = cellView.cell const removeHandler = () => Private.removeHighlighter(id) diff --git a/packages/x6/src/registry/index.ts b/packages/x6/src/registry/index.ts index fd0505c00c3..c0b5e38d62b 100644 --- a/packages/x6/src/registry/index.ts +++ b/packages/x6/src/registry/index.ts @@ -1,25 +1,14 @@ -import { Registry } from './registry' - -export * from './attr' export * from './grid' -export * from './filter' export * from './background' +export * from './filter' +export * from './attr' export * from './highlighter' export * from './port-layout' export * from './port-label-layout' export * from './tool' - -// connection export * from './marker' export * from './node-anchor' export * from './edge-anchor' export * from './connection-point' export * from './router' export * from './connector' -export * from './connection-strategy' - -// - -export * from './registry' - -export const create = Registry.create diff --git a/packages/x6/src/registry/marker/async.ts b/packages/x6/src/registry/marker/async.ts index e3abaed9d8d..3f38cb77616 100644 --- a/packages/x6/src/registry/marker/async.ts +++ b/packages/x6/src/registry/marker/async.ts @@ -1,5 +1,5 @@ -import { KeyValue } from '../../types' -import { Path } from '../../geometry' +import { Path } from '@antv/x6-geometry' +import { KeyValue } from '@antv/x6-common' import { normalize } from './util' import { Marker } from './index' diff --git a/packages/x6/src/registry/marker/circle.ts b/packages/x6/src/registry/marker/circle.ts index 69af3beca67..5ab7fa22ce1 100644 --- a/packages/x6/src/registry/marker/circle.ts +++ b/packages/x6/src/registry/marker/circle.ts @@ -1,4 +1,4 @@ -import { Path } from '../../geometry' +import { Path } from '@antv/x6-geometry' import { Attr } from '../attr' import { normalize } from './util' import { Marker } from './index' diff --git a/packages/x6/src/registry/marker/classic.ts b/packages/x6/src/registry/marker/classic.ts index 41136c4f32e..045ca2c6e3f 100644 --- a/packages/x6/src/registry/marker/classic.ts +++ b/packages/x6/src/registry/marker/classic.ts @@ -1,6 +1,5 @@ -import { KeyValue } from '../../types' -import { Path } from '../../geometry' -import { NumberExt } from '../../util' +import { Path } from '@antv/x6-geometry' +import { NumberExt, KeyValue } from '@antv/x6-common' import { normalize } from './util' import { Marker } from './index' diff --git a/packages/x6/src/registry/marker/cross.ts b/packages/x6/src/registry/marker/cross.ts index 15f98ad3290..4da6dc8a95a 100644 --- a/packages/x6/src/registry/marker/cross.ts +++ b/packages/x6/src/registry/marker/cross.ts @@ -1,4 +1,4 @@ -import { Path } from '../../geometry' +import { Path } from '@antv/x6-geometry' import { Attr } from '../attr' import { normalize } from './util' import { Marker } from './index' diff --git a/packages/x6/src/registry/marker/diamond.ts b/packages/x6/src/registry/marker/diamond.ts index 4e6de19057c..0077e8c5a5b 100644 --- a/packages/x6/src/registry/marker/diamond.ts +++ b/packages/x6/src/registry/marker/diamond.ts @@ -1,4 +1,4 @@ -import { Path } from '../../geometry' +import { Path } from '@antv/x6-geometry' import { Attr } from '../attr' import { normalize } from './util' import { Marker } from './index' diff --git a/packages/x6/src/registry/marker/index.ts b/packages/x6/src/registry/marker/index.ts index fae0ecfefec..c3735becad4 100644 --- a/packages/x6/src/registry/marker/index.ts +++ b/packages/x6/src/registry/marker/index.ts @@ -1,5 +1,4 @@ -import { KeyValue } from '../../types' -import { Registry } from '../registry' +import { Registry, KeyValue } from '@antv/x6-common' import { Attr } from '../attr' import * as markers from './main' import { normalize as normalizeMarker } from './util' diff --git a/packages/x6/src/registry/marker/util.ts b/packages/x6/src/registry/marker/util.ts index c6367fb3a25..5146dabd7ae 100644 --- a/packages/x6/src/registry/marker/util.ts +++ b/packages/x6/src/registry/marker/util.ts @@ -1,4 +1,4 @@ -import { Path } from '../../geometry' +import { Path } from '@antv/x6-geometry' /** * Normalizes marker's path data by translate the center diff --git a/packages/x6/src/registry/node-anchor/bbox.ts b/packages/x6/src/registry/node-anchor/bbox.ts index 929c165f26c..b344a7c3a09 100644 --- a/packages/x6/src/registry/node-anchor/bbox.ts +++ b/packages/x6/src/registry/node-anchor/bbox.ts @@ -1,4 +1,4 @@ -import { NumberExt } from '../../util' +import { NumberExt } from '@antv/x6-common' import { NodeAnchor } from './index' export interface BBoxEndpointOptions { diff --git a/packages/x6/src/registry/node-anchor/index.ts b/packages/x6/src/registry/node-anchor/index.ts index bd0b43061b2..6a60f69eb42 100644 --- a/packages/x6/src/registry/node-anchor/index.ts +++ b/packages/x6/src/registry/node-anchor/index.ts @@ -1,8 +1,7 @@ -import { KeyValue } from '../../types' -import { Point } from '../../geometry' +import { Point } from '@antv/x6-geometry' +import { Registry, KeyValue } from '@antv/x6-common' import { Edge } from '../../model' import { EdgeView, NodeView } from '../../view' -import { Registry } from '../registry' import * as anchors from './main' export namespace NodeAnchor { diff --git a/packages/x6/src/registry/node-anchor/middle-side.ts b/packages/x6/src/registry/node-anchor/middle-side.ts index 2a24e489960..02e4bf74f78 100644 --- a/packages/x6/src/registry/node-anchor/middle-side.ts +++ b/packages/x6/src/registry/node-anchor/middle-side.ts @@ -1,6 +1,6 @@ +import { Point } from '@antv/x6-geometry' import { ResolveOptions, resolve } from './util' import { NodeAnchor } from './index' -import { Point } from '../../geometry' export interface MiddleSideEndpointOptions extends ResolveOptions { rotate?: boolean diff --git a/packages/x6/src/registry/node-anchor/orth.ts b/packages/x6/src/registry/node-anchor/orth.ts index bbf1500625d..37bd9cecd81 100644 --- a/packages/x6/src/registry/node-anchor/orth.ts +++ b/packages/x6/src/registry/node-anchor/orth.ts @@ -1,4 +1,4 @@ -import { Angle } from '../../geometry' +import { Angle } from '@antv/x6-geometry' import { ResolveOptions, resolve } from './util' import { NodeAnchor } from './index' diff --git a/packages/x6/src/registry/node-anchor/util.ts b/packages/x6/src/registry/node-anchor/util.ts index ae9813117f8..5c913e55a81 100644 --- a/packages/x6/src/registry/node-anchor/util.ts +++ b/packages/x6/src/registry/node-anchor/util.ts @@ -1,5 +1,5 @@ -import { NumberExt } from '../../util' -import { Point } from '../../geometry' +import { NumberExt } from '@antv/x6-common' +import { Point } from '@antv/x6-geometry' import { EdgeView } from '../../view' export interface ResolveOptions { @@ -16,7 +16,7 @@ export function resolve(fn: S): T { options: ResolveOptions, ) { if (ref instanceof Element) { - const refView = this.graph.renderer.findViewByElem(ref) + const refView = this.graph.findViewByElem(ref) let refPoint if (refView) { if (refView.isEdgeElement(ref)) { diff --git a/packages/x6/src/registry/port-label-layout/index.ts b/packages/x6/src/registry/port-label-layout/index.ts index b87074be172..c43230e5e9e 100644 --- a/packages/x6/src/registry/port-label-layout/index.ts +++ b/packages/x6/src/registry/port-label-layout/index.ts @@ -1,7 +1,6 @@ -import { KeyValue } from '../../types' -import { Point, Rectangle } from '../../geometry' +import { Point, Rectangle } from '@antv/x6-geometry' +import { Registry, KeyValue } from '@antv/x6-common' import { Attr } from '../attr' -import { Registry } from '../registry' import * as layouts from './main' export namespace PortLabelLayout { diff --git a/packages/x6/src/registry/port-label-layout/inout.ts b/packages/x6/src/registry/port-label-layout/inout.ts index c2450794c1f..866f8757e53 100644 --- a/packages/x6/src/registry/port-label-layout/inout.ts +++ b/packages/x6/src/registry/port-label-layout/inout.ts @@ -1,4 +1,4 @@ -import { Point, Rectangle } from '../../geometry' +import { Point, Rectangle } from '@antv/x6-geometry' import { PortLabelLayout } from './index' import { toResult } from './util' diff --git a/packages/x6/src/registry/port-label-layout/radial.ts b/packages/x6/src/registry/port-label-layout/radial.ts index 41c9c14d067..9d1557763dd 100644 --- a/packages/x6/src/registry/port-label-layout/radial.ts +++ b/packages/x6/src/registry/port-label-layout/radial.ts @@ -1,4 +1,4 @@ -import { Point } from '../../geometry' +import { Point } from '@antv/x6-geometry' import { PortLabelLayout } from './index' import { toResult } from './util' diff --git a/packages/x6/src/registry/port-label-layout/util.ts b/packages/x6/src/registry/port-label-layout/util.ts index 3722103cb84..d5b5a0666d7 100644 --- a/packages/x6/src/registry/port-label-layout/util.ts +++ b/packages/x6/src/registry/port-label-layout/util.ts @@ -1,4 +1,4 @@ -import { ObjectExt } from '../../util' +import { ObjectExt } from '@antv/x6-common' import { PortLabelLayout } from './index' const defaults: PortLabelLayout.Result = { diff --git a/packages/x6/src/registry/port-layout/ellipse.ts b/packages/x6/src/registry/port-layout/ellipse.ts index fcce74b8da7..7a66b6203ae 100644 --- a/packages/x6/src/registry/port-layout/ellipse.ts +++ b/packages/x6/src/registry/port-layout/ellipse.ts @@ -1,4 +1,4 @@ -import { Rectangle, Ellipse } from '../../geometry' +import { Rectangle, Ellipse } from '@antv/x6-geometry' import { PortLayout } from './index' import { toResult } from './util' diff --git a/packages/x6/src/registry/port-layout/index.ts b/packages/x6/src/registry/port-layout/index.ts index 76c2ba4f8c6..80a70b5ec09 100644 --- a/packages/x6/src/registry/port-layout/index.ts +++ b/packages/x6/src/registry/port-layout/index.ts @@ -1,6 +1,5 @@ -import { KeyValue } from '../../types' -import { Rectangle, Point } from '../../geometry' -import { Registry } from '../registry' +import { Rectangle, Point } from '@antv/x6-geometry' +import { Registry, KeyValue } from '@antv/x6-common' import * as layouts from './main' export namespace PortLayout { diff --git a/packages/x6/src/registry/port-layout/line.ts b/packages/x6/src/registry/port-layout/line.ts index 1eb4b955986..aaaedd05e8b 100644 --- a/packages/x6/src/registry/port-layout/line.ts +++ b/packages/x6/src/registry/port-layout/line.ts @@ -1,4 +1,4 @@ -import { Point, Line } from '../../geometry' +import { Point, Line } from '@antv/x6-geometry' import { normalizePoint, toResult } from './util' import { PortLayout } from './index' diff --git a/packages/x6/src/registry/port-layout/util.ts b/packages/x6/src/registry/port-layout/util.ts index 60e3d675106..a51d50f3f1f 100644 --- a/packages/x6/src/registry/port-layout/util.ts +++ b/packages/x6/src/registry/port-layout/util.ts @@ -1,5 +1,5 @@ -import { NumberExt } from '../../util' -import { Point, Rectangle } from '../../geometry' +import { NumberExt } from '@antv/x6-common' +import { Point, Rectangle } from '@antv/x6-geometry' import { PortLayout } from './index' export function normalizePoint( diff --git a/packages/x6/src/registry/registry.ts b/packages/x6/src/registry/registry.ts deleted file mode 100644 index 4172219f8e4..00000000000 --- a/packages/x6/src/registry/registry.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { KeyValue } from '../types' -import { StringExt, FunctionExt, Platform } from '../util' - -export class Registry< - Entity, - Presets = KeyValue, - OptionalType = never, -> { - public readonly data: KeyValue - public readonly options: Registry.Options - - constructor(options: Registry.Options) { - this.options = { ...options } - this.data = (this.options.data as KeyValue) || {} - this.register = this.register.bind(this) - this.unregister = this.unregister.bind(this) - } - - get names() { - return Object.keys(this.data) - } - - register( - entities: { [name: string]: Entity | OptionalType }, - force?: boolean, - ): void - register( - name: K, - entity: Presets[K], - force?: boolean, - ): Entity - register(name: string, entity: Entity | OptionalType, force?: boolean): Entity - register( - name: string | { [name: string]: Entity | OptionalType }, - options: any, - force = false, - ) { - if (typeof name === 'object') { - Object.keys(name).forEach((key) => { - this.register(key, name[key], options) - }) - return - } - - if (this.exist(name) && !force && !Platform.isApplyingHMR()) { - this.onDuplicated(name) - } - - const process = this.options.process - const entity = process - ? FunctionExt.call(process, this as any, name, options) - : options - - this.data[name] = entity - - return entity - } - - unregister(name: K): Entity | null - unregister(name: string): Entity | null - unregister(name: string): Entity | null { - const entity = name ? this.data[name] : null - delete this.data[name] - return entity - } - - get(name: K): Entity | null - get(name: string): Entity | null - get(name: string): Entity | null { - return name ? this.data[name] : null - } - - exist(name: K): boolean - exist(name: string): boolean - exist(name: string): boolean { - return name ? this.data[name] != null : false - } - - onDuplicated(name: string) { - // eslint-disable-next-line no-useless-catch - try { - // race - if (this.options.onConflict) { - FunctionExt.call(this.options.onConflict, this as any, name) - } - throw new Error( - `${StringExt.upperFirst( - this.options.type, - )} with name '${name}' already registered.`, - ) - } catch (err) { - throw err - } - } - - onNotFound(name: string, prefix?: string): never { - throw new Error(this.getSpellingSuggestion(name, prefix)) - } - - getSpellingSuggestion(name: string, prefix?: string) { - const suggestion = this.getSpellingSuggestionForName(name) - const prefixed = prefix - ? `${prefix} ${StringExt.lowerFirst(this.options.type)}` - : this.options.type - - return ( - // eslint-disable-next-line - `${StringExt.upperFirst(prefixed)} with name '${name}' does not exist.${ - suggestion ? ` Did you mean '${suggestion}'?` : '' - }` - ) - } - - protected getSpellingSuggestionForName(name: string) { - return StringExt.getSpellingSuggestion( - name, - Object.keys(this.data), - (candidate) => candidate, - ) - } -} - -export namespace Registry { - export interface Options { - type: string - data?: KeyValue - process?: >( - this: Context, - name: string, - entity: Entity, - ) => T - onConflict?: >( - this: Context, - name: string, - ) => void - } -} - -export namespace Registry { - export function create< - Entity, - Presets = KeyValue, - OptionalType = never, - >(options: Options) { - return new Registry(options) - } -} diff --git a/packages/x6/src/registry/router/index.ts b/packages/x6/src/registry/router/index.ts index 01ae87a4666..4b5956092f1 100644 --- a/packages/x6/src/registry/router/index.ts +++ b/packages/x6/src/registry/router/index.ts @@ -1,7 +1,6 @@ -import { KeyValue } from '../../types' -import { Point } from '../../geometry' +import { Point } from '@antv/x6-geometry' +import { Registry, KeyValue } from '@antv/x6-common' import { EdgeView } from '../../view' -import { Registry } from '../registry' import * as routers from './main' export namespace Router { diff --git a/packages/x6/src/registry/router/loop.ts b/packages/x6/src/registry/router/loop.ts index bfa99086448..0c4dc41ca6a 100644 --- a/packages/x6/src/registry/router/loop.ts +++ b/packages/x6/src/registry/router/loop.ts @@ -1,4 +1,4 @@ -import { Angle, Point, Line } from '../../geometry' +import { Angle, Point, Line } from '@antv/x6-geometry' import { Router } from './index' export interface LoopRouterOptions { diff --git a/packages/x6/src/registry/router/manhattan/index.ts b/packages/x6/src/registry/router/manhattan/index.ts index eb66b3bfd7e..6641e7b47a4 100644 --- a/packages/x6/src/registry/router/manhattan/index.ts +++ b/packages/x6/src/registry/router/manhattan/index.ts @@ -1,4 +1,4 @@ -import { FunctionExt } from '../../../util' +import { FunctionExt } from '@antv/x6-common' import { Router } from '../index' import { router } from './router' import { defaults, ManhattanRouterOptions } from './options' diff --git a/packages/x6/src/registry/router/manhattan/obstacle-map.ts b/packages/x6/src/registry/router/manhattan/obstacle-map.ts index 43ba70ebdb0..2d8136ba87e 100644 --- a/packages/x6/src/registry/router/manhattan/obstacle-map.ts +++ b/packages/x6/src/registry/router/manhattan/obstacle-map.ts @@ -1,6 +1,5 @@ -import { ArrayExt } from '../../../util' -import { KeyValue } from '../../../types' -import { Rectangle, Point } from '../../../geometry' +import { ArrayExt, KeyValue } from '@antv/x6-common' +import { Rectangle, Point } from '@antv/x6-geometry' import { Cell, Edge, Model } from '../../../model' import { ResolvedOptions } from './options' diff --git a/packages/x6/src/registry/router/manhattan/options.ts b/packages/x6/src/registry/router/manhattan/options.ts index 070bee34761..6da1f8ae214 100644 --- a/packages/x6/src/registry/router/manhattan/options.ts +++ b/packages/x6/src/registry/router/manhattan/options.ts @@ -1,5 +1,5 @@ -import { NumberExt } from '../../../util' -import { Point, Rectangle, Angle } from '../../../geometry' +import { NumberExt } from '@antv/x6-common' +import { Point, Rectangle, Angle } from '@antv/x6-geometry' import { Edge } from '../../../model' import { EdgeView } from '../../../view' import { orth } from '../orth' diff --git a/packages/x6/src/registry/router/manhattan/router.ts b/packages/x6/src/registry/router/manhattan/router.ts index 66ed0eaa268..69b0bcd740b 100644 --- a/packages/x6/src/registry/router/manhattan/router.ts +++ b/packages/x6/src/registry/router/manhattan/router.ts @@ -1,6 +1,5 @@ -import { KeyValue } from '../../../types' -import { FunctionExt } from '../../../util' -import { Point, Rectangle } from '../../../geometry' +import { FunctionExt, KeyValue } from '@antv/x6-common' +import { Point, Rectangle } from '@antv/x6-geometry' import { EdgeView } from '../../../view' import { Router } from '../index' import { SortedSet } from './sorted-set' diff --git a/packages/x6/src/registry/router/manhattan/sorted-set.ts b/packages/x6/src/registry/router/manhattan/sorted-set.ts index 8cd154409d1..aac56d0385a 100644 --- a/packages/x6/src/registry/router/manhattan/sorted-set.ts +++ b/packages/x6/src/registry/router/manhattan/sorted-set.ts @@ -1,4 +1,4 @@ -import { ArrayExt } from '../../../util' +import { ArrayExt } from '@antv/x6-common' const OPEN = 1 const CLOSE = 2 diff --git a/packages/x6/src/registry/router/manhattan/util.ts b/packages/x6/src/registry/router/manhattan/util.ts index 4ab59f55d7a..01d6c474ae3 100644 --- a/packages/x6/src/registry/router/manhattan/util.ts +++ b/packages/x6/src/registry/router/manhattan/util.ts @@ -1,6 +1,5 @@ -import { KeyValue } from '../../../types' -import { Util } from '../../../global/util' -import { Point, Line, Angle, Rectangle } from '../../../geometry' +import { Point, Line, Angle, Rectangle, Util } from '@antv/x6-geometry' +import { KeyValue } from '@antv/x6-common' import { EdgeView } from '../../../view/edge' import { ResolvedOptions, Direction } from './options' diff --git a/packages/x6/src/registry/router/metro.ts b/packages/x6/src/registry/router/metro.ts index b02dece89c3..84f40a0ac85 100644 --- a/packages/x6/src/registry/router/metro.ts +++ b/packages/x6/src/registry/router/metro.ts @@ -1,5 +1,5 @@ -import { FunctionExt } from '../../util' -import { Point, Line, Angle } from '../../geometry' +import { FunctionExt } from '@antv/x6-common' +import { Point, Line, Angle } from '@antv/x6-geometry' import { ManhattanRouterOptions, resolve } from './manhattan/options' import { manhattan } from './manhattan/index' import { Router } from './index' diff --git a/packages/x6/src/registry/router/oneside.ts b/packages/x6/src/registry/router/oneside.ts index 28963f0b2d6..97b5da5d377 100644 --- a/packages/x6/src/registry/router/oneside.ts +++ b/packages/x6/src/registry/router/oneside.ts @@ -1,4 +1,4 @@ -import { NumberExt } from '../../util' +import { NumberExt } from '@antv/x6-common' import { PaddingOptions } from './util' import { Router } from './index' diff --git a/packages/x6/src/registry/router/orth.ts b/packages/x6/src/registry/router/orth.ts index 8b7dc509090..ea3c4354fcd 100644 --- a/packages/x6/src/registry/router/orth.ts +++ b/packages/x6/src/registry/router/orth.ts @@ -1,6 +1,6 @@ +import { ArrayExt } from '@antv/x6-common' +import { Point, Rectangle, Line, Angle } from '@antv/x6-geometry' import { Router } from './index' -import { ArrayExt } from '../../util' -import { Point, Rectangle, Line, Angle } from '../../geometry' import * as Util from './util' export interface OrthRouterOptions extends Util.PaddingOptions {} diff --git a/packages/x6/src/registry/router/util.ts b/packages/x6/src/registry/router/util.ts index f71cebb7998..91d47a70d8e 100644 --- a/packages/x6/src/registry/router/util.ts +++ b/packages/x6/src/registry/router/util.ts @@ -1,5 +1,5 @@ -import { NumberExt } from '../../util' -import { Point, Rectangle } from '../../geometry' +import { NumberExt } from '@antv/x6-common' +import { Point, Rectangle } from '@antv/x6-geometry' import { EdgeView } from '../../view/edge' export interface PaddingOptions { diff --git a/packages/x6/src/registry/tool/anchor.ts b/packages/x6/src/registry/tool/anchor.ts index df36c24267e..716def8e564 100644 --- a/packages/x6/src/registry/tool/anchor.ts +++ b/packages/x6/src/registry/tool/anchor.ts @@ -1,6 +1,6 @@ -import { Dom, FunctionExt } from '../../util' +import { Dom, FunctionExt } from '@antv/x6-common' +import { Point } from '@antv/x6-geometry' import { Attr } from '../attr' -import { Point } from '../../geometry' import { Edge } from '../../model/edge' import { Node } from '../../model/node' import { EdgeView } from '../../view/edge' @@ -128,7 +128,7 @@ class Anchor extends ToolsView.ToolItem { } } - protected onMouseDown(evt: JQuery.MouseDownEvent) { + protected onMouseDown(evt: Dom.MouseDownEvent) { if (this.guard(evt)) { return } @@ -163,7 +163,7 @@ class Anchor extends ToolsView.ToolItem { } } - protected onMouseMove(evt: JQuery.MouseMoveEvent) { + protected onMouseMove(evt: Dom.MouseMoveEvent) { const terminalType = this.type const edgeView = this.cellView const terminalView = edgeView.getTerminalView(terminalType) @@ -174,7 +174,7 @@ class Anchor extends ToolsView.ToolItem { const e = this.normalizeEvent(evt) const terminalCell = terminalView.cell const terminalMagnet = edgeView.getTerminalMagnet(terminalType)! - let coords = this.graph.clientToLocal(e.clientX, e.clientY) + let coords = this.graph.coord.clientToLocalPoint(e.clientX, e.clientY) const snapFn = this.options.snap if (typeof snapFn === 'function') { @@ -234,7 +234,7 @@ class Anchor extends ToolsView.ToolItem { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected onMouseUp(evt: JQuery.MouseUpEvent) { + protected onMouseUp(evt: Dom.MouseUpEvent) { this.graph.view.delegateEvents() this.undelegateDocumentEvents() this.blur() diff --git a/packages/x6/src/registry/tool/arrowhead.ts b/packages/x6/src/registry/tool/arrowhead.ts index ecf7a9af982..dc93f413b43 100644 --- a/packages/x6/src/registry/tool/arrowhead.ts +++ b/packages/x6/src/registry/tool/arrowhead.ts @@ -1,6 +1,6 @@ +import { Dom } from '@antv/x6-common' +import { Point } from '@antv/x6-geometry' import { Attr } from '../attr' -import { Dom } from '../../util' -import { Point } from '../../geometry' import { Edge } from '../../model/edge' import { EdgeView } from '../../view/edge' import { ToolsView } from '../../view/tool' @@ -53,7 +53,7 @@ class Arrowhead extends ToolsView.ToolItem { return this } - protected onMouseDown(evt: JQuery.MouseDownEvent) { + protected onMouseDown(evt: Dom.MouseDownEvent) { if (this.guard(evt)) { return } @@ -87,14 +87,14 @@ class Arrowhead extends ToolsView.ToolItem { this.focus() } - protected onMouseMove(evt: JQuery.MouseMoveEvent) { + protected onMouseMove(evt: Dom.MouseMoveEvent) { const e = this.normalizeEvent(evt) const coords = this.graph.snapToGrid(e.clientX, e.clientY) this.cellView.onMouseMove(e, coords.x, coords.y) this.update() } - protected onMouseUp(evt: JQuery.MouseUpEvent) { + protected onMouseUp(evt: Dom.MouseUpEvent) { this.undelegateDocumentEvents() const e = this.normalizeEvent(evt) const edgeView = this.cellView diff --git a/packages/x6/src/registry/tool/boundary.ts b/packages/x6/src/registry/tool/boundary.ts index 6080f36aa0b..b0e3d19ab65 100644 --- a/packages/x6/src/registry/tool/boundary.ts +++ b/packages/x6/src/registry/tool/boundary.ts @@ -1,5 +1,5 @@ +import { NumberExt, Dom } from '@antv/x6-common' import { Attr } from '../attr' -import { NumberExt, Dom } from '../../util' import { NodeView } from '../../view/node' import { EdgeView } from '../../view/edge' import { ToolsView } from '../../view/tool' diff --git a/packages/x6/src/registry/tool/button.ts b/packages/x6/src/registry/tool/button.ts index 218aea41348..5c8fbc14fc5 100644 --- a/packages/x6/src/registry/tool/button.ts +++ b/packages/x6/src/registry/tool/button.ts @@ -1,5 +1,5 @@ -import { Point } from '../../geometry' -import { Dom, NumberExt, FunctionExt } from '../../util' +import { Point } from '@antv/x6-geometry' +import { Dom, NumberExt, FunctionExt } from '@antv/x6-common' import { CellView } from '../../view/cell' import { NodeView } from '../../view/node' import { EdgeView } from '../../view/edge' @@ -111,7 +111,7 @@ export class Button extends ToolsView.ToolItem< return matrix } - protected onMouseDown(e: JQuery.MouseDownEvent) { + protected onMouseDown(e: Dom.MouseDownEvent) { if (this.guard(e)) { return } @@ -142,7 +142,7 @@ export namespace Button { onClick?: ( this: CellView, args: { - e: JQuery.MouseDownEvent + e: Dom.MouseDownEvent cell: Cell view: CellView btn: Button diff --git a/packages/x6/src/registry/tool/editor.ts b/packages/x6/src/registry/tool/editor.ts index a7fac554c6d..6a5941377b7 100644 --- a/packages/x6/src/registry/tool/editor.ts +++ b/packages/x6/src/registry/tool/editor.ts @@ -1,12 +1,13 @@ +import { Point } from '@antv/x6-geometry' +import { Dom, FunctionExt } from '@antv/x6-common' import { ToolsView } from '../../view/tool' import { Cell, Edge } from '../../model' import { CellView, NodeView, EdgeView } from '../../view' -import { Point } from '../../geometry' -import { Dom, FunctionExt } from '../../util' +import { Util } from '../../util' export class CellEditor extends ToolsView.ToolItem< NodeView | EdgeView, - CellEditor.CellEditorOptions & { event: JQuery.MouseEventBase } + CellEditor.CellEditorOptions & { event: Dom.EventObject } > { private editor: HTMLDivElement private labelIndex = -1 @@ -55,7 +56,7 @@ export class CellEditor extends ToolsView.ToolItem< const matrix = parent.getAttribute('transform') const { translation } = Dom.parseTransformString(matrix) pos = new Point(translation.tx, translation.ty) - minWidth = Dom.getBBox(target).width + minWidth = Util.getBBox(target).width } else { if (!this.options.labelAddable) { return this @@ -96,7 +97,7 @@ export class CellEditor extends ToolsView.ToolItem< return this } - onDocumentMouseDown(e: JQuery.MouseDownEvent) { + onDocumentMouseDown(e: Dom.MouseDownEvent) { if (e.target !== this.editor) { const cell = this.cell const value = this.editor.innerText.replace(/\n$/, '') || '' @@ -116,11 +117,11 @@ export class CellEditor extends ToolsView.ToolItem< } } - onDblClick(e: JQuery.DoubleClickEvent) { + onDblClick(e: Dom.DoubleClickEvent) { e.stopPropagation() } - onMouseDown(e: JQuery.MouseDownEvent) { + onMouseDown(e: Dom.MouseDownEvent) { e.stopPropagation() } diff --git a/packages/x6/src/registry/tool/index.ts b/packages/x6/src/registry/tool/index.ts index 827e7206096..caf75cde049 100644 --- a/packages/x6/src/registry/tool/index.ts +++ b/packages/x6/src/registry/tool/index.ts @@ -1,6 +1,5 @@ -import { KeyValue } from '../../types' +import { Registry, KeyValue } from '@antv/x6-common' import { ToolsView } from '../../view/tool' -import { Registry } from '../registry' import { Button } from './button' import { Boundary } from './boundary' import { Vertices } from './vertices' diff --git a/packages/x6/src/registry/tool/segments.ts b/packages/x6/src/registry/tool/segments.ts index a3c7cb3013c..89668ddbcec 100644 --- a/packages/x6/src/registry/tool/segments.ts +++ b/packages/x6/src/registry/tool/segments.ts @@ -1,13 +1,13 @@ -import { Dom, ObjectExt, FunctionExt } from '../../util' -import { Point, Line } from '../../geometry' -import { Graph } from '../../graph' -import { Edge } from '../../model/edge' +import { Dom, ObjectExt, FunctionExt } from '@antv/x6-common' +import { Point, Line } from '@antv/x6-geometry' import { View } from '../../view/view' -import { CellView } from '../../view/cell' -import { EdgeView } from '../../view/edge' import { ToolsView } from '../../view/tool' import * as Util from './util' import { Attr } from '../attr' +import { CellView } from '../../view/cell' +import { EdgeView } from '../../view/edge' +import { Edge } from '../../model/edge' +import { Graph } from '../../graph' export class Segments extends ToolsView.ToolItem { protected handles: Segments.Handle[] = [] @@ -55,13 +55,6 @@ export class Segments extends ToolsView.ToolItem { this.options.processHandle(handle) } - this.graph.hook.onToolItemCreated({ - name: 'segments', - cell: this.cell, - view: this.cellView, - tool: handle, - }) - this.updateHandle(handle, vertex, nextVertex) this.container.appendChild(handle.container) this.startHandleListening(handle) @@ -440,7 +433,7 @@ export namespace Segments { }) } - protected onMouseDown(evt: JQuery.MouseDownEvent) { + protected onMouseDown(evt: Dom.MouseDownEvent) { if (this.options.guard(evt)) { return } @@ -462,11 +455,11 @@ export namespace Segments { ) } - protected onMouseMove(evt: JQuery.MouseMoveEvent) { + protected onMouseMove(evt: Dom.MouseMoveEvent) { this.emit('changing', { e: evt, handle: this }) } - protected onMouseUp(evt: JQuery.MouseUpEvent) { + protected onMouseUp(evt: Dom.MouseUpEvent) { this.emit('changed', { e: evt, handle: this }) this.undelegateDocumentEvents() this.options.graph.view.delegateEvents() @@ -484,16 +477,16 @@ export namespace Segments { export namespace Handle { export interface Options { graph: Graph - guard: (evt: JQuery.TriggeredEvent) => boolean + guard: (evt: Dom.EventObject) => boolean attrs: Attr.SimpleAttrs | ((handle: Handle) => Attr.SimpleAttrs) index?: number axis?: 'x' | 'y' } export interface EventArgs { - change: { e: JQuery.MouseDownEvent; handle: Handle } - changing: { e: JQuery.MouseMoveEvent; handle: Handle } - changed: { e: JQuery.MouseUpEvent; handle: Handle } + change: { e: Dom.MouseDownEvent; handle: Handle } + changing: { e: Dom.MouseMoveEvent; handle: Handle } + changed: { e: Dom.MouseUpEvent; handle: Handle } } } } diff --git a/packages/x6/src/registry/tool/util.ts b/packages/x6/src/registry/tool/util.ts index 95e3757d1b1..1429b6e7cca 100644 --- a/packages/x6/src/registry/tool/util.ts +++ b/packages/x6/src/registry/tool/util.ts @@ -1,9 +1,9 @@ -import { FunctionExt } from '../../util' -import { Point } from '../../geometry' +import { FunctionExt } from '@antv/x6-common' +import { Point } from '@antv/x6-geometry' +import { ConnectionStrategy } from '../connection-strategy' import { Edge } from '../../model/edge' import { CellView } from '../../view/cell' import { EdgeView } from '../../view/edge' -import { ConnectionStrategy } from '../connection-strategy' export function getAnchor( this: EdgeView, diff --git a/packages/x6/src/registry/tool/vertices.ts b/packages/x6/src/registry/tool/vertices.ts index b6ffe3a4e03..07e0d976736 100644 --- a/packages/x6/src/registry/tool/vertices.ts +++ b/packages/x6/src/registry/tool/vertices.ts @@ -1,11 +1,12 @@ -import { Util } from '../../global/util' -import { Point } from '../../geometry' -import { Graph } from '../../graph' +import { Point } from '@antv/x6-geometry' +import { Dom } from '@antv/x6-common' +import { Config } from '../../config' import { View } from '../../view/view' +import { ToolsView } from '../../view/tool' import { EdgeView } from '../../view/edge' import { Edge } from '../../model/edge' -import { ToolsView } from '../../view/tool' import { Attr } from '../attr' +import { Graph } from '../../graph' export class Vertices extends ToolsView.ToolItem { protected handles: Vertices.Handle[] = [] @@ -60,7 +61,7 @@ export class Vertices extends ToolsView.ToolItem { const handle = createHandle({ index: i, graph: this.graph, - guard: (evt: JQuery.TriggeredEvent) => this.guard(evt), // eslint-disable-line no-loop-func + guard: (evt: Dom.EventObject) => this.guard(evt), // eslint-disable-line no-loop-func attrs: this.options.attrs || {}, }) @@ -68,13 +69,6 @@ export class Vertices extends ToolsView.ToolItem { processHandle(handle) } - this.graph.hook.onToolItemCreated({ - name: 'vertices', - cell: this.cell, - view: this.cellView, - tool: handle, - }) - handle.updatePosition(vertex.x, vertex.y) this.stamp(handle.container) this.container.appendChild(handle.container) @@ -139,7 +133,7 @@ export class Vertices extends ToolsView.ToolItem { } } - protected getMouseEventArgs(evt: T) { + protected getMouseEventArgs(evt: T) { const e = this.normalizeEvent(evt) const { x, y } = this.graph.snapToGrid(e.clientX!, e.clientY!) return { e, x, y } @@ -243,7 +237,7 @@ export class Vertices extends ToolsView.ToolItem { } } - protected onPathMouseDown(evt: JQuery.MouseDownEvent) { + protected onPathMouseDown(evt: Dom.MouseDownEvent) { const edgeView = this.cellView if ( @@ -327,7 +321,7 @@ export namespace Vertices { this.setAttrs({ cx: x, cy: y }) } - onMouseDown(evt: JQuery.MouseDownEvent) { + onMouseDown(evt: Dom.MouseDownEvent) { if (this.options.guard(evt)) { return } @@ -350,17 +344,17 @@ export namespace Vertices { this.emit('change', { e: evt, handle: this }) } - protected onMouseMove(evt: JQuery.MouseMoveEvent) { + protected onMouseMove(evt: Dom.MouseMoveEvent) { this.emit('changing', { e: evt, handle: this }) } - protected onMouseUp(evt: JQuery.MouseUpEvent) { + protected onMouseUp(evt: Dom.MouseUpEvent) { this.emit('changed', { e: evt, handle: this }) this.undelegateDocumentEvents() this.graph.view.delegateEvents() } - protected onDoubleClick(evt: JQuery.DoubleClickEvent) { + protected onDoubleClick(evt: Dom.DoubleClickEvent) { this.emit('remove', { e: evt, handle: this }) } } @@ -369,21 +363,21 @@ export namespace Vertices { export interface Options { graph: Graph index: number - guard: (evt: JQuery.TriggeredEvent) => boolean + guard: (evt: Dom.EventObject) => boolean attrs: Attr.SimpleAttrs | ((handle: Handle) => Attr.SimpleAttrs) } export interface EventArgs { - change: { e: JQuery.MouseDownEvent; handle: Handle } - changing: { e: JQuery.MouseMoveEvent; handle: Handle } - changed: { e: JQuery.MouseUpEvent; handle: Handle } - remove: { e: JQuery.DoubleClickEvent; handle: Handle } + change: { e: Dom.MouseDownEvent; handle: Handle } + changing: { e: Dom.MouseMoveEvent; handle: Handle } + changed: { e: Dom.MouseUpEvent; handle: Handle } + remove: { e: Dom.DoubleClickEvent; handle: Handle } } } } export namespace Vertices { - const pathClassName = Util.prefix('edge-tool-vertex-path') + const pathClassName = Config.prefix('edge-tool-vertex-path') Vertices.config({ name: 'vertices', diff --git a/packages/x6-next/src/renderer/index.ts b/packages/x6/src/renderer/index.ts similarity index 100% rename from packages/x6-next/src/renderer/index.ts rename to packages/x6/src/renderer/index.ts diff --git a/packages/x6-next/src/renderer/queueJob.ts b/packages/x6/src/renderer/queueJob.ts similarity index 100% rename from packages/x6-next/src/renderer/queueJob.ts rename to packages/x6/src/renderer/queueJob.ts diff --git a/packages/x6-next/src/renderer/renderer.ts b/packages/x6/src/renderer/renderer.ts similarity index 100% rename from packages/x6-next/src/renderer/renderer.ts rename to packages/x6/src/renderer/renderer.ts diff --git a/packages/x6-next/src/renderer/scheduler.ts b/packages/x6/src/renderer/scheduler.ts similarity index 100% rename from packages/x6-next/src/renderer/scheduler.ts rename to packages/x6/src/renderer/scheduler.ts diff --git a/packages/x6/src/shape/base.ts b/packages/x6/src/shape/base.ts index bd02165e767..8c7cc86d24a 100644 --- a/packages/x6/src/shape/base.ts +++ b/packages/x6/src/shape/base.ts @@ -1,5 +1,5 @@ -import { Node } from '../model/node' -import { ObjectExt } from '../util' +import { ObjectExt } from '@antv/x6-common' +import { Node } from '../model' export class Base< Properties extends Node.Properties = Node.Properties, diff --git a/packages/x6/src/shape/basic/circle.ts b/packages/x6/src/shape/basic/circle.ts deleted file mode 100644 index 7c4d9430565..00000000000 --- a/packages/x6/src/shape/basic/circle.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createShape } from './util' - -export const Circle = createShape('circle', { - width: 60, - height: 60, - attrs: { - circle: { - r: 30, - cx: 30, - cy: 30, - }, - }, -}) diff --git a/packages/x6/src/shape/basic/cylinder.ts b/packages/x6/src/shape/basic/cylinder.ts deleted file mode 100644 index 6ff231af1d0..00000000000 --- a/packages/x6/src/shape/basic/cylinder.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Path } from './path' -import { createShape } from './util' - -export const Cylinder = createShape( - 'cylinder', - { - width: 40, - height: 40, - attrs: { - path: { - fill: '#FFFFFF', - stroke: '#cbd2d7', - strokeWidth: 3, - d: [ - 'M 0 10 C 10 5, 30 5, 40 10 C 30 15, 10 15, 0 10', - 'L 0 20', - 'C 10 25, 30 25, 40 20', - 'L 40 10', - ].join(' '), - }, - text: { - refY: 0.7, - refDy: null, - fill: '#435460', - }, - }, - }, - { - parent: Path, - ignoreMarkup: true, - }, -) diff --git a/packages/x6/src/shape/basic/ellipse.ts b/packages/x6/src/shape/basic/ellipse.ts deleted file mode 100644 index 12247e66a1d..00000000000 --- a/packages/x6/src/shape/basic/ellipse.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createShape } from './util' - -export const Ellipse = createShape('ellipse', { - width: 60, - height: 40, - attrs: { - ellipse: { - rx: 30, - ry: 20, - cx: 30, - cy: 20, - }, - }, -}) diff --git a/packages/x6/src/shape/basic/image.ts b/packages/x6/src/shape/basic/image.ts deleted file mode 100644 index 1bae0da0f8f..00000000000 --- a/packages/x6/src/shape/basic/image.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createShape, getImageUrlHook } from './util' - -export const Image = createShape('image', { - attrs: { - text: { - refY: null, - refDy: 16, - }, - }, - propHooks: getImageUrlHook(), -}) diff --git a/packages/x6/src/shape/basic/index.ts b/packages/x6/src/shape/basic/index.ts deleted file mode 100644 index b59d85f4992..00000000000 --- a/packages/x6/src/shape/basic/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './rect' -export * from './circle' -export * from './ellipse' -export * from './polygon' -export * from './polyline' -export * from './image' -export * from './path' -export * from './rhombus' -export * from './cylinder' -export * from './text' -export * from './text-block' diff --git a/packages/x6/src/shape/basic/path.ts b/packages/x6/src/shape/basic/path.ts deleted file mode 100644 index df02864fa5b..00000000000 --- a/packages/x6/src/shape/basic/path.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ObjectExt } from '../../util' -import { createShape } from './util' - -export const Path = createShape('path', { - width: 60, - height: 60, - attrs: { - text: { - ref: 'path', - refY: null, - refDy: 16, - }, - }, - propHooks(metadata) { - const { d, ...others } = metadata - if (d != null) { - ObjectExt.setByPath(others, 'attrs/path/d', d) - } - return others - }, -}) diff --git a/packages/x6/src/shape/basic/polygon.ts b/packages/x6/src/shape/basic/polygon.ts deleted file mode 100644 index ee845c806fe..00000000000 --- a/packages/x6/src/shape/basic/polygon.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createShape } from './util' - -export const Polygon = createShape('polygon', { - width: 60, - height: 40, - attrs: { - text: { - refY: null, - refDy: 16, - }, - }, -}) diff --git a/packages/x6/src/shape/basic/polyline.ts b/packages/x6/src/shape/basic/polyline.ts deleted file mode 100644 index 952644d5bdc..00000000000 --- a/packages/x6/src/shape/basic/polyline.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createShape } from './util' - -export const Polyline = createShape('polyline', { - width: 60, - height: 40, - attrs: { - text: { - refY: null, - refDy: 16, - }, - }, -}) diff --git a/packages/x6/src/shape/basic/rect.ts b/packages/x6/src/shape/basic/rect.ts deleted file mode 100644 index 767953dc4c9..00000000000 --- a/packages/x6/src/shape/basic/rect.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createShape } from './util' - -export const Rect = createShape('rect', { - attrs: { - rect: { - width: 100, - height: 60, - }, - }, -}) diff --git a/packages/x6/src/shape/basic/rhombus.ts b/packages/x6/src/shape/basic/rhombus.ts deleted file mode 100644 index 8d859c0acb0..00000000000 --- a/packages/x6/src/shape/basic/rhombus.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Path } from './path' -import { createShape } from './util' - -export const Rhombus = createShape( - 'rhombus', - { - d: 'M 30 0 L 60 30 30 60 0 30 z', - attrs: { - text: { - refY: 0.5, - refDy: null, - }, - }, - }, - { - parent: Path, - ignoreMarkup: true, - }, -) diff --git a/packages/x6/src/shape/basic/text-block.ts b/packages/x6/src/shape/basic/text-block.ts deleted file mode 100644 index b1c9b70096a..00000000000 --- a/packages/x6/src/shape/basic/text-block.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { Platform, StringExt, ObjectExt, Dom } from '../../util' -import { Size } from '../../types' -import { Attr } from '../../registry' -import { Node } from '../../model' -import { Store } from '../../model/store' -import { NodeView } from '../../view' -import { getName } from './util' - -const contentSelector = '.text-block-content' -const registryName = getName('text-block') - -export class TextBlock< - Properties extends TextBlock.Properties = TextBlock.Properties, -> extends Node { - public readonly store: Store - - get content() { - return this.getContent() - } - - set content(val: string) { - this.setContent(val) - } - - getContent() { - return this.store.get('content', '') - } - - setContent(content?: string, options: Node.SetOptions = {}) { - this.store.set('content', content, options) - } - - protected setup() { - super.setup() - this.store.on('change:*', (metadata) => { - const key = metadata.key - if (key === 'content') { - this.updateContent(this.getContent()) - } else if (key === 'size') { - this.updateSize(this.getSize()) - } - }) - - this.updateSize(this.getSize()) - this.updateContent(this.getContent()) - } - - protected updateSize(size: Size) { - if (Platform.SUPPORT_FOREIGNOBJECT) { - this.setAttrs({ - foreignObject: { ...size }, - [contentSelector]: { - style: { ...size }, - }, - }) - } - } - - protected updateContent(content?: string) { - if (Platform.SUPPORT_FOREIGNOBJECT) { - this.setAttrs({ - [contentSelector]: { - html: content ? StringExt.sanitizeHTML(content) : '', - }, - }) - } else { - this.setAttrs({ - [contentSelector]: { - text: content, - }, - }) - } - } -} - -export namespace TextBlock { - export interface Properties extends Node.Properties { - content?: string - } -} - -export namespace TextBlock { - TextBlock.config({ - type: registryName, - view: registryName, - markup: [ - '', - '', - Platform.SUPPORT_FOREIGNOBJECT - ? [ - ``, - ``, - `
`, - ``, - ``, - ].join('') - : ``, - '', - ].join(''), - attrs: { - '.': { - fill: '#ffffff', - stroke: 'none', - }, - rect: { - fill: '#ffffff', - stroke: '#000000', - width: 80, - height: 100, - }, - text: { - fill: '#000000', - fontSize: 14, - fontFamily: 'Arial, helvetica, sans-serif', - }, - body: { - style: { - background: 'transparent', - position: 'static', - margin: 0, - padding: 0, - }, - }, - foreignObject: { - style: { - overflow: 'hidden', - }, - }, - [contentSelector]: { - refX: 0.5, - refY: 0.5, - yAlign: 'middle', - xAlign: 'middle', - style: { - textAlign: 'center', - verticalAlign: 'middle', - display: 'table-cell', - padding: '0 5px', - margin: 0, - }, - }, - }, - }) - - Node.registry.register(registryName, TextBlock) -} - -export namespace TextBlock { - const contentAction = 'content' as any - - export class View extends NodeView { - confirmUpdate(flag: number, options: any = {}) { - let ret = super.confirmUpdate(flag, options) - if (this.hasAction(ret, contentAction)) { - this.updateContent() - ret = this.removeAction(ret, contentAction) - } - return ret - } - - update(partialAttrs?: Attr.CellAttrs) { - if (Platform.SUPPORT_FOREIGNOBJECT) { - super.update(partialAttrs) - } else { - const node = this.cell - const attrs = { ...(partialAttrs || node.getAttrs()) } - delete attrs[contentSelector] - super.update(attrs) - if (!partialAttrs || ObjectExt.has(partialAttrs, contentSelector)) { - this.updateContent(partialAttrs) - } - } - } - - updateContent(partialAttrs?: Attr.CellAttrs) { - if (Platform.SUPPORT_FOREIGNOBJECT) { - super.update(partialAttrs) - } else { - const node = this.cell - const textAttrs = (partialAttrs || node.getAttrs())[contentSelector] - - // Break the text to fit the node size taking into - // account the attributes set on the node. - const text = Dom.breakText( - node.getContent(), - node.getSize(), - textAttrs, - { - svgDocument: this.graph.view.svg, - }, - ) - - const attrs = { - [contentSelector]: ObjectExt.merge({}, textAttrs, { text }), - } - - super.update(attrs) - } - } - } - - export namespace View { - View.config({ - bootstrap: ['render', contentAction], - actions: Platform.SUPPORT_FOREIGNOBJECT - ? {} - : { - size: contentAction, - content: contentAction, - }, - }) - - NodeView.registry.register(registryName, View) - } -} diff --git a/packages/x6/src/shape/basic/text.ts b/packages/x6/src/shape/basic/text.ts deleted file mode 100644 index 3faa8a92a5a..00000000000 --- a/packages/x6/src/shape/basic/text.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { NodeView } from '../../view' -import { getName, createShape } from './util' - -const viewName = getName('text') - -export class Text extends createShape( - 'text', - { - view: viewName, - attrs: { - text: { - fontSize: 18, - fill: '#000000', - stroke: null, - refX: 0.5, - refY: 0.5, - }, - }, - }, - { noText: true }, -) {} - -export namespace Text { - export class View extends NodeView { - confirmUpdate(flag: number, options: any = {}) { - let ret = super.confirmUpdate(flag, options) - if (this.hasAction(ret, 'scale')) { - this.resize() - ret = this.removeAction(ret, 'scale') - } - return ret - } - } - - View.config({ - actions: { - attrs: ['scale'], - }, - }) - - NodeView.registry.register(viewName, View) -} diff --git a/packages/x6/src/shape/basic/util.ts b/packages/x6/src/shape/basic/util.ts deleted file mode 100644 index f7970666966..00000000000 --- a/packages/x6/src/shape/basic/util.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { ObjectExt } from '../../util' -import { Node } from '../../model/node' -import { Cell } from '../../model/cell' -import { Base } from '../base' - -export function getMarkup(tagName: string, noText = false) { - return `<${tagName}/>${ - noText ? '' : '' - }` -} - -export function getName(name: string) { - return `basic.${name}` -} - -export function getImageUrlHook(attrName = 'xlink:href') { - const hook: Cell.PropHook = (metadata) => { - const { imageUrl, imageWidth, imageHeight, ...others } = metadata - if (imageUrl != null || imageWidth != null || imageHeight != null) { - const apply = () => { - if (others.attrs) { - const image = others.attrs.image - if (imageUrl != null) { - image[attrName] = imageUrl - } - if (imageWidth != null) { - image.width = imageWidth - } - if (imageHeight != null) { - image.height = imageHeight - } - others.attrs.image = image - } - } - - if (others.attrs) { - if (others.attrs.image == null) { - others.attrs.image = {} - } - apply() - } else { - others.attrs = { - image: {}, - } - apply() - } - } - - return others - } - - return hook -} - -export function createShape( - shape: string, - config: Node.Config, - options: { - noText?: boolean - ignoreMarkup?: boolean - parent?: Node.Definition | typeof Base - } = {}, -) { - const name = getName(shape) - const defaults: Node.Config = { - constructorName: name, - attrs: { - '.': { - fill: '#ffffff', - stroke: 'none', - }, - [shape]: { - fill: '#ffffff', - stroke: '#000000', - }, - }, - } - - if (!options.ignoreMarkup) { - defaults.markup = getMarkup(shape, options.noText === true) - } - - const base = options.parent || Base - return base.define( - ObjectExt.merge(defaults, config, { shape: name }), - ) as typeof Base -} diff --git a/packages/x6-next/src/shape/circle.ts b/packages/x6/src/shape/circle.ts similarity index 100% rename from packages/x6-next/src/shape/circle.ts rename to packages/x6/src/shape/circle.ts diff --git a/packages/x6-next/src/shape/edge.ts b/packages/x6/src/shape/edge.ts similarity index 100% rename from packages/x6-next/src/shape/edge.ts rename to packages/x6/src/shape/edge.ts diff --git a/packages/x6-next/src/shape/ellipse.ts b/packages/x6/src/shape/ellipse.ts similarity index 100% rename from packages/x6-next/src/shape/ellipse.ts rename to packages/x6/src/shape/ellipse.ts diff --git a/packages/x6-next/src/shape/image.ts b/packages/x6/src/shape/image.ts similarity index 100% rename from packages/x6-next/src/shape/image.ts rename to packages/x6/src/shape/image.ts diff --git a/packages/x6/src/shape/index.ts b/packages/x6/src/shape/index.ts index 5adeea72872..8410e167dec 100644 --- a/packages/x6/src/shape/index.ts +++ b/packages/x6/src/shape/index.ts @@ -1,4 +1,11 @@ -import * as BasicShape from './basic' -import * as Shape from './standard' - -export { BasicShape, Shape } +export * from './rect' +export * from './edge' +export * from './rect' +export * from './ellipse' +export * from './polygon' +export * from './polyline' +export * from './path' +export * from './text-block' +export * from './image' +export * from './edge' +export * from './circle' diff --git a/packages/x6-next/src/shape/path.ts b/packages/x6/src/shape/path.ts similarity index 100% rename from packages/x6-next/src/shape/path.ts rename to packages/x6/src/shape/path.ts diff --git a/packages/x6-next/src/shape/poly.ts b/packages/x6/src/shape/poly.ts similarity index 100% rename from packages/x6-next/src/shape/poly.ts rename to packages/x6/src/shape/poly.ts diff --git a/packages/x6-next/src/shape/polygon.ts b/packages/x6/src/shape/polygon.ts similarity index 100% rename from packages/x6-next/src/shape/polygon.ts rename to packages/x6/src/shape/polygon.ts diff --git a/packages/x6-next/src/shape/polyline.ts b/packages/x6/src/shape/polyline.ts similarity index 100% rename from packages/x6-next/src/shape/polyline.ts rename to packages/x6/src/shape/polyline.ts diff --git a/packages/x6-next/src/shape/rect.ts b/packages/x6/src/shape/rect.ts similarity index 100% rename from packages/x6-next/src/shape/rect.ts rename to packages/x6/src/shape/rect.ts diff --git a/packages/x6/src/shape/standard/circle.ts b/packages/x6/src/shape/standard/circle.ts deleted file mode 100644 index 7f1e6814e69..00000000000 --- a/packages/x6/src/shape/standard/circle.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createShape } from './util' - -export const Circle = createShape('circle', { - attrs: { - body: { - refCx: '50%', - refCy: '50%', - refR: '50%', - }, - }, -}) diff --git a/packages/x6/src/shape/standard/cylinder.ts b/packages/x6/src/shape/standard/cylinder.ts deleted file mode 100644 index be3ec4d4e82..00000000000 --- a/packages/x6/src/shape/standard/cylinder.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { NumberExt } from '../../util' -import { Base } from '../base' - -const CYLINDER_TILT = 10 - -export const Cylinder = Base.define({ - shape: 'cylinder', - overwrite: true, - markup: [ - { - tagName: 'path', - selector: 'body', - }, - { - tagName: 'ellipse', - selector: 'top', - }, - { - tagName: 'text', - selector: 'label', - }, - ], - attrs: { - body: { - ...Base.bodyAttr, - lateral: CYLINDER_TILT, - }, - top: { - ...Base.bodyAttr, - refCx: '50%', - refRx: '50%', - cy: CYLINDER_TILT, - ry: CYLINDER_TILT, - }, - }, - attrHooks: { - lateral: { - set(t: number | string, { refBBox }) { - const isPercentage = NumberExt.isPercentage(t) - if (isPercentage) { - // eslint-disable-next-line - t = parseFloat(t as string) / 100 - } - - const x = refBBox.x - const y = refBBox.y - const w = refBBox.width - const h = refBBox.height - - // curve control point variables - const rx = w / 2 - const ry = isPercentage ? h * (t as number) : (t as number) - - const kappa = 0.551784 - const cx = kappa * rx - const cy = kappa * ry - - // shape variables - const xLeft = x - const xCenter = x + w / 2 - const xRight = x + w - - const ySideTop = y + ry - const yCurveTop = ySideTop - ry - const ySideBottom = y + h - ry - const yCurveBottom = y + h - - // return calculated shape - const data = [ - 'M', - xLeft, - ySideTop, - 'L', - xLeft, - ySideBottom, - 'C', - x, - ySideBottom + cy, - xCenter - cx, - yCurveBottom, - xCenter, - yCurveBottom, - 'C', - xCenter + cx, - yCurveBottom, - xRight, - ySideBottom + cy, - xRight, - ySideBottom, - 'L', - xRight, - ySideTop, - 'C', - xRight, - ySideTop - cy, - xCenter + cx, - yCurveTop, - xCenter, - yCurveTop, - 'C', - xCenter - cx, - yCurveTop, - xLeft, - ySideTop - cy, - xLeft, - ySideTop, - 'Z', - ] - - return { d: data.join(' ') } - }, - }, - }, - knob: { - enabled: true, - position({ node }) { - const lateral = node.attr('body/lateral') - return { x: 0, y: lateral } - }, - onMouseMove({ node, data, deltaY }) { - if (deltaY !== 0) { - const bbox = node.getBBox() - const previous = node.attr('body/lateral') - - if (data.round == null) { - data.round = previous - } - const min = 0 - const max = bbox.height / 2 - const current = NumberExt.clamp(data.round + deltaY, min, max) - if (current !== previous) { - node.attr({ - body: { lateral: current }, - top: { - cy: current, - ry: current, - }, - }) - } - } - }, - }, -}) diff --git a/packages/x6/src/shape/standard/edge-doubled.ts b/packages/x6/src/shape/standard/edge-doubled.ts deleted file mode 100644 index 58f25d60910..00000000000 --- a/packages/x6/src/shape/standard/edge-doubled.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Edge } from '../../model/edge' - -export const DoubleEdge = Edge.define({ - shape: 'double-edge', - markup: [ - { - tagName: 'path', - selector: 'outline', - attrs: { - fill: 'none', - }, - }, - { - tagName: 'path', - selector: 'line', - attrs: { - fill: 'none', - cursor: 'pointer', - }, - }, - ], - attrs: { - line: { - connection: true, - stroke: '#dddddd', - strokeWidth: 4, - strokeLinejoin: 'round', - targetMarker: { - tagName: 'path', - stroke: '#000000', - d: 'M 10 -3 10 -10 -2 0 10 10 10 3', - }, - }, - outline: { - connection: true, - stroke: '#000000', - strokeWidth: 6, - strokeLinejoin: 'round', - }, - }, -}) diff --git a/packages/x6/src/shape/standard/edge-shadow.ts b/packages/x6/src/shape/standard/edge-shadow.ts deleted file mode 100644 index d4c7ebe797c..00000000000 --- a/packages/x6/src/shape/standard/edge-shadow.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Edge } from '../../model/edge' - -export const ShadowEdge = Edge.define({ - shape: 'shadow-edge', - markup: [ - { - tagName: 'path', - selector: 'shadow', - attrs: { - fill: 'none', - }, - }, - { - tagName: 'path', - selector: 'line', - attrs: { - fill: 'none', - cursor: 'pointer', - }, - }, - ], - attrs: { - line: { - connection: true, - stroke: '#FF0000', - strokeWidth: 20, - strokeLinejoin: 'round', - targetMarker: { - name: 'path', - stroke: 'none', - d: 'M 0 -10 -10 0 0 10 z', - offsetX: -5, - }, - sourceMarker: { - name: 'path', - stroke: 'none', - d: 'M -10 -10 0 0 -10 10 0 10 0 -10 z', - offsetX: -5, - }, - }, - shadow: { - connection: true, - refX: 3, - refY: 6, - stroke: '#000000', - strokeOpacity: 0.2, - strokeWidth: 20, - strokeLinejoin: 'round', - targetMarker: { - name: 'path', - d: 'M 0 -10 -10 0 0 10 z', - stroke: 'none', - offsetX: -5, - }, - sourceMarker: { - name: 'path', - stroke: 'none', - d: 'M -10 -10 0 0 -10 10 0 10 0 -10 z', - offsetX: -5, - }, - }, - }, -}) diff --git a/packages/x6/src/shape/standard/edge.ts b/packages/x6/src/shape/standard/edge.ts deleted file mode 100644 index 5025e811482..00000000000 --- a/packages/x6/src/shape/standard/edge.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Edge as EdgeBase } from '../../model/edge' - -export const Edge = EdgeBase.define({ - shape: 'edge', - markup: [ - { - tagName: 'path', - selector: 'wrap', - groupSelector: 'lines', - attrs: { - fill: 'none', - cursor: 'pointer', - stroke: 'transparent', - strokeLinecap: 'round', - }, - }, - { - tagName: 'path', - selector: 'line', - groupSelector: 'lines', - attrs: { - fill: 'none', - pointerEvents: 'none', - }, - }, - ], - attrs: { - lines: { - connection: true, - strokeLinejoin: 'round', - }, - wrap: { - strokeWidth: 10, - }, - line: { - stroke: '#333', - strokeWidth: 2, - targetMarker: 'classic', - }, - }, -}) diff --git a/packages/x6/src/shape/standard/ellipse.ts b/packages/x6/src/shape/standard/ellipse.ts deleted file mode 100644 index 92bbde60599..00000000000 --- a/packages/x6/src/shape/standard/ellipse.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createShape } from './util' - -export const Ellipse = createShape('ellipse', { - attrs: { - body: { - refCx: '50%', - refCy: '50%', - refRx: '50%', - refRy: '50%', - }, - }, -}) diff --git a/packages/x6/src/shape/standard/empty.ts b/packages/x6/src/shape/standard/empty.ts deleted file mode 100644 index e5ae6bf11f7..00000000000 --- a/packages/x6/src/shape/standard/empty.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Base } from '../base' - -export const Empty = Base.define({ - shape: 'empty', -}) diff --git a/packages/x6/src/shape/standard/html.ts b/packages/x6/src/shape/standard/html.ts deleted file mode 100644 index b0314b6f732..00000000000 --- a/packages/x6/src/shape/standard/html.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Registry } from '../../registry' -import { Markup } from '../../view' -import { Node } from '../../model/node' -import { NodeView } from '../../view/node' -import { Graph } from '../../graph/graph' -import { Base } from '../base' - -export class HTML< - Properties extends HTML.Properties = HTML.Properties, -> extends Base { - get html() { - return this.getHTML() - } - - set html(val: HTML.Component | HTML.UpdatableComponent | null | undefined) { - this.setHTML(val) - } - - getHTML() { - return this.store.get< - HTML.Component | HTML.UpdatableComponent | null | undefined - >('html') - } - - setHTML( - html: HTML.Component | HTML.UpdatableComponent | null | undefined, - options: Node.SetOptions = {}, - ) { - if (html == null) { - this.removeHTML(options) - } else { - this.store.set('html', html, options) - } - - return this - } - - removeHTML(options: Node.SetOptions = {}) { - return this.store.remove('html', options) - } -} - -export namespace HTML { - export type Elem = string | HTMLElement | null | undefined - export type UnionElem = Elem | ((this: Graph, node: Node) => Elem) - export interface Properties extends Node.Properties { - html?: - | UnionElem - | { - render: UnionElem - shouldComponentUpdate?: - | boolean - | ((this: Graph, node: Node) => boolean) - } - } -} - -export namespace HTML { - export class View extends NodeView { - protected init() { - super.init() - this.cell.on('change:*', () => { - const shouldUpdate = this.graph.hook.shouldUpdateHTMLComponent( - this.cell, - ) - if (shouldUpdate) { - this.renderHTMLComponent() - } - }) - } - - confirmUpdate(flag: number) { - const ret = super.confirmUpdate(flag) - return this.handleAction(ret, View.action, () => - this.renderHTMLComponent(), - ) - } - - protected renderHTMLComponent() { - const container = this.selectors.foContent - if (container) { - const $wrap = this.$(container).empty() - const component = this.graph.hook.getHTMLComponent(this.cell) - if (component) { - if (typeof component === 'string') { - $wrap.html(component) - } else { - $wrap.append(component) - } - } - } - } - } - - export namespace View { - export const action = 'html' as any - - View.config({ - bootstrap: [action], - actions: { - html: action, - }, - }) - - NodeView.registry.register('html-view', View) - } -} - -export namespace HTML { - HTML.config({ - view: 'html-view', - markup: [ - { - tagName: 'rect', - selector: 'body', - }, - { - ...Markup.getForeignObjectMarkup(), - }, - { - tagName: 'text', - selector: 'label', - }, - ], - attrs: { - body: { - fill: 'none', - stroke: 'none', - refWidth: '100%', - refHeight: '100%', - }, - fo: { - refWidth: '100%', - refHeight: '100%', - }, - }, - }) - - Node.registry.register('html', HTML) -} - -export namespace HTML { - export type Component = - | HTMLElement - | string - | ((this: Graph, node: HTML) => HTMLElement | string) - - export type UpdatableComponent = { - render: Component - shouldComponentUpdate: boolean | ((this: Graph, node: HTML) => boolean) - } - - export const componentRegistry = Registry.create< - Component | UpdatableComponent - >({ - type: 'html componnet', - }) -} diff --git a/packages/x6/src/shape/standard/image-bordered.ts b/packages/x6/src/shape/standard/image-bordered.ts deleted file mode 100644 index ea1c8ef4c43..00000000000 --- a/packages/x6/src/shape/standard/image-bordered.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { getImageUrlHook } from '../basic/util' -import { createShape } from './util' - -export const BorderedImage = createShape('image-bordered', { - markup: [ - { - tagName: 'rect', - selector: 'background', - attrs: { - stroke: 'none', - }, - }, - { - tagName: 'image', - selector: 'image', - }, - { - tagName: 'rect', - selector: 'border', - attrs: { - fill: 'none', - }, - }, - { - tagName: 'text', - selector: 'label', - }, - ], - attrs: { - background: { - refWidth: -1, - refHeight: -1, - x: 0.5, - y: 0.5, - fill: '#ffffff', - }, - border: { - refWidth: '100%', - refHeight: '100%', - stroke: '#333333', - strokeWidth: 2, - }, - image: { - // xlinkHref: '[URL]' - refWidth: -1, - refHeight: -1, - x: 0.5, - y: 0.5, - }, - }, - propHooks: getImageUrlHook(), -}) diff --git a/packages/x6/src/shape/standard/image-embedded.ts b/packages/x6/src/shape/standard/image-embedded.ts deleted file mode 100644 index b642b38b943..00000000000 --- a/packages/x6/src/shape/standard/image-embedded.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { getImageUrlHook } from '../basic/util' -import { createShape } from './util' - -export const EmbeddedImage = createShape('image-embedded', { - markup: [ - { - tagName: 'rect', - selector: 'body', - }, - { - tagName: 'image', - selector: 'image', - }, - { - tagName: 'text', - selector: 'label', - }, - ], - attrs: { - body: { - refWidth: '100%', - refHeight: '100%', - stroke: '#333333', - fill: '#FFFFFF', - strokeWidth: 2, - }, - image: { - // xlinkHref: '[URL]' - refWidth: '30%', - refHeight: -20, - x: 10, - y: 10, - preserveAspectRatio: 'xMidYMin', - }, - }, - propHooks: getImageUrlHook(), -}) diff --git a/packages/x6/src/shape/standard/image-inscribed.ts b/packages/x6/src/shape/standard/image-inscribed.ts deleted file mode 100644 index a3cb56efeb4..00000000000 --- a/packages/x6/src/shape/standard/image-inscribed.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { getImageUrlHook } from '../basic/util' -import { createShape } from './util' - -export const InscribedImage = createShape('image-inscribed', { - propHooks: getImageUrlHook(), - markup: [ - { - tagName: 'ellipse', - selector: 'background', - }, - { - tagName: 'image', - selector: 'image', - }, - { - tagName: 'ellipse', - selector: 'border', - attrs: { - fill: 'none', - }, - }, - { - tagName: 'text', - selector: 'label', - }, - ], - attrs: { - border: { - refRx: '50%', - refRy: '50%', - refCx: '50%', - refCy: '50%', - stroke: '#333333', - strokeWidth: 2, - }, - background: { - refRx: '50%', - refRy: '50%', - refCx: '50%', - refCy: '50%', - fill: '#ffffff', - }, - image: { - // The image corners touch the border when its size is Math.sqrt(2) / 2 = 0.707.. ~= 70% - refWidth: '68%', - refHeight: '68%', - // The image offset is calculated as (100% - 68%) / 2 - refX: '16%', - refY: '16%', - preserveAspectRatio: 'xMidYMid', - // xlinkHref: '[URL]' - }, - // label: { - // refX: '50%', - // refY: '100%', - // refY2: 10, - // textAnchor: 'middle', - // textVerticalAnchor: 'top', - // }, - }, -}) diff --git a/packages/x6/src/shape/standard/image.ts b/packages/x6/src/shape/standard/image.ts deleted file mode 100644 index c66f6e86612..00000000000 --- a/packages/x6/src/shape/standard/image.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { getImageUrlHook } from '../basic/util' -import { createShape } from './util' - -export const Image = createShape( - 'image', - { - attrs: { - image: { - refWidth: '100%', - refHeight: '100%', - }, - }, - propHooks: getImageUrlHook(), - }, - { - selector: 'image', - }, -) diff --git a/packages/x6/src/shape/standard/index.ts b/packages/x6/src/shape/standard/index.ts deleted file mode 100644 index a4a5945305f..00000000000 --- a/packages/x6/src/shape/standard/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -export * from './empty' -export * from './rect' -export * from './rect-headered' -export * from './circle' -export * from './cylinder' -export * from './ellipse' -export * from './polygon' -export * from './polyline' -export * from './path' -export * from './text-block' -export * from './image' -export * from './image-bordered' -export * from './image-embedded' -export * from './image-inscribed' - -export * from './edge' -export * from './edge-shadow' -export * from './edge-doubled' - -export * from './html' diff --git a/packages/x6/src/shape/standard/path.ts b/packages/x6/src/shape/standard/path.ts deleted file mode 100644 index a19e400cc5e..00000000000 --- a/packages/x6/src/shape/standard/path.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ObjectExt } from '../../util' -import { Base } from '../base' - -export const Path = Base.define({ - shape: 'path', - markup: [ - { - tagName: 'rect', - selector: 'bg', - }, - { - tagName: 'path', - selector: 'body', - }, - { - tagName: 'text', - selector: 'label', - }, - ], - attrs: { - bg: { - refWidth: '100%', - refHeight: '100%', - fill: 'none', - stroke: 'none', - pointerEvents: 'all', - }, - body: { - fill: 'none', - stroke: '#000', - strokeWidth: 2, - }, - }, - propHooks(metadata) { - const { path, ...others } = metadata - if (path) { - ObjectExt.setByPath(others, 'attrs/body/refD', path) - } - - return others - }, -}) diff --git a/packages/x6/src/shape/standard/poly.ts b/packages/x6/src/shape/standard/poly.ts deleted file mode 100644 index 3654c5d8ffb..00000000000 --- a/packages/x6/src/shape/standard/poly.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Base } from '../base' -import { Point } from '../../geometry' -import { Node } from '../../model/node' -import { ObjectExt } from '../../util' - -export class Poly extends Base { - get points() { - return this.getPoints() - } - - set points(pts: string | undefined | null) { - this.setPoints(pts) - } - - getPoints() { - return this.getAttrByPath('body/refPoints') - } - - setPoints( - points?: string | Point.PointLike[] | Point.PointData[] | null, - options?: Node.SetOptions, - ) { - if (points == null) { - this.removePoints() - } else { - this.setAttrByPath('body/refPoints', Poly.pointsToString(points), options) - } - - return this - } - - removePoints() { - this.removeAttrByPath('body/refPoints') - return this - } -} - -export namespace Poly { - export function pointsToString( - points: Point.PointLike[] | Point.PointData[] | string, - ) { - return typeof points === 'string' - ? points - : (points as Point.PointLike[]) - .map((p) => { - if (Array.isArray(p)) { - return p.join(',') - } - if (Point.isPointLike(p)) { - return `${p.x}, ${p.y}` - } - return '' - }) - .join(' ') - } - - Poly.config({ - propHooks(metadata) { - const { points, ...others } = metadata - if (points) { - const data = pointsToString(points) - if (data) { - ObjectExt.setByPath(others, 'attrs/body/refPoints', data) - } - } - return others - }, - }) -} diff --git a/packages/x6/src/shape/standard/polygon.ts b/packages/x6/src/shape/standard/polygon.ts deleted file mode 100644 index babd94f2a1a..00000000000 --- a/packages/x6/src/shape/standard/polygon.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Base } from '../base' -import { Poly } from './poly' -import { createShape } from './util' - -export const Polygon = createShape( - 'polygon', - {}, - { parent: Poly as typeof Base }, -) diff --git a/packages/x6/src/shape/standard/polyline.ts b/packages/x6/src/shape/standard/polyline.ts deleted file mode 100644 index 2a41dad716d..00000000000 --- a/packages/x6/src/shape/standard/polyline.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Base } from '../base' -import { Poly } from './poly' -import { createShape } from './util' - -export const Polyline = createShape( - 'polyline', - {}, - { parent: Poly as typeof Base }, -) diff --git a/packages/x6/src/shape/standard/rect-headered.ts b/packages/x6/src/shape/standard/rect-headered.ts deleted file mode 100644 index 9558f921449..00000000000 --- a/packages/x6/src/shape/standard/rect-headered.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Node } from '../../model/node' -import { Base } from '../base' - -export const HeaderedRect = Node.define({ - shape: 'rect-headered', - markup: [ - { - tagName: 'rect', - selector: 'body', - }, - { - tagName: 'rect', - selector: 'header', - }, - { - tagName: 'text', - selector: 'headerText', - }, - { - tagName: 'text', - selector: 'bodyText', - }, - ], - attrs: { - body: { - ...Base.bodyAttr, - refWidth: '100%', - refHeight: '100%', - }, - header: { - ...Base.bodyAttr, - refWidth: '100%', - height: 30, - stroke: '#000000', - }, - headerText: { - ...Base.labelAttr, - refX: '50%', - refY: 15, - fontSize: 16, - }, - bodyText: { - ...Base.labelAttr, - refY2: 15, - }, - }, -}) diff --git a/packages/x6/src/shape/standard/rect.ts b/packages/x6/src/shape/standard/rect.ts deleted file mode 100644 index b204447eb58..00000000000 --- a/packages/x6/src/shape/standard/rect.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createShape } from './util' - -export const Rect = createShape('rect', { - attrs: { - body: { - refWidth: '100%', - refHeight: '100%', - }, - }, -}) diff --git a/packages/x6/src/shape/standard/text-block.ts b/packages/x6/src/shape/standard/text-block.ts deleted file mode 100644 index 83d5e0ed225..00000000000 --- a/packages/x6/src/shape/standard/text-block.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Platform, Dom, FunctionExt, ObjectExt } from '../../util' -import { Attr } from '../../registry' -import { Base } from '../base' - -export const TextBlock = Base.define({ - shape: 'text-block', - markup: [ - { - tagName: 'rect', - selector: 'body', - }, - Platform.SUPPORT_FOREIGNOBJECT - ? { - tagName: 'foreignObject', - selector: 'foreignObject', - children: [ - { - tagName: 'div', - ns: Dom.ns.xhtml, - selector: 'label', - style: { - width: '100%', - height: '100%', - position: 'static', - backgroundColor: 'transparent', - textAlign: 'center', - margin: 0, - padding: '0px 5px', - boxSizing: 'border-box', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, - }, - ], - } - : { - tagName: 'text', - selector: 'label', - attrs: { - textAnchor: 'middle', - }, - }, - ], - attrs: { - body: { - ...Base.bodyAttr, - refWidth: '100%', - refHeight: '100%', - }, - foreignObject: { - refWidth: '100%', - refHeight: '100%', - }, - label: { - style: { - fontSize: 14, - }, - }, - }, - propHooks(metadata) { - const { text, ...others } = metadata - if (text) { - ObjectExt.setByPath(others, 'attrs/label/text', text) - } - return others - }, - attrHooks: { - text: { - set(text: string, { cell, view, refBBox, elem, attrs }) { - if (elem instanceof HTMLElement) { - elem.textContent = text - } else { - // No foreign object - const style = (attrs.style as Attr.SimpleAttrs) || {} - const wrapValue = { text, width: -5, height: '100%' } - const wrapAttrs = { - textVerticalAnchor: 'middle', - ...style, - } - - const textWrap = Attr.presets.textWrap as Attr.SetDefinition - FunctionExt.call(textWrap.set, this, wrapValue, { - cell, - view, - elem, - refBBox, - attrs: wrapAttrs, - }) - - return { fill: (style.color as string) || null } - } - }, - position(text, { refBBox, elem }) { - if (elem instanceof SVGElement) { - return refBBox.getCenter() - } - }, - }, - }, -}) diff --git a/packages/x6/src/shape/standard/util.ts b/packages/x6/src/shape/standard/util.ts deleted file mode 100644 index 9b88aebfd2c..00000000000 --- a/packages/x6/src/shape/standard/util.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ObjectExt } from '../../util' -import { Markup } from '../../view/markup' -import { Node } from '../../model/node' -import { Base } from '../base' - -export function getMarkup(tagName: string, selector = 'body'): Markup { - return [ - { - tagName, - selector, - }, - { - tagName: 'text', - selector: 'label', - }, - ] -} - -export function createShape( - shape: string, - config: Node.Config, - options: { - selector?: string - parent?: Node.Definition | typeof Base - } = {}, -) { - const defaults: Node.Config = { - constructorName: shape, - markup: getMarkup(shape, options.selector), - attrs: { - [shape]: { ...Base.bodyAttr }, - }, - } - - const base = options.parent || Base - return base.define( - ObjectExt.merge(defaults, config, { shape }), - ) as typeof Base -} diff --git a/packages/x6-next/src/shape/text-block.ts b/packages/x6/src/shape/text-block.ts similarity index 100% rename from packages/x6-next/src/shape/text-block.ts rename to packages/x6/src/shape/text-block.ts diff --git a/packages/x6-next/src/shape/util.ts b/packages/x6/src/shape/util.ts similarity index 100% rename from packages/x6-next/src/shape/util.ts rename to packages/x6/src/shape/util.ts diff --git a/packages/x6/src/style/raw.ts b/packages/x6/src/style/raw.ts index ab139715433..132848d68b2 100644 --- a/packages/x6/src/style/raw.ts +++ b/packages/x6/src/style/raw.ts @@ -142,1105 +142,6 @@ export const content = `.x6-graph { .x6-highlight-opacity { opacity: 0.3; } -@keyframes halo-pie-visibility { - 0% { - visibility: hidden; - } - 100% { - visibility: visible; - } -} -@keyframes halo-pie-opening { - 0% { - transform: scale(0.4) rotate(-20deg); - } - 100% { - transform: scale(1) rotate(0); - } -} -.x6-widget-handle { - position: absolute; - width: 20px; - height: 20px; - background-color: transparent; - background-repeat: no-repeat; - background-position: 0 0; - background-size: 20px 20px; - cursor: pointer; - user-select: none; - pointer-events: auto; - -webkit-user-drag: none; - user-drag: none; - /* stylelint-disable-line */ -} -.x6-widget-handle.hidden { - display: none; -} -.x6-widget-handle-selected { - background-color: rgba(0, 0, 0, 0.1); - border-radius: 3px; -} -.x6-widget-handle-remove { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M15.386%2C3.365c-3.315-3.314-8.707-3.313-12.021%2C0c-3.314%2C3.315-3.314%2C8.706%2C0%2C12.02%20c3.314%2C3.314%2C8.707%2C3.314%2C12.021%2C0S18.699%2C6.68%2C15.386%2C3.365L15.386%2C3.365z%20M4.152%2C14.598C1.273%2C11.719%2C1.273%2C7.035%2C4.153%2C4.154%20c2.88-2.88%2C7.563-2.88%2C10.443%2C0c2.881%2C2.88%2C2.881%2C7.562%2C0%2C10.443C11.716%2C17.477%2C7.032%2C17.477%2C4.152%2C14.598L4.152%2C14.598z%22%2F%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M12.157%2C11.371L7.38%2C6.593C7.162%2C6.375%2C6.809%2C6.375%2C6.592%2C6.592c-0.218%2C0.219-0.218%2C0.572%2C0%2C0.79%20l4.776%2C4.776c0.218%2C0.219%2C0.571%2C0.219%2C0.79%2C0C12.375%2C11.941%2C12.375%2C11.588%2C12.157%2C11.371L12.157%2C11.371z%22%2F%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M11.369%2C6.593l-4.777%2C4.778c-0.217%2C0.217-0.217%2C0.568%2C0%2C0.787c0.219%2C0.219%2C0.571%2C0.217%2C0.788%2C0l4.777-4.777%20c0.218-0.218%2C0.218-0.571%2C0.001-0.789C11.939%2C6.375%2C11.587%2C6.375%2C11.369%2C6.593L11.369%2C6.593z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - cursor: pointer; -} -.x6-widget-handle-remove:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M15.386%2C3.365c-3.315-3.314-8.707-3.313-12.021%2C0c-3.314%2C3.315-3.314%2C8.706%2C0%2C12.02%20c3.314%2C3.314%2C8.707%2C3.314%2C12.021%2C0S18.699%2C6.68%2C15.386%2C3.365L15.386%2C3.365z%22%2F%3E%3Cpath%20fill%3D%22%23FFFFFF%22%20d%3D%22M12.157%2C11.371L7.38%2C6.593C7.162%2C6.375%2C6.809%2C6.375%2C6.592%2C6.592c-0.218%2C0.219-0.218%2C0.572%2C0%2C0.79%20l4.776%2C4.776c0.218%2C0.219%2C0.571%2C0.219%2C0.79%2C0C12.375%2C11.941%2C12.375%2C11.588%2C12.157%2C11.371L12.157%2C11.371z%22%2F%3E%3Cpath%20fill%3D%22%23FFFFFF%22%20d%3D%22M11.369%2C6.593l-4.777%2C4.778c-0.217%2C0.217-0.217%2C0.568%2C0%2C0.787c0.219%2C0.219%2C0.571%2C0.217%2C0.788%2C0l4.777-4.777%20c0.218-0.218%2C0.218-0.571%2C0.001-0.789C11.939%2C6.375%2C11.587%2C6.375%2C11.369%2C6.593L11.369%2C6.593z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); -} -.x6-widget-handle-rotate { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M9.374%2C17.592c-4.176%2C0-7.57-3.401-7.57-7.575c0-4.175%2C3.395-7.574%2C7.57-7.574c0.28%2C0%2C0.56%2C0.018%2C0.837%2C0.05%20V1.268c0-0.158%2C0.099-0.3%2C0.239-0.36c0.151-0.058%2C0.315-0.026%2C0.428%2C0.086l2.683%2C2.688c0.152%2C0.154%2C0.152%2C0.399%2C0%2C0.553l-2.68%2C2.693%20c-0.115%2C0.112-0.279%2C0.147-0.431%2C0.087c-0.141-0.063-0.239-0.205-0.239-0.361V5.296C9.934%2C5.243%2C9.654%2C5.22%2C9.374%2C5.22%20c-2.646%2C0-4.796%2C2.152-4.796%2C4.797s2.154%2C4.798%2C4.796%2C4.798c2.645%2C0%2C4.798-2.153%2C4.798-4.798c0-0.214%2C0.174-0.391%2C0.391-0.391h1.991%20c0.217%2C0%2C0.394%2C0.177%2C0.394%2C0.391C16.947%2C14.19%2C13.549%2C17.592%2C9.374%2C17.592L9.374%2C17.592z%20M9.374%2C17.592%22%2F%3E%3C%2Fsvg%3E%20'); - cursor: move; -} -.x6-widget-handle-rotate:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FD6EB6%22%20d%3D%22M9.374%2C17.592c-4.176%2C0-7.57-3.401-7.57-7.575c0-4.175%2C3.395-7.574%2C7.57-7.574c0.28%2C0%2C0.56%2C0.018%2C0.837%2C0.05%20V1.268c0-0.158%2C0.099-0.3%2C0.239-0.36c0.151-0.058%2C0.315-0.026%2C0.428%2C0.086l2.683%2C2.688c0.152%2C0.154%2C0.152%2C0.399%2C0%2C0.553l-2.68%2C2.693%20c-0.115%2C0.112-0.279%2C0.147-0.431%2C0.087c-0.141-0.063-0.239-0.205-0.239-0.361V5.296C9.934%2C5.243%2C9.654%2C5.22%2C9.374%2C5.22%20c-2.646%2C0-4.796%2C2.152-4.796%2C4.797s2.154%2C4.798%2C4.796%2C4.798c2.645%2C0%2C4.798-2.153%2C4.798-4.798c0-0.214%2C0.174-0.391%2C0.391-0.391h1.991%20c0.217%2C0%2C0.394%2C0.177%2C0.394%2C0.391C16.947%2C14.19%2C13.549%2C17.592%2C9.374%2C17.592L9.374%2C17.592z%20M9.374%2C17.592%22%2F%3E%3C%2Fsvg%3E%20'); -} -.x6-widget-handle-resize { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20height%3D%2224px%22%20version%3D%221.1%22%20viewBox%3D%220%200%2024%2024%22%20width%3D%2224px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Asketch%3D%22http%3A%2F%2Fwww.bohemiancoding.com%2Fsketch%2Fns%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Ctitle%2F%3E%3Cdesc%2F%3E%3Cdefs%2F%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%20id%3D%22miu%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%3E%3Cg%20id%3D%22Artboard-1%22%20transform%3D%22translate(-251.000000%2C%20-443.000000)%22%3E%3Cg%20id%3D%22slice%22%20transform%3D%22translate(215.000000%2C%20119.000000)%22%2F%3E%3Cpath%20d%3D%22M252%2C448%20L256%2C448%20L256%2C444%20L252%2C444%20L252%2C448%20Z%20M257%2C448%20L269%2C448%20L269%2C446%20L257%2C446%20L257%2C448%20Z%20M257%2C464%20L269%2C464%20L269%2C462%20L257%2C462%20L257%2C464%20Z%20M270%2C444%20L270%2C448%20L274%2C448%20L274%2C444%20L270%2C444%20Z%20M252%2C462%20L252%2C466%20L256%2C466%20L256%2C462%20L252%2C462%20Z%20M270%2C462%20L270%2C466%20L274%2C466%20L274%2C462%20L270%2C462%20Z%20M254%2C461%20L256%2C461%20L256%2C449%20L254%2C449%20L254%2C461%20Z%20M270%2C461%20L272%2C461%20L272%2C449%20L270%2C449%20L270%2C461%20Z%22%20fill%3D%22%236A6C8A%22%20id%3D%22editor-crop-glyph%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E'); - cursor: se-resize; -} -.x6-widget-handle-resize:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20height%3D%2224px%22%20version%3D%221.1%22%20viewBox%3D%220%200%2024%2024%22%20width%3D%2224px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Asketch%3D%22http%3A%2F%2Fwww.bohemiancoding.com%2Fsketch%2Fns%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Ctitle%2F%3E%3Cdesc%2F%3E%3Cdefs%2F%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%20id%3D%22miu%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%3E%3Cg%20id%3D%22Artboard-1%22%20transform%3D%22translate(-251.000000%2C%20-443.000000)%22%3E%3Cg%20id%3D%22slice%22%20transform%3D%22translate(215.000000%2C%20119.000000)%22%2F%3E%3Cpath%20d%3D%22M252%2C448%20L256%2C448%20L256%2C444%20L252%2C444%20L252%2C448%20Z%20M257%2C448%20L269%2C448%20L269%2C446%20L257%2C446%20L257%2C448%20Z%20M257%2C464%20L269%2C464%20L269%2C462%20L257%2C462%20L257%2C464%20Z%20M270%2C444%20L270%2C448%20L274%2C448%20L274%2C444%20L270%2C444%20Z%20M252%2C462%20L252%2C466%20L256%2C466%20L256%2C462%20L252%2C462%20Z%20M270%2C462%20L270%2C466%20L274%2C466%20L274%2C462%20L270%2C462%20Z%20M254%2C461%20L256%2C461%20L256%2C449%20L254%2C449%20L254%2C461%20Z%20M270%2C461%20L272%2C461%20L272%2C449%20L270%2C449%20L270%2C461%20Z%22%20fill%3D%22%23FD6EB6%22%20id%3D%22editor-crop-glyph%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E'); -} -.x6-widget-handle-clone { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M12.852%2C0.875h-9.27c-0.853%2C0-1.547%2C0.694-1.547%2C1.547v10.816h1.547V2.422h9.27V0.875z%20M15.172%2C3.965h-8.5%20c-0.849%2C0-1.547%2C0.698-1.547%2C1.547v10.816c0%2C0.849%2C0.698%2C1.547%2C1.547%2C1.547h8.5c0.85%2C0%2C1.543-0.698%2C1.543-1.547V5.512%20C16.715%2C4.663%2C16.021%2C3.965%2C15.172%2C3.965L15.172%2C3.965z%20M15.172%2C16.328h-8.5V5.512h8.5V16.328z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - cursor: move; -} -.x6-widget-handle-clone:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cpath%20fill%3D%22%23FD6EB6%22%20d%3D%22M12.852%2C0.875h-9.27c-0.853%2C0-1.547%2C0.694-1.547%2C1.547v10.816h1.547V2.422h9.27V0.875z%20M15.172%2C3.965h-8.5%20c-0.849%2C0-1.547%2C0.698-1.547%2C1.547v10.816c0%2C0.849%2C0.698%2C1.547%2C1.547%2C1.547h8.5c0.849%2C0%2C1.543-0.698%2C1.543-1.547V5.512%20C16.715%2C4.663%2C16.021%2C3.965%2C15.172%2C3.965L15.172%2C3.965z%20M15.172%2C16.328h-8.5V5.512h8.5V16.328z%20M15.172%2C16.328%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); -} -.x6-widget-handle-link { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M9.884%2C9.838c0.54-0.551%2C1.005-0.955%2C1.384-1.201c0.463-0.308%2C0.749-0.352%2C0.887-0.352h1.34v1.367%20c0%2C0.104%2C0.061%2C0.2%2C0.154%2C0.242s0.204%2C0.027%2C0.284-0.038l3.168-2.669c0.06-0.051%2C0.096-0.125%2C0.096-0.203S17.16%2C6.83%2C17.101%2C6.781%20l-3.168-2.677c-0.08-0.067-0.19-0.081-0.284-0.038c-0.094%2C0.045-0.154%2C0.139-0.154%2C0.242v1.414h-1.343%20c-1.24%2C0.014-2.215%2C0.67-2.927%2C1.242c-0.797%2C0.65-1.533%2C1.447-2.245%2C2.217c-0.361%2C0.391-0.7%2C0.759-1.044%2C1.1%20c-0.541%2C0.549-1.011%2C0.951-1.395%2C1.199c-0.354%2C0.231-0.678%2C0.357-0.921%2C0.357h-1.8c-0.146%2C0-0.266%2C0.12-0.266%2C0.265v2.029%20c0%2C0.148%2C0.12%2C0.268%2C0.266%2C0.268h1.8l0%2C0c1.255-0.014%2C2.239-0.667%2C2.958-1.24c0.82-0.661%2C1.572-1.475%2C2.297-2.256%20C9.225%2C10.524%2C9.555%2C10.169%2C9.884%2C9.838z%22%2F%3E%3C%2Fsvg%3E%20'); - cursor: move; - cursor: -moz-grabbing; - cursor: -webkit-grabbing; -} -.x6-widget-handle-link:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20fill%3D%22%23FD6EB6%22%20d%3D%22M9.884%2C9.838c0.54-0.551%2C1.005-0.955%2C1.384-1.201c0.463-0.308%2C0.749-0.352%2C0.887-0.352h1.34v1.367%20c0%2C0.104%2C0.061%2C0.2%2C0.154%2C0.242s0.204%2C0.027%2C0.284-0.038l3.168-2.669c0.06-0.051%2C0.096-0.125%2C0.096-0.203S17.16%2C6.83%2C17.101%2C6.781%20l-3.168-2.677c-0.08-0.067-0.19-0.081-0.284-0.038c-0.094%2C0.045-0.154%2C0.139-0.154%2C0.242v1.414h-1.343%20c-1.24%2C0.014-2.215%2C0.67-2.927%2C1.242c-0.797%2C0.65-1.533%2C1.447-2.245%2C2.217c-0.361%2C0.391-0.7%2C0.759-1.044%2C1.1%20c-0.541%2C0.549-1.011%2C0.951-1.395%2C1.199c-0.354%2C0.231-0.678%2C0.357-0.921%2C0.357h-1.8c-0.146%2C0-0.266%2C0.12-0.266%2C0.265v2.029%20c0%2C0.148%2C0.12%2C0.268%2C0.266%2C0.268h1.8l0%2C0c1.255-0.014%2C2.239-0.667%2C2.958-1.24c0.82-0.661%2C1.572-1.475%2C2.297-2.256%20C9.225%2C10.524%2C9.555%2C10.169%2C9.884%2C9.838z%22%2F%3E%3C%2Fsvg%3E%20'); -} -.x6-widget-handle-fork { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cg%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20fill%3D%22%236A6C8A%22%20d%3D%22M13.307%2C11.593c-0.69%2C0-1.299%2C0.33-1.693%2C0.835l-4.136-2.387%20C7.552%2C9.82%2C7.602%2C9.589%2C7.602%2C9.344c0-0.25-0.051-0.487-0.129-0.71l4.097-2.364c0.393%2C0.536%2C1.022%2C0.888%2C1.737%2C0.888%20c1.193%2C0%2C2.16-0.967%2C2.16-2.159s-0.967-2.159-2.16-2.159c-1.191%2C0-2.158%2C0.967-2.158%2C2.159c0%2C0.076%2C0.014%2C0.149%2C0.021%2C0.223%20L6.848%2C7.716C6.469%2C7.39%2C5.982%2C7.185%2C5.442%2C7.185c-1.191%2C0-2.158%2C0.967-2.158%2C2.159s0.967%2C2.159%2C2.158%2C2.159%20c0.545%2C0%2C1.037-0.208%2C1.417-0.541l4.319%2C2.493c-0.014%2C0.098-0.029%2C0.194-0.029%2C0.296c0%2C1.193%2C0.967%2C2.159%2C2.158%2C2.159%20c1.193%2C0%2C2.16-0.966%2C2.16-2.159C15.467%2C12.559%2C14.5%2C11.593%2C13.307%2C11.593z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); - cursor: move; -} -.x6-widget-handle-fork:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cg%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20fill%3D%22%23FD6EB6%22%20d%3D%22M13.307%2C11.593c-0.69%2C0-1.299%2C0.33-1.693%2C0.835l-4.136-2.387%20c0.075-0.22%2C0.125-0.452%2C0.125-0.697c0-0.25-0.051-0.487-0.129-0.71l4.097-2.365c0.394%2C0.536%2C1.022%2C0.888%2C1.737%2C0.888%20c1.193%2C0%2C2.16-0.967%2C2.16-2.159s-0.967-2.159-2.16-2.159c-1.191%2C0-2.158%2C0.967-2.158%2C2.159c0%2C0.076%2C0.015%2C0.148%2C0.022%2C0.223%20L6.848%2C7.716C6.469%2C7.39%2C5.981%2C7.185%2C5.442%2C7.185c-1.191%2C0-2.158%2C0.967-2.158%2C2.159s0.967%2C2.159%2C2.158%2C2.159%20c0.545%2C0%2C1.037-0.208%2C1.417-0.541l4.319%2C2.493c-0.013%2C0.098-0.029%2C0.194-0.029%2C0.296c0%2C1.193%2C0.967%2C2.159%2C2.158%2C2.159%20c1.193%2C0%2C2.16-0.966%2C2.16-2.159C15.467%2C12.559%2C14.5%2C11.593%2C13.307%2C11.593z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); -} -.x6-widget-handle-unlink { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cg%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M12.285%2C9.711l-2.104-0.302L9.243%2C8.568L6.669%2C7.095C6.948%2C6.6%2C6.995%2C6.026%2C6.845%2C5.474%20c-0.191-0.698-0.695-1.36-1.438-1.786C4.068%2C2.922%2C2.464%2C3.214%2C1.82%2C4.338C1.536%2C4.836%2C1.489%2C5.414%2C1.64%2C5.97%20c0.189%2C0.698%2C0.694%2C1.36%2C1.438%2C1.787c0.328%2C0.187%2C0.67%2C0.31%2C1.01%2C0.372c0.002%2C0%2C0.006%2C0.002%2C0.008%2C0.004%20c0.027%2C0.004%2C0.057%2C0.009%2C0.088%2C0.011c2.12%2C0.316%2C3.203%2C0.915%2C3.73%2C1.337c-0.527%2C0.424-1.61%2C1.021-3.731%2C1.339%20c-0.029%2C0.003-0.058%2C0.007-0.087%2C0.012c-0.002%2C0.002-0.004%2C0.002-0.007%2C0.003c-0.341%2C0.062-0.684%2C0.187-1.013%2C0.374%20c-0.74%2C0.425-1.246%2C1.089-1.437%2C1.787c-0.149%2C0.555-0.105%2C1.133%2C0.181%2C1.632c0.011%2C0.018%2C0.021%2C0.033%2C0.033%2C0.049l0.883%2C0.783%20c0.765%2C0.366%2C1.775%2C0.328%2C2.67-0.184c0.744-0.425%2C1.248-1.088%2C1.439-1.786c0.148-0.552%2C0.104-1.126-0.176-1.62l2.573-1.473%20c0.573%2C0.287%2C2.299%2C1.292%2C2.299%2C1.292s3.602%2C1.445%2C4.241%2C1.812c0.773%2C0.191%2C0.566-0.151%2C0.566-0.151L12.285%2C9.711z%20M5.571%2C6.482%20C5.279%2C6.993%2C4.425%2C7.076%2C3.705%2C6.664C3.282%2C6.424%2C2.966%2C6.039%2C2.856%2C5.64C2.81%2C5.464%2C2.778%2C5.203%2C2.917%2C4.963%20c0.291-0.51%2C1.146-0.593%2C1.866-0.182C5.21%2C5.027%2C5.521%2C5.4%2C5.632%2C5.807C5.679%2C5.98%2C5.708%2C6.242%2C5.571%2C6.482z%20M5.632%2C13.159%20c-0.111%2C0.406-0.422%2C0.778-0.848%2C1.025c-0.719%2C0.409-1.576%2C0.327-1.867-0.184c-0.137-0.239-0.106-0.499-0.06-0.676%20c0.108-0.398%2C0.426-0.781%2C0.847-1.022c0.72-0.412%2C1.574-0.329%2C1.866%2C0.181C5.708%2C12.723%2C5.679%2C12.983%2C5.632%2C13.159z%20M16.181%2C5.139%20c-0.448%2C0.258-4.435%2C1.9-4.435%2C1.9s-1.556%2C0.855-2.104%2C1.13l0.937%2C0.843l2.057-0.229l4.11-3.638%20C16.745%2C5.146%2C17.013%2C4.664%2C16.181%2C5.139z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); -} -.x6-widget-handle-unlink:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2218.75px%22%20height%3D%2218.75px%22%20viewBox%3D%220%200%2018.75%2018.75%22%20enable-background%3D%22new%200%200%2018.75%2018.75%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%3E%3Cg%3E%3Cpath%20fill%3D%22%23FD6EB6%22%20d%3D%22M12.285%2C9.711l-2.104-0.302L9.243%2C8.568L6.669%2C7.095C6.948%2C6.6%2C6.995%2C6.026%2C6.845%2C5.474%20c-0.191-0.698-0.695-1.36-1.438-1.786C4.068%2C2.922%2C2.464%2C3.214%2C1.82%2C4.338C1.536%2C4.836%2C1.489%2C5.414%2C1.64%2C5.97%20c0.189%2C0.698%2C0.694%2C1.36%2C1.438%2C1.787c0.328%2C0.187%2C0.67%2C0.31%2C1.01%2C0.372c0.002%2C0%2C0.006%2C0.002%2C0.008%2C0.004%20c0.027%2C0.004%2C0.057%2C0.009%2C0.088%2C0.011c2.12%2C0.316%2C3.203%2C0.915%2C3.73%2C1.337c-0.527%2C0.424-1.61%2C1.021-3.731%2C1.339%20c-0.029%2C0.003-0.058%2C0.007-0.087%2C0.012c-0.002%2C0.002-0.004%2C0.002-0.007%2C0.003c-0.341%2C0.062-0.684%2C0.187-1.013%2C0.374%20c-0.74%2C0.425-1.246%2C1.089-1.437%2C1.787c-0.149%2C0.555-0.105%2C1.133%2C0.181%2C1.632c0.011%2C0.018%2C0.021%2C0.033%2C0.033%2C0.049l0.883%2C0.783%20c0.765%2C0.366%2C1.775%2C0.328%2C2.67-0.184c0.744-0.425%2C1.248-1.088%2C1.439-1.786c0.148-0.552%2C0.104-1.126-0.176-1.62l2.573-1.473%20c0.573%2C0.287%2C2.299%2C1.292%2C2.299%2C1.292s3.602%2C1.445%2C4.241%2C1.812c0.773%2C0.191%2C0.566-0.151%2C0.566-0.151L12.285%2C9.711z%20M5.571%2C6.482%20C5.279%2C6.993%2C4.425%2C7.076%2C3.705%2C6.664C3.282%2C6.424%2C2.966%2C6.039%2C2.856%2C5.64C2.81%2C5.464%2C2.778%2C5.203%2C2.917%2C4.963%20c0.291-0.51%2C1.146-0.593%2C1.866-0.182C5.21%2C5.027%2C5.521%2C5.4%2C5.632%2C5.807C5.679%2C5.98%2C5.708%2C6.242%2C5.571%2C6.482z%20M5.632%2C13.159%20c-0.111%2C0.406-0.422%2C0.778-0.848%2C1.025c-0.719%2C0.409-1.576%2C0.327-1.867-0.184c-0.137-0.239-0.106-0.499-0.06-0.676%20c0.108-0.398%2C0.426-0.781%2C0.847-1.022c0.72-0.412%2C1.574-0.329%2C1.866%2C0.181C5.708%2C12.723%2C5.679%2C12.983%2C5.632%2C13.159z%20M16.181%2C5.139%20c-0.448%2C0.258-4.435%2C1.9-4.435%2C1.9s-1.556%2C0.855-2.104%2C1.13l0.937%2C0.843l2.057-0.229l4.11-3.638%20C16.745%2C5.146%2C17.013%2C4.664%2C16.181%2C5.139z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E%20'); -} -.x6-widget-handle-direction { - background-image: url("data:image/svg+xml;charset=UTF-8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3C!DOCTYPE%20svg%20%20PUBLIC%20'-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN'%20%20'http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd'%3E%3Csvg%20id%3D%22Layer_1%22%20style%3D%22enable-background%3Anew%200%200%20512%20512%3B%22%20version%3D%221.1%22%20viewBox%3D%220%200%20512%20512%22%20xml%3Aspace%3D%22preserve%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%236A6C8A%3Bstroke%3A%236A6C8A%3Bstroke-width%3A30%7D%0A%09.dot%7Bfill%3A%236A6C8A%3B%7D%0A%3C%2Fstyle%3E%3Cg%3E%3Cg%20id%3D%22XMLID_475_%22%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M133.1%2C277.1c1.8%2C0%2C3.7-0.6%2C5.4-1.7c4.1-3%2C5-8.7%2C2-12.8c-3-4.1-8.7-5-12.8-2c0%2C0%2C0%2C0%2C0%2C0%20%20%20%20%20c-4.1%2C3-5%2C8.7-2%2C12.8C127.5%2C275.8%2C130.3%2C277.1%2C133.1%2C277.1z%22%20id%3D%22XMLID_489_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M138.5%2C359.6c-4.1-3-9.8-2.1-12.8%2C2c-3%2C4.1-2.1%2C9.8%2C2%2C12.8c1.6%2C1.2%2C3.5%2C1.7%2C5.4%2C1.7%20%20%20%20%20c2.8%2C0%2C5.6-1.3%2C7.4-3.7C143.5%2C368.3%2C142.6%2C362.6%2C138.5%2C359.6z%22%20id%3D%22XMLID_726_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M108.1%2C327.7c-4.8%2C1.6-7.4%2C6.7-5.9%2C11.5c1.3%2C3.9%2C4.8%2C6.3%2C8.7%2C6.3c0.9%2C0%2C1.9-0.1%2C2.8-0.4%20%20%20%20%20c4.8-1.6%2C7.4-6.7%2C5.9-11.5C118%2C328.8%2C112.9%2C326.2%2C108.1%2C327.7z%22%20id%3D%22XMLID_776_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M108.1%2C307.3c0.9%2C0.3%2C1.9%2C0.4%2C2.8%2C0.4c3.8%2C0%2C7.4-2.4%2C8.7-6.3c1.6-4.8-1.1-9.9-5.9-11.5%20%20%20%20%20c-4.8-1.6-9.9%2C1.1-11.5%2C5.9C100.7%2C300.6%2C103.3%2C305.7%2C108.1%2C307.3z%22%20id%3D%22XMLID_777_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M169.2%2C265.4c2.4%2C0%2C4.7-1%2C6.5-2.6c1.7-1.7%2C2.7-4.1%2C2.7-6.5c0-2.4-1-4.8-2.7-6.5%20%20%20%20%20c-1.7-1.7-4.1-2.7-6.5-2.7s-4.7%2C1-6.5%2C2.7c-1.7%2C1.7-2.7%2C4-2.7%2C6.5c0%2C2.4%2C1%2C4.7%2C2.7%2C6.5C164.4%2C264.4%2C166.8%2C265.4%2C169.2%2C265.4z%22%20id%3D%22XMLID_797_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M247.7%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C243.7%2C265.4%2C247.7%2C261.3%2C247.7%2C256.3z%22%20id%3D%22XMLID_798_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M213%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C208.9%2C265.4%2C213%2C261.3%2C213%2C256.3z%22%20id%3D%22XMLID_799_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M317.2%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C313.1%2C265.4%2C317.2%2C261.3%2C317.2%2C256.3z%22%20id%3D%22XMLID_800_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M282.5%2C256.3c0-5-4.1-9.1-9.1-9.1s-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20S282.5%2C261.3%2C282.5%2C256.3z%22%20id%3D%22XMLID_801_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M401.1%2C185.2c0.9%2C0%2C1.9-0.1%2C2.8-0.5c4.8-1.6%2C7.4-6.7%2C5.9-11.5c-1.6-4.8-6.7-7.4-11.5-5.8%20%20%20%20%20c-4.8%2C1.6-7.4%2C6.7-5.8%2C11.5C393.6%2C182.8%2C397.2%2C185.2%2C401.1%2C185.2z%22%20id%3D%22XMLID_802_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M403.9%2C205.2c-4.8-1.6-9.9%2C1-11.5%2C5.9l0%2C0c-1.6%2C4.8%2C1.1%2C9.9%2C5.9%2C11.5%20%20%20%20%20c0.9%2C0.3%2C1.9%2C0.5%2C2.8%2C0.5c3.9%2C0%2C7.4-2.5%2C8.7-6.3c0%2C0%2C0%2C0%2C0%2C0C411.3%2C211.9%2C408.7%2C206.8%2C403.9%2C205.2z%22%20id%3D%22XMLID_803_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M373.5%2C237.2L373.5%2C237.2c-4.1%2C3-5%2C8.7-2%2C12.8c1.8%2C2.4%2C4.6%2C3.7%2C7.4%2C3.7%20%20%20%20%20c1.8%2C0%2C3.7-0.6%2C5.4-1.8c4.1-3%2C4.9-8.7%2C2-12.8C383.3%2C235.1%2C377.6%2C234.2%2C373.5%2C237.2z%22%20id%3D%22XMLID_804_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M373.5%2C152.9c1.6%2C1.2%2C3.5%2C1.8%2C5.4%2C1.8c2.8%2C0%2C5.6-1.3%2C7.4-3.8c3-4.1%2C2.1-9.8-2-12.7%20%20%20%20%20c-4.1-3-9.8-2.1-12.7%2C2C368.5%2C144.2%2C369.4%2C149.9%2C373.5%2C152.9z%22%20id%3D%22XMLID_805_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M342.8%2C247.1c-2.4%2C0-4.8%2C1-6.5%2C2.7c-1.7%2C1.7-2.7%2C4-2.7%2C6.5c0%2C2.4%2C1%2C4.7%2C2.7%2C6.4%20%20%20%20%20c1.7%2C1.7%2C4%2C2.7%2C6.5%2C2.7c2.4%2C0%2C4.7-1%2C6.5-2.7c1.7-1.7%2C2.7-4%2C2.7-6.4c0-2.4-1-4.8-2.7-6.5C347.6%2C248.1%2C345.2%2C247.1%2C342.8%2C247.1z%22%20id%3D%22XMLID_806_%22%2F%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M342.8%2C124.7H206.6l36.4-36.4c3.6-3.6%2C3.6-9.3%2C0-12.9c-3.6-3.6-9.3-3.6-12.9%2C0l-51.5%2C51.5%20%20%20%20%20c-1.9%2C1.9-2.8%2C4.4-2.7%2C6.9c-0.1%2C2.5%2C0.7%2C5%2C2.7%2C6.9l51.5%2C51.5c1.8%2C1.8%2C4.1%2C2.7%2C6.5%2C2.7c2.3%2C0%2C4.7-0.9%2C6.5-2.7%20%20%20%20%20c3.6-3.6%2C3.6-9.3%2C0-12.9l-36.4-36.4h136.1c0%2C0%2C0.1%2C0%2C0.1%2C0c0.6%2C0%2C1.2-0.1%2C1.8-0.2c0.2%2C0%2C0.4-0.1%2C0.6-0.1c0.1%2C0%2C0.2%2C0%2C0.3-0.1%20%20%20%20%20c3.2-1%2C5.6-3.6%2C6.3-6.9c0.1-0.6%2C0.2-1.2%2C0.2-1.8c0-0.6-0.1-1.2-0.2-1.8C351%2C127.8%2C347.3%2C124.7%2C342.8%2C124.7z%22%20id%3D%22XMLID_807_%22%2F%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M322.1%2C371.3l-51.5-51.5c-3.6-3.6-9.3-3.6-12.9%2C0c-3.6%2C3.6-3.6%2C9.3%2C0%2C12.9l36.9%2C36.9H169.2%20%20%20%20%20c-2.8%2C0-5.4%2C1.3-7%2C3.3c-0.1%2C0.1-0.2%2C0.2-0.3%2C0.4c-0.1%2C0.1-0.2%2C0.2-0.2%2C0.3c-0.1%2C0.1-0.1%2C0.2-0.2%2C0.4c-0.1%2C0.1-0.2%2C0.3-0.2%2C0.4%20%20%20%20%20c0%2C0.1-0.1%2C0.2-0.1%2C0.2c-0.1%2C0.2-0.2%2C0.4-0.3%2C0.6c0%2C0%2C0%2C0%2C0%2C0.1c-0.4%2C1.1-0.7%2C2.2-0.7%2C3.4c0%2C1.5%2C0.4%2C2.9%2C1%2C4.2c0%2C0%2C0%2C0.1%2C0.1%2C0.1%20%20%20%20%20c0.1%2C0.1%2C0.1%2C0.2%2C0.2%2C0.3c0.4%2C0.7%2C0.9%2C1.3%2C1.4%2C1.8c0.4%2C0.4%2C0.7%2C0.7%2C1.2%2C1c0.1%2C0.1%2C0.1%2C0.1%2C0.2%2C0.2c0%2C0%2C0.1%2C0%2C0.1%2C0.1%20%20%20%20%20c1.4%2C0.9%2C3.1%2C1.5%2C5%2C1.5h124.4l-36%2C36c-3.6%2C3.6-3.6%2C9.3%2C0%2C12.9c1.8%2C1.8%2C4.1%2C2.7%2C6.5%2C2.7c2.3%2C0%2C4.7-0.9%2C6.5-2.7l51.5-51.5%20%20%20%20%20c1.9-1.9%2C2.8-4.4%2C2.7-6.9C324.8%2C375.7%2C324%2C373.2%2C322.1%2C371.3z%22%20id%3D%22XMLID_808_%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E"); -} -.x6-widget-handle-direction:hover { - background-image: url("data:image/svg+xml;charset=UTF-8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3C!DOCTYPE%20svg%20%20PUBLIC%20'-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN'%20%20'http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd'%3E%3Csvg%20id%3D%22Layer_1%22%20style%3D%22enable-background%3Anew%200%200%20512%20512%3B%22%20version%3D%221.1%22%20viewBox%3D%220%200%20512%20512%22%20xml%3Aspace%3D%22preserve%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%23FD6EB6%3Bstroke%3A%23FD6EB6%3Bstroke-width%3A30%7D%0A%09.dot%7Bfill%3A%23FD6EB6%3B%7D%0A%3C%2Fstyle%3E%3Cg%3E%3Cg%20id%3D%22XMLID_475_%22%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M133.1%2C277.1c1.8%2C0%2C3.7-0.6%2C5.4-1.7c4.1-3%2C5-8.7%2C2-12.8c-3-4.1-8.7-5-12.8-2c0%2C0%2C0%2C0%2C0%2C0%20%20%20%20%20c-4.1%2C3-5%2C8.7-2%2C12.8C127.5%2C275.8%2C130.3%2C277.1%2C133.1%2C277.1z%22%20id%3D%22XMLID_489_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M138.5%2C359.6c-4.1-3-9.8-2.1-12.8%2C2c-3%2C4.1-2.1%2C9.8%2C2%2C12.8c1.6%2C1.2%2C3.5%2C1.7%2C5.4%2C1.7%20%20%20%20%20c2.8%2C0%2C5.6-1.3%2C7.4-3.7C143.5%2C368.3%2C142.6%2C362.6%2C138.5%2C359.6z%22%20id%3D%22XMLID_726_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M108.1%2C327.7c-4.8%2C1.6-7.4%2C6.7-5.9%2C11.5c1.3%2C3.9%2C4.8%2C6.3%2C8.7%2C6.3c0.9%2C0%2C1.9-0.1%2C2.8-0.4%20%20%20%20%20c4.8-1.6%2C7.4-6.7%2C5.9-11.5C118%2C328.8%2C112.9%2C326.2%2C108.1%2C327.7z%22%20id%3D%22XMLID_776_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M108.1%2C307.3c0.9%2C0.3%2C1.9%2C0.4%2C2.8%2C0.4c3.8%2C0%2C7.4-2.4%2C8.7-6.3c1.6-4.8-1.1-9.9-5.9-11.5%20%20%20%20%20c-4.8-1.6-9.9%2C1.1-11.5%2C5.9C100.7%2C300.6%2C103.3%2C305.7%2C108.1%2C307.3z%22%20id%3D%22XMLID_777_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M169.2%2C265.4c2.4%2C0%2C4.7-1%2C6.5-2.6c1.7-1.7%2C2.7-4.1%2C2.7-6.5c0-2.4-1-4.8-2.7-6.5%20%20%20%20%20c-1.7-1.7-4.1-2.7-6.5-2.7s-4.7%2C1-6.5%2C2.7c-1.7%2C1.7-2.7%2C4-2.7%2C6.5c0%2C2.4%2C1%2C4.7%2C2.7%2C6.5C164.4%2C264.4%2C166.8%2C265.4%2C169.2%2C265.4z%22%20id%3D%22XMLID_797_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M247.7%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C243.7%2C265.4%2C247.7%2C261.3%2C247.7%2C256.3z%22%20id%3D%22XMLID_798_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M213%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C208.9%2C265.4%2C213%2C261.3%2C213%2C256.3z%22%20id%3D%22XMLID_799_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M317.2%2C256.3c0-5-4.1-9.1-9.1-9.1c-5%2C0-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20C313.1%2C265.4%2C317.2%2C261.3%2C317.2%2C256.3z%22%20id%3D%22XMLID_800_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M282.5%2C256.3c0-5-4.1-9.1-9.1-9.1s-9.1%2C4.1-9.1%2C9.1c0%2C5%2C4.1%2C9.1%2C9.1%2C9.1%20%20%20%20%20S282.5%2C261.3%2C282.5%2C256.3z%22%20id%3D%22XMLID_801_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M401.1%2C185.2c0.9%2C0%2C1.9-0.1%2C2.8-0.5c4.8-1.6%2C7.4-6.7%2C5.9-11.5c-1.6-4.8-6.7-7.4-11.5-5.8%20%20%20%20%20c-4.8%2C1.6-7.4%2C6.7-5.8%2C11.5C393.6%2C182.8%2C397.2%2C185.2%2C401.1%2C185.2z%22%20id%3D%22XMLID_802_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M403.9%2C205.2c-4.8-1.6-9.9%2C1-11.5%2C5.9l0%2C0c-1.6%2C4.8%2C1.1%2C9.9%2C5.9%2C11.5%20%20%20%20%20c0.9%2C0.3%2C1.9%2C0.5%2C2.8%2C0.5c3.9%2C0%2C7.4-2.5%2C8.7-6.3c0%2C0%2C0%2C0%2C0%2C0C411.3%2C211.9%2C408.7%2C206.8%2C403.9%2C205.2z%22%20id%3D%22XMLID_803_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M373.5%2C237.2L373.5%2C237.2c-4.1%2C3-5%2C8.7-2%2C12.8c1.8%2C2.4%2C4.6%2C3.7%2C7.4%2C3.7%20%20%20%20%20c1.8%2C0%2C3.7-0.6%2C5.4-1.8c4.1-3%2C4.9-8.7%2C2-12.8C383.3%2C235.1%2C377.6%2C234.2%2C373.5%2C237.2z%22%20id%3D%22XMLID_804_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M373.5%2C152.9c1.6%2C1.2%2C3.5%2C1.8%2C5.4%2C1.8c2.8%2C0%2C5.6-1.3%2C7.4-3.8c3-4.1%2C2.1-9.8-2-12.7%20%20%20%20%20c-4.1-3-9.8-2.1-12.7%2C2C368.5%2C144.2%2C369.4%2C149.9%2C373.5%2C152.9z%22%20id%3D%22XMLID_805_%22%2F%3E%0A%3Cpath%20class%3D%22dot%22%20d%3D%22M342.8%2C247.1c-2.4%2C0-4.8%2C1-6.5%2C2.7c-1.7%2C1.7-2.7%2C4-2.7%2C6.5c0%2C2.4%2C1%2C4.7%2C2.7%2C6.4%20%20%20%20%20c1.7%2C1.7%2C4%2C2.7%2C6.5%2C2.7c2.4%2C0%2C4.7-1%2C6.5-2.7c1.7-1.7%2C2.7-4%2C2.7-6.4c0-2.4-1-4.8-2.7-6.5C347.6%2C248.1%2C345.2%2C247.1%2C342.8%2C247.1z%22%20id%3D%22XMLID_806_%22%2F%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M342.8%2C124.7H206.6l36.4-36.4c3.6-3.6%2C3.6-9.3%2C0-12.9c-3.6-3.6-9.3-3.6-12.9%2C0l-51.5%2C51.5%20%20%20%20%20c-1.9%2C1.9-2.8%2C4.4-2.7%2C6.9c-0.1%2C2.5%2C0.7%2C5%2C2.7%2C6.9l51.5%2C51.5c1.8%2C1.8%2C4.1%2C2.7%2C6.5%2C2.7c2.3%2C0%2C4.7-0.9%2C6.5-2.7%20%20%20%20%20c3.6-3.6%2C3.6-9.3%2C0-12.9l-36.4-36.4h136.1c0%2C0%2C0.1%2C0%2C0.1%2C0c0.6%2C0%2C1.2-0.1%2C1.8-0.2c0.2%2C0%2C0.4-0.1%2C0.6-0.1c0.1%2C0%2C0.2%2C0%2C0.3-0.1%20%20%20%20%20c3.2-1%2C5.6-3.6%2C6.3-6.9c0.1-0.6%2C0.2-1.2%2C0.2-1.8c0-0.6-0.1-1.2-0.2-1.8C351%2C127.8%2C347.3%2C124.7%2C342.8%2C124.7z%22%20id%3D%22XMLID_807_%22%2F%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M322.1%2C371.3l-51.5-51.5c-3.6-3.6-9.3-3.6-12.9%2C0c-3.6%2C3.6-3.6%2C9.3%2C0%2C12.9l36.9%2C36.9H169.2%20%20%20%20%20c-2.8%2C0-5.4%2C1.3-7%2C3.3c-0.1%2C0.1-0.2%2C0.2-0.3%2C0.4c-0.1%2C0.1-0.2%2C0.2-0.2%2C0.3c-0.1%2C0.1-0.1%2C0.2-0.2%2C0.4c-0.1%2C0.1-0.2%2C0.3-0.2%2C0.4%20%20%20%20%20c0%2C0.1-0.1%2C0.2-0.1%2C0.2c-0.1%2C0.2-0.2%2C0.4-0.3%2C0.6c0%2C0%2C0%2C0%2C0%2C0.1c-0.4%2C1.1-0.7%2C2.2-0.7%2C3.4c0%2C1.5%2C0.4%2C2.9%2C1%2C4.2c0%2C0%2C0%2C0.1%2C0.1%2C0.1%20%20%20%20%20c0.1%2C0.1%2C0.1%2C0.2%2C0.2%2C0.3c0.4%2C0.7%2C0.9%2C1.3%2C1.4%2C1.8c0.4%2C0.4%2C0.7%2C0.7%2C1.2%2C1c0.1%2C0.1%2C0.1%2C0.1%2C0.2%2C0.2c0%2C0%2C0.1%2C0%2C0.1%2C0.1%20%20%20%20%20c1.4%2C0.9%2C3.1%2C1.5%2C5%2C1.5h124.4l-36%2C36c-3.6%2C3.6-3.6%2C9.3%2C0%2C12.9c1.8%2C1.8%2C4.1%2C2.7%2C6.5%2C2.7c2.3%2C0%2C4.7-0.9%2C6.5-2.7l51.5-51.5%20%20%20%20%20c1.9-1.9%2C2.8-4.4%2C2.7-6.9C324.8%2C375.7%2C324%2C373.2%2C322.1%2C371.3z%22%20id%3D%22XMLID_808_%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E"); -} -.x6-widget-handle-surround .x6-widget-handle-animate .x6-widget-handle { - transition: background-size 80ms, width 80ms, height 80ms, top 150ms, left 150ms, bottom 150ms, right 150ms; -} -.x6-widget-handle-surround .x6-widget-handle-pos-se { - right: -25px; - bottom: -25px; -} -.x6-widget-handle-surround .x6-widget-handle-pos-nw { - top: -21px; - left: -25px; -} -.x6-widget-handle-surround .x6-widget-handle-pos-n { - top: -22px; - left: 50%; - margin-left: -10px; -} -.x6-widget-handle-surround .x6-widget-handle-pos-e { - top: -webkit-calc(40%); - top: calc(50% - 10px); - right: -25px; -} -.x6-widget-handle-surround .x6-widget-handle-pos-ne { - top: -21px; - right: -25px; -} -.x6-widget-handle-surround .x6-widget-handle-pos-w { - top: 50%; - left: -25px; - margin-top: -10px; -} -.x6-widget-handle-surround .x6-widget-handle-pos-sw { - bottom: -25px; - left: -25px; -} -.x6-widget-handle-surround .x6-widget-handle-pos-s { - bottom: -24px; - left: 50%; - margin-left: -10px; -} -.x6-widget-handle-surround .x6-widget-handle-small .x6-widget-handle { - width: 15px; - height: 15px; - font-size: 15px; - background-size: 15px 15px; -} -.x6-widget-handle-surround .x6-widget-handle-small .x6-widget-handle-pos-se { - right: -19px; - bottom: -19px; -} -.x6-widget-handle-surround .x6-widget-handle-small .x6-widget-handle-pos-nw { - top: -19px; - left: -19px; -} -.x6-widget-handle-surround .x6-widget-handle-small .x6-widget-handle-pos-n { - top: -19px; - margin-left: -7.5px; -} -.x6-widget-handle-surround .x6-widget-handle-small .x6-widget-handle-pos-e { - top: -webkit-calc(42%); - top: calc(50% - 8px); - right: -19px; -} -.x6-widget-handle-surround .x6-widget-handle-small .x6-widget-handle-pos-ne { - top: -19px; - right: -19px; -} -.x6-widget-handle-surround .x6-widget-handle-small .x6-widget-handle-pos-w { - left: -19px; - margin-top: -8px; -} -.x6-widget-handle-surround .x6-widget-handle-small .x6-widget-handle-pos-sw { - bottom: -19px; - left: -19px; -} -.x6-widget-handle-surround .x6-widget-handle-small .x6-widget-handle-pos-s { - bottom: -19px; - margin-left: -7.5px; -} -.x6-widget-handle-surround .x6-widget-handle-tiny .x6-widget-handle { - width: 10px; - height: 10px; - font-size: 10px; - background-size: 10px 10px; -} -.x6-widget-handle-surround .x6-widget-handle-tiny .x6-widget-handle-pos-se { - right: -15px; - bottom: -13px; -} -.x6-widget-handle-surround .x6-widget-handle-tiny .x6-widget-handle-pos-nw { - top: -13px; - left: -15px; -} -.x6-widget-handle-surround .x6-widget-handle-tiny .x6-widget-handle-pos-n { - top: -13px; - margin-left: -5px; -} -.x6-widget-handle-surround .x6-widget-handle-tiny .x6-widget-handle-pos-e { - top: -webkit-calc(45%); - top: calc(50% - 5px); - right: -15px; -} -.x6-widget-handle-surround .x6-widget-handle-tiny .x6-widget-handle-pos-ne { - top: -13px; - right: -15px; -} -.x6-widget-handle-surround .x6-widget-handle-tiny .x6-widget-handle-pos-w { - left: -15px; - margin-top: -5px; -} -.x6-widget-handle-surround .x6-widget-handle-tiny .x6-widget-handle-pos-sw { - bottom: -13px; - left: -15px; -} -.x6-widget-handle-surround .x6-widget-handle-tiny .x6-widget-handle-pos-s { - bottom: -13px; - margin-left: -5px; -} -.x6-widget-handle-toolbar { - position: absolute; - top: -50px; - display: table-row; - padding: 7px 5px; -} -.x6-widget-handle-toolbar::after { - position: absolute; - top: 100%; - left: 10px; - width: 0; - height: 0; - margin-top: 4px; - border-right: 10px solid transparent; - border-left: 10px solid transparent; - content: ''; -} -.x6-widget-handle-toolbar .x6-widget-handle { - position: relative; - display: table-cell; - min-width: 20px; - margin: 0 2px; - background-position: 3px 3px; - background-size: 16px 16px; -} -.x6-widget-handle-toolbar .x6-widget-handle::after { - position: absolute; - bottom: -11px; - width: 100%; - content: ''; -} -.x6-widget-handle-pie { - position: absolute; - top: -webkit-calc(0%); - top: calc(50% - 50px); - right: -50px; - z-index: 1; - display: none; - width: 100px; - height: 100px; - margin: -2px -2px 0 0; - border-radius: 50%; - cursor: default; - pointer-events: visiblePainted; -} -.x6-widget-handle-pie .x6-widget-handle { - width: 1px; - height: auto; - pointer-events: visiblePainted; -} -.x6-widget-handle-pie-slice-svg { - width: 100%; - height: 100%; - overflow: visible !important; -} -.x6-widget-handle-pie-slice-img, -.x6-widget-handle-pie-slice-txt { - display: none; - pointer-events: none; -} -.x6-widget-handle-pie[data-pie-toggle-position='e'] { - top: calc(50% - 50px); - right: -50px; - left: auto; -} -.x6-widget-handle-pie[data-pie-toggle-position='w'] { - top: calc(50% - 50px); - right: auto; - left: -52px; -} -.x6-widget-handle-pie[data-pie-toggle-position='n'] { - top: -50px; - right: auto; - bottom: auto; - left: calc(50% - 52px); -} -.x6-widget-handle-pie[data-pie-toggle-position='s'] { - top: auto; - right: auto; - bottom: -52px; - left: calc(50% - 52px); -} -.x6-widget-handle-pie-opened { - display: block; - animation: halo-pie-visibility 0.1s, halo-pie-opening 0.1s; - animation-timing-function: step-end, ease; - animation-delay: 0s, 0.1s; -} -.x6-widget-handle-pie-toggle { - position: absolute; - top: -webkit-calc(35%); - top: calc(50% - 15px); - right: -15px; - z-index: 2; - display: block; - box-sizing: border-box; - width: 30px; - height: 30px; - background-repeat: no-repeat; - background-position: center; - background-size: 20px 20px; - border-radius: 50%; - cursor: pointer; - user-select: none; - pointer-events: visiblePainted; - -webkit-user-drag: none; - user-drag: none; - /* stylelint-disable-line */ -} -.x6-widget-handle-pie-toggle-pos-e { - top: -webkit-calc(35%); - top: calc(50% - 15px); - right: -15px; - bottom: auto; - left: auto; -} -.x6-widget-handle-pie-toggle-pos-w { - top: -webkit-calc(35%); - top: calc(50% - 15px); - right: auto; - bottom: auto; - left: -15px; -} -.x6-widget-handle-pie-toggle-pos-n { - top: -15px; - right: auto; - bottom: auto; - left: -webkit-calc(35%); - left: calc(50% - 15px); -} -.x6-widget-handle-pie-toggle-pos-s { - top: auto; - right: auto; - bottom: -15px; - left: -webkit-calc(35%); - left: calc(50% - 15px); -} -.x6-widget-handle-pie-toggle-opened { - transition: 0.1s background-image; -} -.x6-widget-handle-toolbar { - position: static; - display: inline-block; - margin-top: -50px; - margin-left: 45px; - white-space: nowrap; - vertical-align: top; - background-color: #f5f5f5; - border-bottom: 3px solid #333; - border-radius: 5px; - box-shadow: 0 1px 2px #222; -} -.x6-widget-handle-toolbar::after { - top: -12px; - left: 55px; - margin-top: 0; - border-top: 6px solid #333; - border-right: 10px solid transparent; - border-left: 10px solid transparent; -} -.x6-widget-handle-toolbar .x6-widget-handle { - display: inline-block; - vertical-align: top; -} -.x6-widget-handle-toolbar .x6-widget-handle:hover::after { - border-bottom: 4px solid #fc6cb8; -} -.x6-widget-handle-toolbar .x6-widget-handle-rotate { - position: absolute; - top: 100%; - right: 100%; - margin-top: 3px; - margin-right: 6px; -} -.x6-widget-handle-toolbar .x6-widget-handle-remove:hover::after, -.x6-widget-handle-toolbar .x6-widget-handle-rotate:hover::after { - border-bottom: none; -} -.x6-widget-handle-toolbar .x6-widget-handle + .x6-widget-handle { - margin-left: 4px; -} -.x6-widget-handle-pie { - box-sizing: content-box; - background-color: #f5f5f5; - border: 2px solid #404040; -} -.x6-widget-handle-pie-slice { - fill: transparent; - stroke: #e9e9e9; - stroke-width: 1; -} -.x6-widget-handle-pie-slice:hover { - fill: #fff; -} -.x6-widget-handle-pie-slice-img { - display: block; -} -.x6-widget-handle-selected .x6-widget-handle-pie-slice { - fill: #fff; -} -.x6-widget-handle-pie-toggle { - background-color: #f6f6f6; - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20height%3D%2216px%22%20id%3D%22Layer_1%22%20style%3D%22enable-background%3Anew%200%200%2016%2016%3B%22%20version%3D%221.1%22%20viewBox%3D%220%200%2016%2016%22%20width%3D%2216px%22%20xml%3Aspace%3D%22preserve%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cpath%20fill%3D%22%236A6C8A%22%20d%3D%22M15%2C6h-5V1c0-0.55-0.45-1-1-1H7C6.45%2C0%2C6%2C0.45%2C6%2C1v5H1C0.45%2C6%2C0%2C6.45%2C0%2C7v2c0%2C0.55%2C0.45%2C1%2C1%2C1h5v5c0%2C0.55%2C0.45%2C1%2C1%2C1h2%20c0.55%2C0%2C1-0.45%2C1-1v-5h5c0.55%2C0%2C1-0.45%2C1-1V7C16%2C6.45%2C15.55%2C6%2C15%2C6z%22%2F%3E%3C%2Fsvg%3E'); - background-size: 16px 16px; - border: 2px solid #3b425f; -} -.x6-widget-handle-pie-toggle:hover { - background-color: #fff; - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20height%3D%2216px%22%20id%3D%22Layer_1%22%20style%3D%22enable-background%3Anew%200%200%2016%2016%3B%22%20version%3D%221.1%22%20viewBox%3D%220%200%2016%2016%22%20width%3D%2216px%22%20xml%3Aspace%3D%22preserve%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cpath%20fill%3D%22%23FD6EB6%22%20d%3D%22M15%2C6h-5V1c0-0.55-0.45-1-1-1H7C6.45%2C0%2C6%2C0.45%2C6%2C1v5H1C0.45%2C6%2C0%2C6.45%2C0%2C7v2c0%2C0.55%2C0.45%2C1%2C1%2C1h5v5c0%2C0.55%2C0.45%2C1%2C1%2C1h2%20c0.55%2C0%2C1-0.45%2C1-1v-5h5c0.55%2C0%2C1-0.45%2C1-1V7C16%2C6.45%2C15.55%2C6%2C15%2C6z%22%2F%3E%3C%2Fsvg%3E'); - border-color: #fd6eb6; -} -.x6-widget-handle-pie-toggle-opened { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22no%22%3F%3E%3Csvg%20xmlns%3Adc%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%22%20xmlns%3Acc%3D%22http%3A%2F%2Fcreativecommons.org%2Fns%23%22%20xmlns%3Ardf%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%22%20xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20id%3D%22Layer_1%22%20xml%3Aspace%3D%22preserve%22%3E%3Cmetadata%20id%3D%22metadata9%22%3E%3Crdf%3ARDF%3E%3Ccc%3AWork%20rdf%3Aabout%3D%22%22%3E%3Cdc%3Aformat%3Eimage%2Fsvg%2Bxml%3C%2Fdc%3Aformat%3E%3Cdc%3Atype%20rdf%3Aresource%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Fdcmitype%2FStillImage%22%20%2F%3E%3Cdc%3Atitle%3E%3C%2Fdc%3Atitle%3E%3C%2Fcc%3AWork%3E%3C%2Frdf%3ARDF%3E%3C%2Fmetadata%3E%3Cdefs%20id%3D%22defs7%22%20%2F%3E%3Cpath%20d%3D%22M%2015%2C6%2010%2C6%20C%201.0301983%2C6.00505%2015.002631%2C6.011353%206%2C6%20L%201%2C6%20C%200.45%2C6%200%2C6.45%200%2C7%20l%200%2C2%20c%200%2C0.55%200.45%2C1%201%2C1%20l%205%2C0%20c%208.988585%2C-0.019732%20-5.02893401%2C-0.018728%204%2C0%20l%205%2C0%20c%200.55%2C0%201%2C-0.45%201%2C-1%20L%2016%2C7%20C%2016%2C6.45%2015.55%2C6%2015%2C6%20z%22%20id%3D%22path3%22%20style%3D%22fill%3A%236a6c8a%22%20%2F%3E%3C%2Fsvg%3E'); -} -.x6-widget-handle-pie-toggle-opened:hover { - background-image: url('data:image/svg+xml;charset=utf8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%20standalone%3D%22no%22%3F%3E%3Csvg%20xmlns%3Adc%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%22%20xmlns%3Acc%3D%22http%3A%2F%2Fcreativecommons.org%2Fns%23%22%20xmlns%3Ardf%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%22%20xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20id%3D%22Layer_1%22%20xml%3Aspace%3D%22preserve%22%3E%3Cmetadata%20id%3D%22metadata9%22%3E%3Crdf%3ARDF%3E%3Ccc%3AWork%20rdf%3Aabout%3D%22%22%3E%3Cdc%3Aformat%3Eimage%2Fsvg%2Bxml%3C%2Fdc%3Aformat%3E%3Cdc%3Atype%20rdf%3Aresource%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Fdcmitype%2FStillImage%22%20%2F%3E%3Cdc%3Atitle%3E%3C%2Fdc%3Atitle%3E%3C%2Fcc%3AWork%3E%3C%2Frdf%3ARDF%3E%3C%2Fmetadata%3E%3Cdefs%20id%3D%22defs7%22%20%2F%3E%3Cpath%20d%3D%22M%2015%2C6%2010%2C6%20C%201.0301983%2C6.00505%2015.002631%2C6.011353%206%2C6%20L%201%2C6%20C%200.45%2C6%200%2C6.45%200%2C7%20l%200%2C2%20c%200%2C0.55%200.45%2C1%201%2C1%20l%205%2C0%20c%208.988585%2C-0.019732%20-5.02893401%2C-0.018728%204%2C0%20l%205%2C0%20c%200.55%2C0%201%2C-0.45%201%2C-1%20L%2016%2C7%20C%2016%2C6.45%2015.55%2C6%2015%2C6%20z%22%20id%3D%22path3%22%20style%3D%22fill%3A%23FD6EB6%22%20%2F%3E%3C%2Fsvg%3E'); -} -.x6-widget-dnd { - position: absolute; - top: -10000px; - left: -10000px; - z-index: 999999; - display: none; - cursor: move; - opacity: 0.7; - pointer-events: 'cursor'; -} -.x6-widget-dnd.dragging { - display: inline-block; -} -.x6-widget-dnd.dragging * { - pointer-events: none !important; -} -.x6-widget-dnd .x6-graph { - background: transparent; - box-shadow: none; -} -.x6-widget-halo { - position: absolute; - pointer-events: none; -} -.x6-widget-halo-content { - position: absolute; - top: 100%; - padding: 6px; - font-size: 10px; - line-height: 14px; - text-align: center; - border-radius: 6px; -} -.x6-widget-halo-handles + .x6-widget-halo-content { - right: -20px; - left: -20px; - margin-top: 30px; -} -.x6-widget-halo-handles.x6-widget-handle-small + .x6-widget-halo-content { - margin-top: 25px; -} -.x6-widget-halo-handles.x6-widget-handle-small + .x6-widget-halo-content { - margin-top: 20px; -} -.x6-widget-halo-handles.x6-widget-handle-pie + .x6-widget-halo-content { - right: 0; - left: 0; - margin-top: 10px; -} -.x6-widget-halo-content { - color: #fff; - background-color: #6a6b8a; -} -.x6-widget-halo.type-node .x6-widget-handle-toolbar .x6-widget-handle-remove { - position: absolute; - right: 100%; - bottom: 100%; - margin-right: 6px; - margin-bottom: 3px; -} -.x6-widget-halo.type-edge .x6-widget-handle-surround .x6-widget-handle-remove { - background-color: #fff; - border-radius: 50%; -} -.x6-widget-halo.type-edge .x6-widget-handle-toolbar { - margin-top: -60px; - margin-left: -18px; -} -.x6-widget-halo.type-edge .x6-widget-handle-toolbar::after { - top: -22px; - left: -9px; -} -.x6-widget-minimap { - position: relative; - display: table-cell; - box-sizing: border-box; - overflow: hidden; - text-align: center; - vertical-align: middle; - background-color: #fff; - user-select: none; -} -.x6-widget-minimap .x6-graph { - display: inline-block; - box-shadow: 0 0 4px 0 #eee; - cursor: pointer; -} -.x6-widget-minimap .x6-graph > svg { - pointer-events: none; - shape-rendering: optimizeSpeed; -} -.x6-widget-minimap .x6-graph .x6-node * { - /* stylelint-disable-next-line */ - vector-effect: initial; -} -.x6-widget-minimap-viewport { - position: absolute; - box-sizing: content-box !important; - margin: -2px 0 0 -2px; - border: 2px solid #31d0c6; - cursor: move; -} -.x6-widget-minimap-viewport-zoom { - position: absolute; - right: 0; - bottom: 0; - box-sizing: border-box; - width: 12px; - height: 12px; - margin: 0 -6px -6px 0; - background-color: #fff; - border: 2px solid #31d0c6; - border-radius: 50%; - cursor: nwse-resize; -} -.x6-graph-scroller { - position: relative; - box-sizing: border-box; - overflow: scroll; - outline: none; -} -.x6-graph-scroller-content { - position: relative; -} -.x6-graph-scroller-background { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; -} -.x6-graph-scroller .x6-graph { - position: absolute; - display: inline-block; - margin: 0; - box-shadow: none; -} -.x6-graph-scroller .x6-graph > svg { - display: block; -} -.x6-graph-scroller.x6-graph-scroller-paged .x6-graph { - box-shadow: 0 0 4px 0 #eee; -} -.x6-graph-scroller.x6-graph-scroller-pannable[data-panning='false'] { - cursor: grab; - cursor: -moz-grab; - cursor: -webkit-grab; -} -.x6-graph-scroller.x6-graph-scroller-pannable[data-panning='true'] { - cursor: grabbing; - cursor: -moz-grabbing; - cursor: -webkit-grabbing; - user-select: none; -} -.x6-graph-pagebreak { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; -} -.x6-graph-pagebreak-vertical { - position: absolute; - top: 0; - bottom: 0; - box-sizing: border-box; - width: 1px; - border-left: 1px dashed #bdbdbd; -} -.x6-graph-pagebreak-horizontal { - position: absolute; - right: 0; - left: 0; - box-sizing: border-box; - height: 1px; - border-top: 1px dashed #bdbdbd; -} -.x6-widget-selection { - position: absolute; - display: none; - width: 0; - height: 0; - touch-action: none; -} -.x6-widget-selection-rubberband { - display: block; - overflow: visible; - opacity: 0.3; -} -.x6-widget-selection-selected { - display: block; -} -.x6-widget-selection-box { - cursor: move; -} -.x6-widget-selection-inner[data-selection-length='0'], -.x6-widget-selection-inner[data-selection-length='1'] { - display: none; -} -.x6-widget-selection-content { - position: absolute; - top: 100%; - right: -20px; - left: -20px; - margin-top: 30px; - padding: 6px; - line-height: 14px; - text-align: center; - border-radius: 6px; -} -.x6-widget-selection-content:empty { - display: none; -} -.x6-widget-selection-rubberband { - background-color: #3498db; - border: 2px solid #2980b9; -} -.x6-widget-selection-box { - box-sizing: content-box !important; - margin-top: -4px; - margin-left: -4px; - padding-right: 4px; - padding-bottom: 4px; - border: 2px dashed #feb663; - box-shadow: 2px 2px 5px #d3d3d3; -} -.x6-widget-selection-inner { - box-sizing: content-box !important; - margin-top: -8px; - margin-left: -8px; - padding-right: 12px; - padding-bottom: 12px; - border: 2px solid #feb663; - box-shadow: 2px 2px 5px #d3d3d3; -} -.x6-widget-selection-content { - color: #fff; - font-size: 10px; - background-color: #6a6b8a; -} -.x6-widget-snapline { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - pointer-events: none; -} -.x6-widget-snapline-vertical, -.x6-widget-snapline-horizontal { - position: absolute; - opacity: 1; - pointer-events: none; -} -.x6-widget-snapline-horizontal { - border-bottom: 1px solid #2ecc71; -} -.x6-widget-snapline-vertical { - border-right: 1px solid #2ecc71; -} -.x6-widget-stencil { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; -} -.x6-widget-stencil::after { - position: absolute; - top: 0; - display: block; - width: 100%; - height: 20px; - padding: 8px 0; - line-height: 20px; - text-align: center; - opacity: 0; - transition: top 0.1s linear, opacity 0.1s linear; - content: ' '; - pointer-events: none; -} -.x6-widget-stencil-content { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - height: auto; - overflow-x: hidden; - overflow-y: auto; -} -.x6-widget-stencil .x6-node [magnet]:not([magnet='passive']) { - pointer-events: none; -} -.x6-widget-stencil-group { - padding: 0; - padding-bottom: 8px; - overflow: hidden; - user-select: none; -} -.x6-widget-stencil-group.collapsed { - height: auto; - padding-bottom: 0; -} -.x6-widget-stencil-group-title { - position: relative; - margin-top: 0; - margin-bottom: 0; - padding: 4px; - cursor: pointer; -} -.x6-widget-stencil-title, -.x6-widget-stencil-group > .x6-widget-stencil-group-title { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - user-select: none; -} -.x6-widget-stencil .unmatched { - opacity: 0.3; -} -.x6-widget-stencil .x6-node.unmatched { - display: none; -} -.x6-widget-stencil-group.unmatched { - display: none; -} -.x6-widget-stencil-search-text { - position: relative; - z-index: 1; - box-sizing: border-box; - width: 100%; - height: 30px; - max-height: 30px; - line-height: 30px; - outline: 0; -} -.x6-widget-stencil.not-found::after { - opacity: 1; - content: attr(data-not-found-text); -} -.x6-widget-stencil.not-found.searchable::after { - top: 30px; -} -.x6-widget-stencil.not-found.searchable.collapsable::after { - top: 50px; -} -.x6-widget-stencil { - color: #333; - background: #f5f5f5; -} -.x6-widget-stencil-content { - position: absolute; -} -.x6-widget-stencil.collapsable > .x6-widget-stencil-content { - top: 32px; -} -.x6-widget-stencil.searchable > .x6-widget-stencil-content { - top: 80px; -} -.x6-widget-stencil.not-found::after { - position: absolute; -} -.x6-widget-stencil.not-found.searchable.collapsable::after { - top: 80px; -} -.x6-widget-stencil.not-found.searchable::after { - top: 60px; -} -.x6-widget-stencil-group { - height: auto; - margin-bottom: 1px; - padding: 0; - transition: none; -} -.x6-widget-stencil-group .x6-graph { - background: transparent; - box-shadow: none; -} -.x6-widget-stencil-group.collapsed { - height: auto; - max-height: 31px; -} -.x6-widget-stencil-title, -.x6-widget-stencil-group > .x6-widget-stencil-group-title { - position: relative; - left: 0; - box-sizing: border-box; - width: 100%; - height: 32px; - padding: 0 5px 0 8px; - color: #666; - font-weight: 700; - font-size: 12px; - line-height: 32px; - cursor: default; - transition: all 0.3; -} -.x6-widget-stencil-title:hover, -.x6-widget-stencil-group > .x6-widget-stencil-group-title:hover { - color: #444; -} -.x6-widget-stencil-title { - background: #e9e9e9; -} -.x6-widget-stencil-group > .x6-widget-stencil-group-title { - background: #ededed; -} -.x6-widget-stencil.collapsable > .x6-widget-stencil-title, -.x6-widget-stencil-group.collapsable > .x6-widget-stencil-group-title { - padding-left: 32px; - cursor: pointer; -} -.x6-widget-stencil.collapsable > .x6-widget-stencil-title::before, -.x6-widget-stencil-group.collapsable > .x6-widget-stencil-group-title::before { - position: absolute; - top: 6px; - left: 8px; - display: block; - width: 18px; - height: 18px; - margin: 0; - padding: 0; - background-color: transparent; - background-repeat: no-repeat; - background-position: 0 0; - border: none; - content: ' '; -} -.x6-widget-stencil.collapsable > .x6-widget-stencil-title::before, -.x6-widget-stencil-group.collapsable > .x6-widget-stencil-group-title::before { - background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIj48cGF0aCBkPSJNOS4zNzUuNUM0LjY4Ny41Ljg3NSA0LjMxMy44NzUgOWMwIDQuNjg4IDMuODEyIDguNSA4LjUgOC41IDQuNjg3IDAgOC41LTMuODEyIDguNS04LjUgMC00LjY4Ny0zLjgxMy04LjUtOC41LTguNXptMCAxNS44ODZDNS4zMDMgMTYuMzg2IDEuOTkgMTMuMDcyIDEuOTkgOXMzLjMxMi03LjM4NSA3LjM4NS03LjM4NVMxNi43NiA0LjkyOCAxNi43NiA5YzAgNC4wNzItMy4zMTMgNy4zODYtNy4zODUgNy4zODZ6Ii8+PHBhdGggZD0iTTEyLjc1MyA4LjQ0M0g1Ljk5N2EuNTU4LjU1OCAwIDAwMCAxLjExNmg2Ljc1NmEuNTU4LjU1OCAwIDAwMC0xLjExNnoiLz48L2c+PC9zdmc+'); - opacity: 0.4; - transition: all 0.3s; -} -.x6-widget-stencil.collapsable > .x6-widget-stencil-title:hover::before, -.x6-widget-stencil-group.collapsable > .x6-widget-stencil-group-title:hover::before { - opacity: 0.6; -} -.x6-widget-stencil.collapsable.collapsed > .x6-widget-stencil-title::before, -.x6-widget-stencil-group.collapsable.collapsed > .x6-widget-stencil-group-title::before { - background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIj48cGF0aCBkPSJNOS4zNzUuNUM0LjY4Ny41Ljg3NSA0LjMxMy44NzUgOWMwIDQuNjg4IDMuODEyIDguNSA4LjUgOC41IDQuNjg3IDAgOC41LTMuODEyIDguNS04LjUgMC00LjY4Ny0zLjgxMy04LjUtOC41LTguNXptMCAxNS44ODZDNS4zMDMgMTYuMzg2IDEuOTkgMTMuMDcyIDEuOTkgOXMzLjMxMi03LjM4NSA3LjM4NS03LjM4NVMxNi43NiA0LjkyOCAxNi43NiA5YzAgNC4wNzItMy4zMTMgNy4zODYtNy4zODUgNy4zODZ6Ii8+PHBhdGggZD0iTTEyLjc1MyA4LjQ0M0g1Ljk5N2EuNTU4LjU1OCAwIDAwMCAxLjExNmg2Ljc1NmEuNTU4LjU1OCAwIDAwMC0xLjExNnoiLz48cGF0aCBkPSJNOC44MTcgNS42MjN2Ni43NTZhLjU1OC41NTggMCAwMDEuMTE2IDBWNS42MjNhLjU1OC41NTggMCAxMC0xLjExNiAweiIvPjwvZz48L3N2Zz4='); - opacity: 0.4; -} -.x6-widget-stencil.collapsable.collapsed > .x6-widget-stencil-title:hover::before, -.x6-widget-stencil-group.collapsable.collapsed > .x6-widget-stencil-group-title:hover::before { - opacity: 0.6; -} -.x6-widget-stencil input[type='search'] { - -webkit-appearance: textfield; -} -.x6-widget-stencil input[type='search']::-webkit-search-cancel-button, -.x6-widget-stencil input[type='search']::-webkit-search-decoration { - -webkit-appearance: none; -} -.x6-widget-stencil-search-text { - display: block; - width: 90%; - margin: 8px 5%; - padding-left: 8px; - color: #333; - background: #fff; - border: 1px solid #e9e9e9; - border-radius: 12px; - outline: 0; -} -.x6-widget-stencil-search-text:focus { - outline: 0; -} -.x6-widget-stencil::after { - color: #808080; - font-weight: 600; - font-size: 12px; - background: 0 0; -} -.x6-widget-transform { - position: absolute; - box-sizing: content-box !important; - margin: -5px 0 0 -5px; - padding: 4px; - border: 1px dashed #000; - border-radius: 5px; - user-select: none; - pointer-events: none; -} -.x6-widget-transform > div { - position: absolute; - box-sizing: border-box; - background-color: #fff; - border: 1px solid #000; - transition: background-color 0.2s; - pointer-events: auto; - -webkit-user-drag: none; - user-drag: none; - /* stylelint-disable-line */ -} -.x6-widget-transform > div:hover { - background-color: #d3d3d3; -} -.x6-widget-transform-cursor-n { - cursor: n-resize; -} -.x6-widget-transform-cursor-s { - cursor: s-resize; -} -.x6-widget-transform-cursor-e { - cursor: e-resize; -} -.x6-widget-transform-cursor-w { - cursor: w-resize; -} -.x6-widget-transform-cursor-ne { - cursor: ne-resize; -} -.x6-widget-transform-cursor-nw { - cursor: nw-resize; -} -.x6-widget-transform-cursor-se { - cursor: se-resize; -} -.x6-widget-transform-cursor-sw { - cursor: sw-resize; -} -.x6-widget-transform-resize { - width: 10px; - height: 10px; - border-radius: 6px; -} -.x6-widget-transform-resize[data-position='top-left'] { - top: -5px; - left: -5px; -} -.x6-widget-transform-resize[data-position='top-right'] { - top: -5px; - right: -5px; -} -.x6-widget-transform-resize[data-position='bottom-left'] { - bottom: -5px; - left: -5px; -} -.x6-widget-transform-resize[data-position='bottom-right'] { - right: -5px; - bottom: -5px; -} -.x6-widget-transform-resize[data-position='top'] { - top: -5px; - left: 50%; - margin-left: -5px; -} -.x6-widget-transform-resize[data-position='bottom'] { - bottom: -5px; - left: 50%; - margin-left: -5px; -} -.x6-widget-transform-resize[data-position='left'] { - top: 50%; - left: -5px; - margin-top: -5px; -} -.x6-widget-transform-resize[data-position='right'] { - top: 50%; - right: -5px; - margin-top: -5px; -} -.x6-widget-transform.prevent-aspect-ratio .x6-widget-transform-resize[data-position='top'], -.x6-widget-transform.prevent-aspect-ratio .x6-widget-transform-resize[data-position='bottom'], -.x6-widget-transform.prevent-aspect-ratio .x6-widget-transform-resize[data-position='left'], -.x6-widget-transform.prevent-aspect-ratio .x6-widget-transform-resize[data-position='right'] { - display: none; -} -.x6-widget-transform.no-orth-resize .x6-widget-transform-resize[data-position='bottom'], -.x6-widget-transform.no-orth-resize .x6-widget-transform-resize[data-position='left'], -.x6-widget-transform.no-orth-resize .x6-widget-transform-resize[data-position='right'], -.x6-widget-transform.no-orth-resize .x6-widget-transform-resize[data-position='top'] { - display: none; -} -.x6-widget-transform.no-resize .x6-widget-transform-resize { - display: none; -} -.x6-widget-transform-rotate { - top: -20px; - left: -20px; - width: 12px; - height: 12px; - border-radius: 6px; - cursor: crosshair; -} -.x6-widget-transform.no-rotate .x6-widget-transform-rotate { - display: none; -} -.x6-widget-transform-active { - border-color: transparent; - pointer-events: all; -} -.x6-widget-transform-active > div { - display: none; -} -.x6-widget-transform-active > .x6-widget-transform-active-handle { - display: block; - background-color: #808080; -} -.x6-widget-knob { - position: absolute; - box-sizing: border-box; - width: 16px; - height: 16px; - margin-top: -8px; - margin-left: -8px; - cursor: pointer; - user-select: none; -} -.x6-widget-knob::before, -.x6-widget-knob::after { - position: absolute; - transform: rotate(45deg); - content: ''; -} -.x6-widget-knob::before { - top: 4px; - left: 4px; - box-sizing: border-box; - width: 8px; - height: 8px; - background-color: #fff; -} -.x6-widget-knob::after { - top: 5px; - left: 5px; - box-sizing: border-box; - width: 6px; - height: 6px; - background-color: #fca000; -} -.x6-graph-print { - position: relative; -} -.x6-graph-print .x6-graph-print-ready { - display: none; -} -.x6-graph-print .x6-graph-print-preview { - overflow: hidden !important; - background: #fff !important; -} -@media print { - html, - html > body.x6-graph-printing { - position: relative !important; - width: 100% !important; - height: 100% !important; - margin: 0 !important; - padding: 0 !important; - } - html > body.x6-graph-printing > * { - display: none !important; - } - html > body.x6-graph-printing > .x6-graph-print { - display: block !important; - } - .x6-graph-print { - top: 0 !important; - left: 0 !important; - margin: 0 !important; - padding: 0 !important; - overflow: hidden !important; - page-break-after: always; - background: #fff !important; - } - .x6-graph-print .x6-graph-print-ready { - display: none; - } -} .x6-cell-tool-editor { position: relative; display: inline-block; diff --git a/packages/x6/src/types/attr.ts b/packages/x6/src/types/attr.ts deleted file mode 100644 index dfece4a8132..00000000000 --- a/packages/x6/src/types/attr.ts +++ /dev/null @@ -1,1108 +0,0 @@ -import * as CSS from './csstype' - -type Booleanish = boolean | 'true' | 'false' - -export interface HTMLProps extends AllHTMLAttributes {} - -export interface SVGProps extends SVGAttributes {} - -export interface CSSProperties extends CSS.Properties { - /** - * The index signature was removed to enable closed typing for style - * using CSSType. You're able to use type assertion or module augmentation - * to add properties or an index signature of your own. - * - * For examples and more information, visit: - * https://github.com/frenic/csstype#what-should-i-do-when-i-get-type-errors - */ -} - -// All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/ -interface AriaAttributes { - /** Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application. */ - 'aria-activedescendant'?: string - /** Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute. */ - 'aria-atomic'?: boolean | 'false' | 'true' - /** - * Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be - * presented if they are made. - */ - 'aria-autocomplete'?: 'none' | 'inline' | 'list' | 'both' - /** Indicates an element is being modified and that assistive technologies MAY want to wait until the modifications are complete before exposing them to the user. */ - 'aria-busy'?: boolean | 'false' | 'true' - /** - * Indicates the current "checked" state of checkboxes, radio buttons, and other widgets. - * @see aria-pressed @see aria-selected. - */ - 'aria-checked'?: boolean | 'false' | 'mixed' | 'true' - /** - * Defines the total number of columns in a table, grid, or treegrid. - * @see aria-colindex. - */ - 'aria-colcount'?: number - /** - * Defines an element's column index or position with respect to the total number of columns within a table, grid, or treegrid. - * @see aria-colcount @see aria-colspan. - */ - 'aria-colindex'?: number - /** - * Defines the number of columns spanned by a cell or gridcell within a table, grid, or treegrid. - * @see aria-colindex @see aria-rowspan. - */ - 'aria-colspan'?: number - /** - * Identifies the element (or elements) whose contents or presence are controlled by the current element. - * @see aria-owns. - */ - 'aria-controls'?: string - /** Indicates the element that represents the current item within a container or set of related elements. */ - 'aria-current'?: - | boolean - | 'false' - | 'true' - | 'page' - | 'step' - | 'location' - | 'date' - | 'time' - /** - * Identifies the element (or elements) that describes the object. - * @see aria-labelledby - */ - 'aria-describedby'?: string - /** - * Identifies the element that provides a detailed, extended description for the object. - * @see aria-describedby. - */ - 'aria-details'?: string - /** - * Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable. - * @see aria-hidden @see aria-readonly. - */ - 'aria-disabled'?: boolean | 'false' | 'true' - /** - * Indicates what functions can be performed when a dragged object is released on the drop target. - * @deprecated in ARIA 1.1 - */ - 'aria-dropeffect'?: 'none' | 'copy' | 'execute' | 'link' | 'move' | 'popup' - /** - * Identifies the element that provides an error message for the object. - * @see aria-invalid @see aria-describedby. - */ - 'aria-errormessage'?: string - /** Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed. */ - 'aria-expanded'?: boolean | 'false' | 'true' - /** - * Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion, - * allows assistive technology to override the general default of reading in document source order. - */ - 'aria-flowto'?: string - /** - * Indicates an element's "grabbed" state in a drag-and-drop operation. - * @deprecated in ARIA 1.1 - */ - 'aria-grabbed'?: boolean | 'false' | 'true' - /** Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element. */ - 'aria-haspopup'?: - | boolean - | 'false' - | 'true' - | 'menu' - | 'listbox' - | 'tree' - | 'grid' - | 'dialog' - /** - * Indicates whether the element is exposed to an accessibility API. - * @see aria-disabled. - */ - 'aria-hidden'?: boolean | 'false' | 'true' - /** - * Indicates the entered value does not conform to the format expected by the application. - * @see aria-errormessage. - */ - 'aria-invalid'?: boolean | 'false' | 'true' | 'grammar' | 'spelling' - /** Indicates keyboard shortcuts that an author has implemented to activate or give focus to an element. */ - 'aria-keyshortcuts'?: string - /** - * Defines a string value that labels the current element. - * @see aria-labelledby. - */ - 'aria-label'?: string - /** - * Identifies the element (or elements) that labels the current element. - * @see aria-describedby. - */ - 'aria-labelledby'?: string - /** Defines the hierarchical level of an element within a structure. */ - 'aria-level'?: number - /** Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region. */ - 'aria-live'?: 'off' | 'assertive' | 'polite' - /** Indicates whether an element is modal when displayed. */ - 'aria-modal'?: boolean | 'false' | 'true' - /** Indicates whether a text box accepts multiple lines of input or only a single line. */ - 'aria-multiline'?: boolean | 'false' | 'true' - /** Indicates that the user may select more than one item from the current selectable descendants. */ - 'aria-multiselectable'?: boolean | 'false' | 'true' - /** Indicates whether the element's orientation is horizontal, vertical, or unknown/ambiguous. */ - 'aria-orientation'?: 'horizontal' | 'vertical' - /** - * Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship - * between DOM elements where the DOM hierarchy cannot be used to represent the relationship. - * @see aria-controls. - */ - 'aria-owns'?: string - /** - * Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value. - * A hint could be a sample value or a brief description of the expected format. - */ - 'aria-placeholder'?: string - /** - * Defines an element's number or position in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM. - * @see aria-setsize. - */ - 'aria-posinset'?: number - /** - * Indicates the current "pressed" state of toggle buttons. - * @see aria-checked @see aria-selected. - */ - 'aria-pressed'?: boolean | 'false' | 'mixed' | 'true' - /** - * Indicates that the element is not editable, but is otherwise operable. - * @see aria-disabled. - */ - 'aria-readonly'?: boolean | 'false' | 'true' - /** - * Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified. - * @see aria-atomic. - */ - 'aria-relevant'?: 'additions' | 'additions text' | 'all' | 'removals' | 'text' - /** Indicates that user input is required on the element before a form may be submitted. */ - 'aria-required'?: boolean | 'false' | 'true' - /** Defines a human-readable, author-localized description for the role of an element. */ - 'aria-roledescription'?: string - /** - * Defines the total number of rows in a table, grid, or treegrid. - * @see aria-rowindex. - */ - 'aria-rowcount'?: number - /** - * Defines an element's row index or position with respect to the total number of rows within a table, grid, or treegrid. - * @see aria-rowcount @see aria-rowspan. - */ - 'aria-rowindex'?: number - /** - * Defines the number of rows spanned by a cell or gridcell within a table, grid, or treegrid. - * @see aria-rowindex @see aria-colspan. - */ - 'aria-rowspan'?: number - /** - * Indicates the current "selected" state of various widgets. - * @see aria-checked @see aria-pressed. - */ - 'aria-selected'?: boolean | 'false' | 'true' - /** - * Defines the number of items in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM. - * @see aria-posinset. - */ - 'aria-setsize'?: number - /** Indicates if items in a table or grid are sorted in ascending or descending order. */ - 'aria-sort'?: 'none' | 'ascending' | 'descending' | 'other' - /** Defines the maximum allowed value for a range widget. */ - 'aria-valuemax'?: number - /** Defines the minimum allowed value for a range widget. */ - 'aria-valuemin'?: number - /** - * Defines the current value for a range widget. - * @see aria-valuetext. - */ - 'aria-valuenow'?: number - /** Defines the human readable text alternative of aria-valuenow for a range widget. */ - 'aria-valuetext'?: string -} - -interface HTMLAttributes extends AriaAttributes { - // React-specific Attributes - defaultChecked?: boolean - defaultValue?: string | number | string[] - suppressContentEditableWarning?: boolean - suppressHydrationWarning?: boolean - - // Standard HTML Attributes - accessKey?: string - class?: string - contentEditable?: Booleanish | 'inherit' - contextMenu?: string - dir?: string - draggable?: Booleanish - hidden?: boolean - id?: string - lang?: string - placeholder?: string - slot?: string - spellCheck?: Booleanish - style?: CSSProperties - tabIndex?: number - title?: string - translate?: 'yes' | 'no' - - // Unknown - radioGroup?: string // , - - // WAI-ARIA - role?: string - - // RDFa Attributes - about?: string - datatype?: string - inlist?: any - prefix?: string - property?: string - resource?: string - typeof?: string - vocab?: string - - // Non-standard Attributes - autoCapitalize?: string - autoCorrect?: string - autoSave?: string - color?: string - itemProp?: string - itemScope?: boolean - itemType?: string - itemID?: string - itemRef?: string - results?: number - security?: string - unselectable?: 'on' | 'off' - - // Living Standard - /** - * Hints at the type of data that might be entered by the user while editing the element or its contents - * @see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute - */ - inputMode?: - | 'none' - | 'text' - | 'tel' - | 'url' - | 'email' - | 'numeric' - | 'decimal' - | 'search' - /** - * Specify that a standard HTML element should behave like a defined custom built-in element - * @see https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is - */ - is?: string -} - -interface AllHTMLAttributes extends HTMLAttributes { - // Standard HTML Attributes - accept?: string - acceptCharset?: string - action?: string - allowFullScreen?: boolean - allowTransparency?: boolean - alt?: string - as?: string - async?: boolean - autoComplete?: string - autoFocus?: boolean - autoPlay?: boolean - capture?: boolean | string - cellPadding?: number | string - cellSpacing?: number | string - charSet?: string - challenge?: string - checked?: boolean - cite?: string - classID?: string - cols?: number - colSpan?: number - content?: string - controls?: boolean - coords?: string - crossOrigin?: string - data?: string - dateTime?: string - default?: boolean - defer?: boolean - disabled?: boolean - download?: any - encType?: string - form?: string - formAction?: string - formEncType?: string - formMethod?: string - formNoValidate?: boolean - formTarget?: string - frameBorder?: number | string - headers?: string - height?: number | string - high?: number - href?: string - hrefLang?: string - htmlFor?: string - httpEquiv?: string - integrity?: string - keyParams?: string - keyType?: string - kind?: string - label?: string - list?: string - loop?: boolean - low?: number - manifest?: string - marginHeight?: number - marginWidth?: number - max?: number | string - maxLength?: number - media?: string - mediaGroup?: string - method?: string - min?: number | string - minLength?: number - multiple?: boolean - muted?: boolean - name?: string - nonce?: string - noValidate?: boolean - open?: boolean - optimum?: number - pattern?: string - placeholder?: string - playsInline?: boolean - poster?: string - preload?: string - readOnly?: boolean - rel?: string - required?: boolean - reversed?: boolean - rows?: number - rowSpan?: number - sandbox?: string - scope?: string - scoped?: boolean - scrolling?: string - seamless?: boolean - selected?: boolean - shape?: string - size?: number - sizes?: string - span?: number - src?: string - srcDoc?: string - srcLang?: string - srcSet?: string - start?: number - step?: number | string - summary?: string - target?: string - type?: string - useMap?: string - value?: string | string[] | number - width?: number | string - wmode?: string - wrap?: string -} - -export interface AnchorHTMLAttributes extends HTMLAttributes { - download?: any - href?: string - hrefLang?: string - media?: string - ping?: string - rel?: string - target?: string - type?: string - referrerPolicy?: string -} - -// eslint-disable-next-line:no-empty-interface -export interface AudioHTMLAttributes extends MediaHTMLAttributes {} - -export interface AreaHTMLAttributes extends HTMLAttributes { - alt?: string - coords?: string - download?: any - href?: string - hrefLang?: string - media?: string - rel?: string - shape?: string - target?: string -} - -export interface BaseHTMLAttributes extends HTMLAttributes { - href?: string - target?: string -} - -export interface BlockquoteHTMLAttributes extends HTMLAttributes { - cite?: string -} - -export interface ButtonHTMLAttributes extends HTMLAttributes { - autoFocus?: boolean - disabled?: boolean - form?: string - formAction?: string - formEncType?: string - formMethod?: string - formNoValidate?: boolean - formTarget?: string - name?: string - type?: 'submit' | 'reset' | 'button' - value?: string | string[] | number -} - -export interface CanvasHTMLAttributes extends HTMLAttributes { - height?: number | string - width?: number | string -} - -export interface ColHTMLAttributes extends HTMLAttributes { - span?: number - width?: number | string -} - -export interface ColgroupHTMLAttributes extends HTMLAttributes { - span?: number -} - -export interface DataHTMLAttributes extends HTMLAttributes { - value?: string | string[] | number -} - -export interface DetailsHTMLAttributes extends HTMLAttributes { - open?: boolean -} - -export interface DelHTMLAttributes extends HTMLAttributes { - cite?: string - dateTime?: string -} - -export interface DialogHTMLAttributes extends HTMLAttributes { - open?: boolean -} - -export interface EmbedHTMLAttributes extends HTMLAttributes { - height?: number | string - src?: string - type?: string - width?: number | string -} - -export interface FieldsetHTMLAttributes extends HTMLAttributes { - disabled?: boolean - form?: string - name?: string -} - -export interface FormHTMLAttributes extends HTMLAttributes { - acceptCharset?: string - action?: string - autoComplete?: string - encType?: string - method?: string - name?: string - noValidate?: boolean - target?: string -} - -export interface HtmlHTMLAttributes extends HTMLAttributes { - manifest?: string -} - -export interface IframeHTMLAttributes extends HTMLAttributes { - allow?: string - allowFullScreen?: boolean - allowTransparency?: boolean - frameBorder?: number | string - height?: number | string - marginHeight?: number - marginWidth?: number - name?: string - referrerPolicy?: string - sandbox?: string - scrolling?: string - seamless?: boolean - src?: string - srcDoc?: string - width?: number | string -} - -export interface ImgHTMLAttributes extends HTMLAttributes { - alt?: string - crossOrigin?: 'anonymous' | 'use-credentials' | '' - decoding?: 'async' | 'auto' | 'sync' - height?: number | string - loading?: 'eager' | 'lazy' - referrerPolicy?: 'no-referrer' | 'origin' | 'unsafe-url' - sizes?: string - src?: string - srcSet?: string - useMap?: string - width?: number | string -} - -export interface InsHTMLAttributes extends HTMLAttributes { - cite?: string - dateTime?: string -} - -export interface InputHTMLAttributes extends HTMLAttributes { - accept?: string - alt?: string - autoComplete?: string - autoFocus?: boolean - capture?: boolean | string // https://www.w3.org/TR/html-media-capture/#the-capture-attribute - checked?: boolean - crossOrigin?: string - disabled?: boolean - form?: string - formAction?: string - formEncType?: string - formMethod?: string - formNoValidate?: boolean - formTarget?: string - height?: number | string - list?: string - max?: number | string - maxLength?: number - min?: number | string - minLength?: number - multiple?: boolean - name?: string - pattern?: string - placeholder?: string - readOnly?: boolean - required?: boolean - size?: number - src?: string - step?: number | string - type?: string - value?: string | string[] | number - width?: number | string -} - -export interface KeygenHTMLAttributes extends HTMLAttributes { - autoFocus?: boolean - challenge?: string - disabled?: boolean - form?: string - keyType?: string - keyParams?: string - name?: string -} - -export interface LabelHTMLAttributes extends HTMLAttributes { - form?: string - htmlFor?: string -} - -export interface LiHTMLAttributes extends HTMLAttributes { - value?: string | string[] | number -} - -export interface LinkHTMLAttributes extends HTMLAttributes { - as?: string - crossOrigin?: string - href?: string - hrefLang?: string - integrity?: string - media?: string - rel?: string - sizes?: string - type?: string -} - -export interface MapHTMLAttributes extends HTMLAttributes { - name?: string -} - -export interface MenuHTMLAttributes extends HTMLAttributes { - type?: string -} - -export interface MediaHTMLAttributes extends HTMLAttributes { - autoPlay?: boolean - controls?: boolean - controlsList?: string - crossOrigin?: string - loop?: boolean - mediaGroup?: string - muted?: boolean - playsinline?: boolean - preload?: string - src?: string -} - -export interface MetaHTMLAttributes extends HTMLAttributes { - charSet?: string - content?: string - httpEquiv?: string - name?: string -} - -export interface MeterHTMLAttributes extends HTMLAttributes { - form?: string - high?: number - low?: number - max?: number | string - min?: number | string - optimum?: number - value?: string | string[] | number -} - -export interface QuoteHTMLAttributes extends HTMLAttributes { - cite?: string -} - -export interface ObjectHTMLAttributes extends HTMLAttributes { - classID?: string - data?: string - form?: string - height?: number | string - name?: string - type?: string - useMap?: string - width?: number | string - wmode?: string -} - -export interface OlHTMLAttributes extends HTMLAttributes { - reversed?: boolean - start?: number - type?: '1' | 'a' | 'A' | 'i' | 'I' -} - -export interface OptgroupHTMLAttributes extends HTMLAttributes { - disabled?: boolean - label?: string -} - -export interface OptionHTMLAttributes extends HTMLAttributes { - disabled?: boolean - label?: string - selected?: boolean - value?: string | string[] | number -} - -export interface OutputHTMLAttributes extends HTMLAttributes { - form?: string - htmlFor?: string - name?: string -} - -export interface ParamHTMLAttributes extends HTMLAttributes { - name?: string - value?: string | string[] | number -} - -export interface ProgressHTMLAttributes extends HTMLAttributes { - max?: number | string - value?: string | string[] | number -} - -export interface ScriptHTMLAttributes extends HTMLAttributes { - async?: boolean - charSet?: string - crossOrigin?: string - defer?: boolean - integrity?: string - noModule?: boolean - nonce?: string - src?: string - type?: string -} - -export interface SelectHTMLAttributes extends HTMLAttributes { - autoComplete?: string - autoFocus?: boolean - disabled?: boolean - form?: string - multiple?: boolean - name?: string - required?: boolean - size?: number - value?: string | string[] | number -} - -export interface SourceHTMLAttributes extends HTMLAttributes { - media?: string - sizes?: string - src?: string - srcSet?: string - type?: string -} - -export interface StyleHTMLAttributes extends HTMLAttributes { - media?: string - nonce?: string - scoped?: boolean - type?: string -} - -export interface TableHTMLAttributes extends HTMLAttributes { - cellPadding?: number | string - cellSpacing?: number | string - summary?: string -} - -export interface TextareaHTMLAttributes extends HTMLAttributes { - autoComplete?: string - autoFocus?: boolean - cols?: number - dirName?: string - disabled?: boolean - form?: string - maxLength?: number - minLength?: number - name?: string - placeholder?: string - readOnly?: boolean - required?: boolean - rows?: number - value?: string | string[] | number - wrap?: string -} - -export interface TdHTMLAttributes extends HTMLAttributes { - align?: 'left' | 'center' | 'right' | 'justify' | 'char' - colSpan?: number - headers?: string - rowSpan?: number - scope?: string - abbr?: string - valign?: 'top' | 'middle' | 'bottom' | 'baseline' -} - -export interface ThHTMLAttributes extends HTMLAttributes { - align?: 'left' | 'center' | 'right' | 'justify' | 'char' - colSpan?: number - headers?: string - rowSpan?: number - scope?: string - abbr?: string -} - -export interface TimeHTMLAttributes extends HTMLAttributes { - dateTime?: string -} - -export interface TrackHTMLAttributes extends HTMLAttributes { - default?: boolean - kind?: string - label?: string - src?: string - srcLang?: string -} - -export interface VideoHTMLAttributes extends MediaHTMLAttributes { - height?: number | string - playsInline?: boolean - poster?: string - width?: number | string - disablePictureInPicture?: boolean -} - -export interface WebViewHTMLAttributes extends HTMLAttributes { - allowFullScreen?: boolean - allowpopups?: boolean - autoFocus?: boolean - autosize?: boolean - blinkfeatures?: string - disableblinkfeatures?: string - disableguestresize?: boolean - disablewebsecurity?: boolean - guestinstance?: string - httpreferrer?: string - nodeintegration?: boolean - partition?: string - plugins?: boolean - preload?: string - src?: string - useragent?: string - webpreferences?: string -} - -export interface SVGAttributes extends AriaAttributes { - class?: string - color?: string - height?: number | string - id?: string - lang?: string - max?: number | string - media?: string - method?: string - min?: number | string - name?: string - style?: CSSProperties - target?: string - type?: string - width?: number | string - - // Other HTML properties supported by SVG elements in browsers - role?: string - tabIndex?: number - crossOrigin?: 'anonymous' | 'use-credentials' | '' - - // SVG Specific attributes - accentHeight?: number | string - accumulate?: 'none' | 'sum' - additive?: 'replace' | 'sum' - alignmentBaseline?: - | 'auto' - | 'baseline' - | 'before-edge' - | 'text-before-edge' - | 'middle' - | 'central' - | 'after-edge' - | 'text-after-edge' - | 'ideographic' - | 'alphabetic' - | 'hanging' - | 'mathematical' - | 'inherit' - allowReorder?: 'no' | 'yes' - alphabetic?: number | string - amplitude?: number | string - arabicForm?: 'initial' | 'medial' | 'terminal' | 'isolated' - ascent?: number | string - attributeName?: string - attributeType?: string - autoReverse?: Booleanish - azimuth?: number | string - baseFrequency?: number | string - baselineShift?: number | string - baseProfile?: number | string - bbox?: number | string - begin?: number | string - bias?: number | string - by?: number | string - calcMode?: number | string - capHeight?: number | string - clip?: number | string - clipPath?: string - clipPathUnits?: number | string - clipRule?: number | string - colorInterpolation?: number | string - colorInterpolationFilters?: 'auto' | 'sRGB' | 'linearRGB' | 'inherit' - colorProfile?: number | string - colorRendering?: number | string - contentScriptType?: number | string - contentStyleType?: number | string - cursor?: number | string - cx?: number | string - cy?: number | string - d?: string - decelerate?: number | string - descent?: number | string - diffuseConstant?: number | string - direction?: number | string - display?: number | string - divisor?: number | string - dominantBaseline?: number | string - dur?: number | string - dx?: number | string - dy?: number | string - edgeMode?: number | string - elevation?: number | string - enableBackground?: number | string - end?: number | string - exponent?: number | string - externalResourcesRequired?: Booleanish - fill?: string - fillOpacity?: number | string - fillRule?: 'nonzero' | 'evenodd' | 'inherit' - filter?: string - filterRes?: number | string - filterUnits?: number | string - floodColor?: number | string - floodOpacity?: number | string - focusable?: Booleanish | 'auto' - fontFamily?: string - fontSize?: number | string - fontSizeAdjust?: number | string - fontStretch?: number | string - fontStyle?: number | string - fontVariant?: number | string - fontWeight?: number | string - format?: number | string - from?: number | string - fx?: number | string - fy?: number | string - g1?: number | string - g2?: number | string - glyphName?: number | string - glyphOrientationHorizontal?: number | string - glyphOrientationVertical?: number | string - glyphRef?: number | string - gradientTransform?: string - gradientUnits?: string - hanging?: number | string - horizAdvX?: number | string - horizOriginX?: number | string - href?: string - ideographic?: number | string - imageRendering?: number | string - in2?: number | string - in?: string - intercept?: number | string - k1?: number | string - k2?: number | string - k3?: number | string - k4?: number | string - k?: number | string - kernelMatrix?: number | string - kernelUnitLength?: number | string - kerning?: number | string - keyPoints?: number | string - keySplines?: number | string - keyTimes?: number | string - lengthAdjust?: number | string - letterSpacing?: number | string - lightingColor?: number | string - limitingConeAngle?: number | string - local?: number | string - markerEnd?: string - markerHeight?: number | string - markerMid?: string - markerStart?: string - markerUnits?: number | string - markerWidth?: number | string - mask?: string - maskContentUnits?: number | string - maskUnits?: number | string - mathematical?: number | string - mode?: number | string - numOctaves?: number | string - offset?: number | string - opacity?: number | string - operator?: number | string - order?: number | string - orient?: number | string - orientation?: number | string - origin?: number | string - overflow?: number | string - overlinePosition?: number | string - overlineThickness?: number | string - paintOrder?: number | string - panose1?: number | string - path?: string - pathLength?: number | string - patternContentUnits?: string - patternTransform?: number | string - patternUnits?: string - pointerEvents?: number | string - points?: string - pointsAtX?: number | string - pointsAtY?: number | string - pointsAtZ?: number | string - preserveAlpha?: Booleanish - preserveAspectRatio?: string - primitiveUnits?: number | string - r?: number | string - radius?: number | string - refX?: number | string - refY?: number | string - renderingIntent?: number | string - repeatCount?: number | string - repeatDur?: number | string - requiredExtensions?: number | string - requiredFeatures?: number | string - restart?: number | string - result?: string - rotate?: number | string - rx?: number | string - ry?: number | string - scale?: number | string - seed?: number | string - shapeRendering?: number | string - slope?: number | string - spacing?: number | string - specularConstant?: number | string - specularExponent?: number | string - speed?: number | string - spreadMethod?: string - startOffset?: number | string - stdDeviation?: number | string - stemh?: number | string - stemv?: number | string - stitchTiles?: number | string - stopColor?: string - stopOpacity?: number | string - strikethroughPosition?: number | string - strikethroughThickness?: number | string - string?: number | string - stroke?: string - strokeDasharray?: string | number - strokeDashoffset?: string | number - strokeLinecap?: 'butt' | 'round' | 'square' | 'inherit' - strokeLinejoin?: 'miter' | 'round' | 'bevel' | 'inherit' - strokeMiterlimit?: number | string - strokeOpacity?: number | string - strokeWidth?: number | string - surfaceScale?: number | string - systemLanguage?: number | string - tableValues?: number | string - targetX?: number | string - targetY?: number | string - textAnchor?: string - textDecoration?: number | string - textLength?: number | string - textRendering?: number | string - to?: number | string - transform?: string - u1?: number | string - u2?: number | string - underlinePosition?: number | string - underlineThickness?: number | string - unicode?: number | string - unicodeBidi?: number | string - unicodeRange?: number | string - unitsPerEm?: number | string - vAlphabetic?: number | string - values?: string - vectorEffect?: number | string - version?: string - vertAdvY?: number | string - vertOriginX?: number | string - vertOriginY?: number | string - vHanging?: number | string - vIdeographic?: number | string - viewBox?: string - viewTarget?: number | string - visibility?: number | string - vMathematical?: number | string - widths?: number | string - wordSpacing?: number | string - writingMode?: number | string - x1?: number | string - x2?: number | string - x?: number | string - xChannelSelector?: string - xHeight?: number | string - xlinkActuate?: string - xlinkArcrole?: string - xlinkHref?: string - xlinkRole?: string - xlinkShow?: string - xlinkTitle?: string - xlinkType?: string - xmlBase?: string - xmlLang?: string - xmlns?: string - xmlnsXlink?: string - xmlSpace?: string - y1?: number | string - y2?: number | string - y?: number | string - yChannelSelector?: string - z?: number | string - zoomAndPan?: string -} diff --git a/packages/x6/src/types/common.ts b/packages/x6/src/types/common.ts deleted file mode 100644 index 2e7688a4313..00000000000 --- a/packages/x6/src/types/common.ts +++ /dev/null @@ -1,17 +0,0 @@ -export type Nilable = T | null | undefined - -export interface KeyValue { - [key: string]: T -} - -export interface Padding { - left: number - top: number - right: number - bottom: number -} - -export interface Size { - width: number - height: number -} diff --git a/packages/x6/src/types/csstype.ts b/packages/x6/src/types/csstype.ts deleted file mode 100644 index d6ddde72309..00000000000 --- a/packages/x6/src/types/csstype.ts +++ /dev/null @@ -1,20561 +0,0 @@ -/* eslint-disable */ - -/** -* Auto generated file by copying from node_modules, do not modify it! -* Fix karma error "Can't find csstype [undefined] (required by ..." -*/ - -export {}; - -export type PropertyValue = TValue extends Array - ? Array - : TValue extends infer TUnpacked & {} - ? TUnpacked - : TValue; - -export interface StandardLonghandProperties { - /** - * The CSS **`align-content`** property sets the distribution of space between and around content items along a flexbox's cross-axis or a grid's block axis. - * - * **Syntax**: `normal | | | ? ` - * - * **Initial value**: `normal` - * - * --- - * - * _Supported in Flex Layout_ - * - * | Chrome | Firefox | Safari | Edge | IE | - * | :------: | :-----: | :-------: | :----: | :----: | - * | **29** | **28** | **9** | **12** | **11** | - * | 21 _-x-_ | | 6.1 _-x-_ | | | - * - * --- - * - * _Supported in Grid Layout_ - * - * | Chrome | Firefox | Safari | Edge | IE | - * | :----: | :-----: | :------: | :----: | :-: | - * | **57** | **52** | **10.1** | **16** | No | - * - * --- - * - * @see https://developer.mozilla.org/docs/Web/CSS/align-content - */ - alignContent?: Property.AlignContent; - /** - * The CSS **`align-items`** property sets the `align-self` value on all direct children as a group. In Flexbox, it controls the alignment of items on the Cross Axis. In Grid Layout, it controls the alignment of items on the Block Axis within their grid area. - * - * **Syntax**: `normal | stretch | | [ ? ]` - * - * **Initial value**: `normal` - * - * --- - * - * _Supported in Flex Layout_ - * - * | Chrome | Firefox | Safari | Edge | IE | - * | :------: | :-----: | :-----: | :----: | :----: | - * | **52** | **20** | **9** | **12** | **11** | - * | 21 _-x-_ | | 7 _-x-_ | | | - * - * --- - * - * _Supported in Grid Layout_ - * - * | Chrome | Firefox | Safari | Edge | IE | - * | :----: | :-----: | :------: | :----: | :-: | - * | **57** | **52** | **10.1** | **16** | No | - * - * --- - * - * @see https://developer.mozilla.org/docs/Web/CSS/align-items - */ - alignItems?: Property.AlignItems; - /** - * The **`align-self`** CSS property overrides a grid or flex item's `align-items` value. In Grid, it aligns the item inside the grid area. In Flexbox, it aligns the item on the cross axis. - * - * **Syntax**: `auto | normal | stretch | | ? ` - * - * **Initial value**: `auto` - * - * --- - * - * _Supported in Flex Layout_ - * - * | Chrome | Firefox | Safari | Edge | IE | - * | :------: | :-----: | :-------: | :----: | :----: | - * | **36** | **20** | **9** | **12** | **11** | - * | 21 _-x-_ | | 6.1 _-x-_ | | | - * - * --- - * - * _Supported in Grid Layout_ - * - * | Chrome | Firefox | Safari | Edge | IE | - * | :----: | :-----: | :------: | :----: | :----------: | - * | **57** | **52** | **10.1** | **16** | **10** _-x-_ | - * - * --- - * - * @see https://developer.mozilla.org/docs/Web/CSS/align-self - */ - alignSelf?: Property.AlignSelf; - /** - * The **`align-tracks`** CSS property sets the alignment in the masonry axis for grid containers that have masonry in their block axis. - * - * **Syntax**: `[ normal | | | ? ]#` - * - * **Initial value**: `normal` - * - * | Chrome | Firefox | Safari | Edge | IE | - * | :----: | :-----: | :----: | :--: | :-: | - * | No | n/a | No | No | No | - * - * @see https://developer.mozilla.org/docs/Web/CSS/align-tracks - */ - alignTracks?: Property.AlignTracks; - /** - * The **`animation-delay`** CSS property specifies the amount of time to wait from applying the animation to an element before beginning to perform the animation. The animation can start later, immediately from its beginning, or immediately and partway through the animation. - * - * **Syntax**: `