From f5df2d1b6fad47aa55304c580cfd2f604c12ebf6 Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Tue, 3 Sep 2024 15:40:11 +0200 Subject: [PATCH] feat(linkTools): add Control and RotateLabel link tool (#2723) --- examples/link-labels-ts/.gitignore | 3 + examples/link-labels-ts/README.md | 21 ++ examples/link-labels-ts/index.html | 13 + examples/link-labels-ts/package.json | 36 +++ examples/link-labels-ts/src/index.ts | 254 ++++++++++++++++++ examples/link-labels-ts/tsconfig.json | 9 + examples/link-labels-ts/webpack.config.js | 30 +++ .../src/{linkTools => cellTools}/Boundary.mjs | 0 .../src/{linkTools => cellTools}/Button.mjs | 0 .../src/{linkTools => cellTools}/Connect.mjs | 0 .../{elementTools => cellTools}/Control.mjs | 0 .../{linkTools => cellTools}/HoverConnect.mjs | 2 +- .../src/{linkTools => cellTools}/helpers.mjs | 0 packages/joint-core/src/dia/LinkView.mjs | 2 +- packages/joint-core/src/dia/ToolsView.mjs | 24 +- .../src/elementTools/HoverConnect.mjs | 4 +- .../joint-core/src/elementTools/index.mjs | 9 +- packages/joint-core/src/linkTools/Anchor.mjs | 2 +- .../joint-core/src/linkTools/RotateLabel.mjs | 110 ++++++++ .../joint-core/src/linkTools/Segments.mjs | 2 +- packages/joint-core/src/linkTools/index.mjs | 11 +- packages/joint-core/src/mvc/View.mjs | 1 - .../joint-core/test/jointjs/dia/linkTools.js | 38 +++ packages/joint-core/types/joint.d.ts | 48 ++++ yarn.lock | 15 ++ 25 files changed, 612 insertions(+), 22 deletions(-) create mode 100644 examples/link-labels-ts/.gitignore create mode 100644 examples/link-labels-ts/README.md create mode 100644 examples/link-labels-ts/index.html create mode 100644 examples/link-labels-ts/package.json create mode 100644 examples/link-labels-ts/src/index.ts create mode 100644 examples/link-labels-ts/tsconfig.json create mode 100644 examples/link-labels-ts/webpack.config.js rename packages/joint-core/src/{linkTools => cellTools}/Boundary.mjs (100%) rename packages/joint-core/src/{linkTools => cellTools}/Button.mjs (100%) rename packages/joint-core/src/{linkTools => cellTools}/Connect.mjs (100%) rename packages/joint-core/src/{elementTools => cellTools}/Control.mjs (100%) rename packages/joint-core/src/{linkTools => cellTools}/HoverConnect.mjs (98%) rename packages/joint-core/src/{linkTools => cellTools}/helpers.mjs (100%) create mode 100644 packages/joint-core/src/linkTools/RotateLabel.mjs diff --git a/examples/link-labels-ts/.gitignore b/examples/link-labels-ts/.gitignore new file mode 100644 index 000000000..69c575d17 --- /dev/null +++ b/examples/link-labels-ts/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +node_modules/ diff --git a/examples/link-labels-ts/README.md b/examples/link-labels-ts/README.md new file mode 100644 index 000000000..3a31da3aa --- /dev/null +++ b/examples/link-labels-ts/README.md @@ -0,0 +1,21 @@ +# JointJS Link Label Rotation +## Setup + +Use Yarn to run this demo. + +You need to build *JointJS* first. Navigate to the root folder and run: +```bash +yarn install +yarn run build +``` + +Navigate to this directory, then run: +```bash +yarn start +``` + +## License + +The *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE). + +Copyright © 2013-2024 client IO diff --git a/examples/link-labels-ts/index.html b/examples/link-labels-ts/index.html new file mode 100644 index 000000000..cff7573d9 --- /dev/null +++ b/examples/link-labels-ts/index.html @@ -0,0 +1,13 @@ + + + + + + + Link Labels Typescript | JointJS + + +
+ + + diff --git a/examples/link-labels-ts/package.json b/examples/link-labels-ts/package.json new file mode 100644 index 000000000..63b9cfa98 --- /dev/null +++ b/examples/link-labels-ts/package.json @@ -0,0 +1,36 @@ +{ + "name": "@joint/demo-link-labels-ts", + "version": "4.0.4", + "main": "src/index.ts", + "homepage": "https://jointjs.com", + "author": { + "name": "client IO", + "url": "https://client.io" + }, + "license": "MPL-2.0", + "private": true, + "installConfig": { + "hoistingLimits": "workspaces" + }, + "scripts": { + "start": "webpack-dev-server", + "tsc": "tsc" + }, + "dependencies": { + "@joint/core": "workspace:^" + }, + "devDependencies": { + "css-loader": "3.5.3", + "style-loader": "1.2.1", + "ts-loader": "^9.2.5", + "typescript": "^4.4.3", + "webpack": "^5.61.0", + "webpack-cli": "^4.8.0", + "webpack-dev-server": "^4.2.1" + }, + "volta": { + "node": "16.18.1", + "npm": "8.19.2", + "yarn": "3.4.1" + } +} diff --git a/examples/link-labels-ts/src/index.ts b/examples/link-labels-ts/src/index.ts new file mode 100644 index 000000000..c4333a386 --- /dev/null +++ b/examples/link-labels-ts/src/index.ts @@ -0,0 +1,254 @@ +import { dia, shapes, linkTools, util } from '@joint/core'; + +const graph = new dia.Graph({}, { + cellNamespace: shapes +}); + +const paper = new dia.Paper({ + el: document.getElementById('paper'), + width: 800, + height: 900, + overflow: true, + model: graph, + cellViewNamespace: shapes, + interactive: { + labelMove: true + }, + snapLabels: true, + gridSize: 10, + async: true, +}); + +// Example 1 +// Rotate labels on a link. + +const el1 = new shapes.standard.Rectangle({ + position: { + x: 10, + y: 270 + }, + size: { + width: 80, + height: 80 + }, + attrs: { + body: { + strokeWidth: 3 + } + } +}); +const el2 = new shapes.standard.Rectangle({ + position: { + x: 400, + y: 170 + }, + size: { + width: 80, + height: 80 + }, + attrs: { + body: { + strokeWidth: 3 + } + } +}); + +const l1 = new shapes.standard.Link({ + source: { + id: el1.id + }, + target: { + id: el2.id + }, + attrs: { + line: { + strokeWidth: 3 + } + }, + labels: [{ + markup: util.svg/* xml */` + + + `, + attrs: { + labelPath: { + fill: 'none', + stroke: '#333', + strokeWidth: 4, + strokeLinecap: 'round', + strokeLinejoin: 'round', + d: 'M 0 0 V -100 M -20 -100 v 20 H 20 v -20' + }, + labelCircle: { + fill: '#333', + stroke: '#FFF', + r: 3, + } + }, + position: { + distance: 0.5, + args: { + keepGradient: true + } + } + }, { + markup: util.svg/* xml */` + + + `, + attrs: { + labelPath: { + fill: 'none', + stroke: '#999', + strokeWidth: 2, + strokeDasharray: '5 5', + d: 'M 0 0 V -100 M -20 -80 v -20 H 20 v 20' + }, + labelCircle: { + fill: '#333', + stroke: '#FFF', + r: 5, + } + }, + position: { + distance: 0.8, + args: { + keepGradient: true + } + } + }, { + markup: util.svg/* xml */` + + + `, + attrs: { + labelPath: { + fill: 'none', + stroke: '#333', + strokeWidth: 3, + d: 'M 0 0 V -100 M -20 -80 L 0 -100 l 20 20' + }, + labelCircle: { + fill: '#333', + stroke: '#FFF', + r: 3, + } + }, + position: { + distance: 0.2, + args: { + keepGradient: false + } + } + }] +}); + +graph.addCells([el1, el2, l1]); + +l1.findView(paper).addTools(new dia.ToolsView({ + tools: [ + new linkTools.RotateLabel({ + labelIndex: 0, + offset: -60, + }), + new linkTools.RotateLabel({ + labelIndex: 1, + offset: -125, + buttonColor: '#fff', + iconColor: '#333', + outlineColor: '#333', + scale: 1.5, + }), + new linkTools.RotateLabel({ + labelIndex: 2, + offset: -115, + }) + ] +})); + +// Example 2 +// Add or remove labels on a link with buttons. + +const l2 = new shapes.standard.Link({ + source: { + x: 10, + y: 500 + }, + target: { + x: 400, + y: 570 + }, + attrs: { + line: { + strokeWidth: 3 + } + } +}); + +graph.addCells([l2]); + +l2.findView(paper).addTools(new dia.ToolsView({ + tools: [ + new linkTools.Button({ + attributes: { + cursor: 'pointer' + }, + markup: util.svg/* xml */` + + + `, + distance: '50%', + visibility: (view) => !view.model.hasLabels(), + action: (_evt, view) => { + view.model.appendLabel({ + markup: util.svg/* xml */` + + + `, + attrs: { + labelBody: { + ref: 'labelText', + fill: '#fff', + stroke: '#131E29', + strokeWidth: 2, + width: 'calc(w + 10)', + height: 'calc(h + 10)', + x: 'calc(x - 5)', + y: 'calc(y - 5)', + }, + labelText: { + text: 'Label', + textAnchor: 'middle', + textVerticalAnchor: 'middle', + fill: '#131E29', + fontSize: 16, + fontFamily: 'sans-serif', + } + }, + position: { + distance: 0.5, + args: { + keepGradient: true + } + } + }); + }, + }), + new linkTools.Button({ + attributes: { + cursor: 'pointer' + }, + markup: util.svg/* xml */` + + + `, + distance: '50%', + offset: -30, + visibility: (view) => view.model.hasLabels(), + action: (_evt, view) => { + view.model.removeLabel(0); + } + }) + ] +})); + diff --git a/examples/link-labels-ts/tsconfig.json b/examples/link-labels-ts/tsconfig.json new file mode 100644 index 000000000..a387de8d5 --- /dev/null +++ b/examples/link-labels-ts/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noImplicitAny": false, + "sourceMap": false, + "outDir": "./build" + } +} diff --git a/examples/link-labels-ts/webpack.config.js b/examples/link-labels-ts/webpack.config.js new file mode 100644 index 000000000..0d287f717 --- /dev/null +++ b/examples/link-labels-ts/webpack.config.js @@ -0,0 +1,30 @@ +const path = require('path'); + +module.exports = { + resolve: { + extensions: ['.ts', '.tsx', '.js'] + }, + entry: './src/index.ts', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist'), + publicPath: '/dist/' + }, + mode: 'development', + module: { + rules: [ + { test: /\.ts$/, loader: 'ts-loader' }, + { + test: /\.css$/, + sideEffects: true, + use: ['style-loader', 'css-loader'], + } + ] + }, + devServer: { + static: { + directory: __dirname, + }, + compress: true + }, +}; diff --git a/packages/joint-core/src/linkTools/Boundary.mjs b/packages/joint-core/src/cellTools/Boundary.mjs similarity index 100% rename from packages/joint-core/src/linkTools/Boundary.mjs rename to packages/joint-core/src/cellTools/Boundary.mjs diff --git a/packages/joint-core/src/linkTools/Button.mjs b/packages/joint-core/src/cellTools/Button.mjs similarity index 100% rename from packages/joint-core/src/linkTools/Button.mjs rename to packages/joint-core/src/cellTools/Button.mjs diff --git a/packages/joint-core/src/linkTools/Connect.mjs b/packages/joint-core/src/cellTools/Connect.mjs similarity index 100% rename from packages/joint-core/src/linkTools/Connect.mjs rename to packages/joint-core/src/cellTools/Connect.mjs diff --git a/packages/joint-core/src/elementTools/Control.mjs b/packages/joint-core/src/cellTools/Control.mjs similarity index 100% rename from packages/joint-core/src/elementTools/Control.mjs rename to packages/joint-core/src/cellTools/Control.mjs diff --git a/packages/joint-core/src/linkTools/HoverConnect.mjs b/packages/joint-core/src/cellTools/HoverConnect.mjs similarity index 98% rename from packages/joint-core/src/linkTools/HoverConnect.mjs rename to packages/joint-core/src/cellTools/HoverConnect.mjs index 419cb233a..9b669c450 100644 --- a/packages/joint-core/src/linkTools/HoverConnect.mjs +++ b/packages/joint-core/src/cellTools/HoverConnect.mjs @@ -1,4 +1,4 @@ -import { Connect } from '../linkTools/Connect.mjs'; +import { Connect } from './Connect.mjs'; import V from '../V/index.mjs'; import $ from '../mvc/Dom/index.mjs'; import * as util from '../util/index.mjs'; diff --git a/packages/joint-core/src/linkTools/helpers.mjs b/packages/joint-core/src/cellTools/helpers.mjs similarity index 100% rename from packages/joint-core/src/linkTools/helpers.mjs rename to packages/joint-core/src/cellTools/helpers.mjs diff --git a/packages/joint-core/src/dia/LinkView.mjs b/packages/joint-core/src/dia/LinkView.mjs index db98f4234..774a3972b 100644 --- a/packages/joint-core/src/dia/LinkView.mjs +++ b/packages/joint-core/src/dia/LinkView.mjs @@ -63,7 +63,7 @@ export const LinkView = CellView.extend({ attrs: [Flags.UPDATE], router: [Flags.UPDATE], connector: [Flags.CONNECTOR], - labels: [Flags.LABELS], + labels: [Flags.LABELS, Flags.TOOLS], labelMarkup: [Flags.LABELS], vertices: [Flags.UPDATE], source: [Flags.SOURCE, Flags.UPDATE], diff --git a/packages/joint-core/src/dia/ToolsView.mjs b/packages/joint-core/src/dia/ToolsView.mjs index 32036c72f..180f0889e 100644 --- a/packages/joint-core/src/dia/ToolsView.mjs +++ b/packages/joint-core/src/dia/ToolsView.mjs @@ -44,27 +44,37 @@ export const ToolsView = mvc.View.extend({ update: function(opt) { opt || (opt = {}); - var tools = this.tools; + const tools = this.tools; if (!tools) return this; - var isRendered = this.isRendered; - for (var i = 0, n = tools.length; i < n; i++) { - var tool = tools[i]; + const n = tools.length; + const wasRendered = this.isRendered; + for (let i = 0; i < n; i++) { + const tool = tools[i]; tool.updateVisibility(); if (!tool.isVisible()) continue; - if (!isRendered) { + if (!this.isRendered) { + // There is at least one visible tool + this.isRendered = Array(n).fill(false); + } + if (!this.isRendered[i]) { // First update executes render() tool.render(); + this.isRendered[i] = true; } else if (opt.tool !== tool.cid) { tool.update(); } } + if (!this.isRendered && n > 0) { + // None of the tools is visible + // Note: ToolsView with no tools are always mounted + return this; + } if (!this.isMounted()) { this.mount(); } - if (!isRendered) { + if (!wasRendered) { // Make sure tools are visible (if they were hidden and the tool removed) this.blurTool(); - this.isRendered = true; } return this; }, diff --git a/packages/joint-core/src/elementTools/HoverConnect.mjs b/packages/joint-core/src/elementTools/HoverConnect.mjs index a657a2de6..86575dfca 100644 --- a/packages/joint-core/src/elementTools/HoverConnect.mjs +++ b/packages/joint-core/src/elementTools/HoverConnect.mjs @@ -1,8 +1,8 @@ -import { HoverConnect as LinkHoverConnect } from '../linkTools/HoverConnect.mjs'; +import { HoverConnect as LinkHoverConnect } from '../cellTools/HoverConnect.mjs'; import V from '../V/index.mjs'; import * as g from '../g/index.mjs'; -import { getViewBBox } from '../linkTools/helpers.mjs'; import { isCalcExpression, evalCalcExpression } from '../util/calc.mjs'; +import { getViewBBox } from '../cellTools/helpers.mjs'; export const HoverConnect = LinkHoverConnect.extend({ diff --git a/packages/joint-core/src/elementTools/index.mjs b/packages/joint-core/src/elementTools/index.mjs index 7a8092526..37d046da3 100644 --- a/packages/joint-core/src/elementTools/index.mjs +++ b/packages/joint-core/src/elementTools/index.mjs @@ -1,5 +1,6 @@ -export * from './Control.mjs'; -export { Button, Remove } from '../linkTools/Button.mjs'; -export { Connect } from '../linkTools/Connect.mjs'; -export { Boundary } from '../linkTools/Boundary.mjs'; export { HoverConnect } from './HoverConnect.mjs'; + +export { Button, Remove } from '../cellTools/Button.mjs'; +export { Connect } from '../cellTools/Connect.mjs'; +export { Boundary } from '../cellTools/Boundary.mjs'; +export { Control } from '../cellTools/Control.mjs'; diff --git a/packages/joint-core/src/linkTools/Anchor.mjs b/packages/joint-core/src/linkTools/Anchor.mjs index 6d1755ef9..8da95f158 100644 --- a/packages/joint-core/src/linkTools/Anchor.mjs +++ b/packages/joint-core/src/linkTools/Anchor.mjs @@ -1,7 +1,7 @@ import * as g from '../g/index.mjs'; import * as util from '../util/index.mjs'; import { ToolView } from '../dia/ToolView.mjs'; -import { getAnchor, snapAnchor } from './helpers.mjs'; +import { getAnchor, snapAnchor } from '../cellTools/helpers.mjs'; const Anchor = ToolView.extend({ tagName: 'g', diff --git a/packages/joint-core/src/linkTools/RotateLabel.mjs b/packages/joint-core/src/linkTools/RotateLabel.mjs new file mode 100644 index 000000000..ece5df1c0 --- /dev/null +++ b/packages/joint-core/src/linkTools/RotateLabel.mjs @@ -0,0 +1,110 @@ +import * as g from '../g/index.mjs'; +import { Control } from '../cellTools/Control.mjs'; + +export const RotateLabel = Control.extend({ + + xAxisVector: new g.Point(1, 0), + + children() { + const { + buttonColor = '#333', + iconColor = '#fff', + outlineColor = '#fff' + } = this.options; + return [{ + selector: 'handle', + tagName: 'g', + attributes: { + cursor: 'grab', + }, + children: [{ + tagName: 'circle', + attributes: { + r: 10, + fill: buttonColor, + stroke: outlineColor, + }, + }, { + tagName: 'path', + attributes: { + d: 'M -5 0 A 5 5 0 1 1 0 5', + fill: 'transparent', + stroke: iconColor, + strokeWidth: 2, + strokeLinecap: 'round', + }, + }, { + tagName: 'path', + attributes: { + d: 'M -3 5 L 0 2.5 0 7.5 Z', + fill: iconColor, + stroke: iconColor, + strokeWidth: 1, + transform: 'rotate(-5, -3, 5)', + } + }] + }]; + }, + + getPosition(view) { + const { offset = 0 } = this.options; + const { x = 0, y = 0 } = typeof offset === 'number' ? { x: 0, y: offset } : offset; + const label = this.getLabel(); + const labelPosition = this.getLabelPosition(label); + const coords = view.getLabelCoordinates(labelPosition); + let { angle = 0, args = {}} = labelPosition; + const keepGradient = args.keepGradient; + if (keepGradient) { + const tangent = view.getTangentAtRatio( + view.getClosestPointRatio(coords) + ); + if (tangent) { + // link slope angle + angle += tangent.vector().vectorAngle(this.xAxisVector) || 0; + } + } + const matrix = new DOMMatrix() + .translate(coords.x, coords.y) + .rotate(angle) + .translate(x, y); + return new g.Point(matrix.e, matrix.f); + }, + + // Override the default `computeVisibility` method to hide the tool if the label is not present. + computeVisibility() { + const visibility = Control.prototype.computeVisibility.apply(this, arguments); + return visibility && !!this.getLabel(); + }, + + setPosition(view, coordinates) { + const model = view.model; + const label = this.getLabel(); + if (!label) return; + const labelPosition = this.getLabelPosition(label); + const position = view.getLabelCoordinates(labelPosition); + const angle = 90 - position.theta(coordinates); + const index = this.getLabelIndex(); + model.prop(['labels', index, 'position', 'angle'], angle); + }, + + resetPosition(view) { + const model = view.model; + const index = this.getLabelIndex(); + model.prop(['labels', index, 'position', 'angle'], 0); + }, + + getLabelIndex() { + return this.options.labelIndex || 0; + }, + + getLabel() { + return this.relatedView.model.label(this.getLabelIndex()) || null; + }, + + getLabelPosition(label) { + const view = this.relatedView; + const labelPosition = view._normalizeLabelPosition(label.position); + return view._mergeLabelPositionProperty(labelPosition, view._getDefaultLabelPositionProperty()); + }, + +}); diff --git a/packages/joint-core/src/linkTools/Segments.mjs b/packages/joint-core/src/linkTools/Segments.mjs index 77a972409..b7f4404aa 100644 --- a/packages/joint-core/src/linkTools/Segments.mjs +++ b/packages/joint-core/src/linkTools/Segments.mjs @@ -3,7 +3,7 @@ import V from '../V/index.mjs'; import * as util from '../util/index.mjs'; import * as mvc from '../mvc/index.mjs'; import { ToolView } from '../dia/ToolView.mjs'; -import { getAnchor } from './helpers.mjs'; +import { getAnchor } from '../cellTools/helpers.mjs'; var SegmentHandle = mvc.View.extend({ tagName: 'g', diff --git a/packages/joint-core/src/linkTools/index.mjs b/packages/joint-core/src/linkTools/index.mjs index 5bc03fb58..dd02cef99 100644 --- a/packages/joint-core/src/linkTools/index.mjs +++ b/packages/joint-core/src/linkTools/index.mjs @@ -1,8 +1,11 @@ export * from './Vertices.mjs'; export * from './Segments.mjs'; export * from './Arrowhead.mjs'; -export * from './Boundary.mjs'; export * from './Anchor.mjs'; -export * from './Button.mjs'; -export * from './Connect.mjs'; -export * from './HoverConnect.mjs'; +export * from './RotateLabel.mjs'; + +export * from '../cellTools/Button.mjs'; +export * from '../cellTools/Boundary.mjs'; +export * from '../cellTools/Connect.mjs'; +export * from '../cellTools/HoverConnect.mjs'; +export * from '../cellTools/Control.mjs'; diff --git a/packages/joint-core/src/mvc/View.mjs b/packages/joint-core/src/mvc/View.mjs index 02ce8dcc9..25adc7dbc 100644 --- a/packages/joint-core/src/mvc/View.mjs +++ b/packages/joint-core/src/mvc/View.mjs @@ -1,5 +1,4 @@ import $ from './Dom/index.mjs'; - import * as util from '../util/index.mjs'; import V from '../V/index.mjs'; import { ViewBase } from './ViewBase.mjs'; diff --git a/packages/joint-core/test/jointjs/dia/linkTools.js b/packages/joint-core/test/jointjs/dia/linkTools.js index 5301baba9..495469cc1 100644 --- a/packages/joint-core/test/jointjs/dia/linkTools.js +++ b/packages/joint-core/test/jointjs/dia/linkTools.js @@ -61,6 +61,23 @@ QUnit.module('linkTools', function(hooks) { assert.ok(toolsView.el.parentNode); assert.ok(toolsView.isMounted()); }); + + QUnit.test('are not mounted when none of the tools is visible', function(assert) { + let isVisible = false; + const button1 = new joint.linkTools.Button({ visibility: () => isVisible }); + const button2 = new joint.linkTools.Button({ visibility: () => false }); + const toolsView = new joint.dia.ToolsView({ tools: [button1, button2] }); + linkView.addTools(toolsView); + assert.notOk(toolsView.el.parentNode); + assert.notOk(toolsView.isMounted()); + toolsView.update(); + assert.notOk(toolsView.el.parentNode); + assert.notOk(toolsView.isMounted()); + isVisible = true; + toolsView.update(); + assert.ok(toolsView.el.parentNode); + assert.ok(toolsView.isMounted()); + }); }); QUnit.module('Visibility', function() { @@ -181,6 +198,27 @@ QUnit.module('linkTools', function(hooks) { }); }); + QUnit.module('RotateLabel', function() { + + QUnit.test('postponed rendering', function(assert) { + const button = new joint.linkTools.RotateLabel({ labelIndex: 0 }); + const toolsView = new joint.dia.ToolsView({ + tools: [button] + }); + linkView.addTools(toolsView); + // The button should not be visible because there is no label yet. + assert.notOk(link.label(0)); + assert.equal(button.el.style.display, 'none'); + assert.notOk(toolsView.isRendered); + // The button should be rendered after the label is added. + link.appendLabel({}); + assert.ok(link.label(0)); + assert.notEqual(button.el.style.display, 'none'); + assert.ok(toolsView.isRendered); + }); + + }); + QUnit.module('TargetAnchor', function() { [{ resetAnchor: true, diff --git a/packages/joint-core/types/joint.d.ts b/packages/joint-core/types/joint.d.ts index 418ac48c9..64acef7f1 100644 --- a/packages/joint-core/types/joint.d.ts +++ b/packages/joint-core/types/joint.d.ts @@ -4500,4 +4500,52 @@ export namespace linkTools { protected onMouseleave(evt: dia.Event): void; } + + namespace Control { + interface Options extends dia.ToolView.Options { + selector?: string | null; + padding?: number; + handleAttributes?: Partial; + scale?: number; + } + } + + abstract class Control = Control.Options> extends dia.ToolView { + options: T; + constructor(opt?: T); + + protected getPosition(view: dia.LinkView): dia.Point; + protected setPosition(view: dia.LinkView, coordinates: g.Point): void; + protected resetPosition(view: dia.LinkView): void; + + protected updateHandle(handleNode: SVGElement): void; + protected updateExtras(extrasNode: SVGElement): void; + protected toggleExtras(visible: boolean): void; + + protected onPointerDown(evt: dia.Event): void; + protected onPointerMove(evt: dia.Event): void; + protected onPointerUp(evt: dia.Event): void; + protected onPointerDblClick(evt: dia.Event): void; + } + + namespace RotateLabel { + + interface Options extends Control.Options { + offset?: number | dia.Point; + buttonColor?: string; + iconColor?: string; + outlineColor?: string; + } + } + + class RotateLabel extends Control { + + constructor(opt?: RotateLabel.Options); + + protected getLabelPosition(label: dia.Link.Label): dia.Link.LabelPosition; + + protected getLabelIndex(): number; + + protected getLabel(): dia.Link.Label | null; + } } diff --git a/yarn.lock b/yarn.lock index 2b751401c..03d3be685 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2576,6 +2576,21 @@ __metadata: languageName: unknown linkType: soft +"@joint/demo-link-labels-ts@workspace:examples/link-labels-ts": + version: 0.0.0-use.local + resolution: "@joint/demo-link-labels-ts@workspace:examples/link-labels-ts" + dependencies: + "@joint/core": "workspace:^" + css-loader: 3.5.3 + style-loader: 1.2.1 + ts-loader: ^9.2.5 + typescript: ^4.4.3 + webpack: ^5.61.0 + webpack-cli: ^4.8.0 + webpack-dev-server: ^4.2.1 + languageName: unknown + linkType: soft + "@joint/demo-list@workspace:examples/list": version: 0.0.0-use.local resolution: "@joint/demo-list@workspace:examples/list"