@rasamjs/connectors 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +72 -21
- package/CHANGELOG.md +36 -0
- package/README.md +14 -1
- package/RELEASING.md +12 -2
- package/dist/connectors.d.ts +17 -3
- package/dist/connectors.d.ts.map +1 -1
- package/dist/connectors.js +106 -27
- package/dist/connectors.js.map +1 -1
- package/dist/editor.d.ts +15 -1
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +20 -8
- package/dist/editor.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/metadata.d.ts +6 -2
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +28 -3
- package/dist/metadata.js.map +1 -1
- package/dist/routing.d.ts +5 -2
- package/dist/routing.d.ts.map +1 -1
- package/dist/routing.js +28 -8
- package/dist/routing.js.map +1 -1
- package/dist/tool.d.ts +4 -1
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js +37 -16
- package/dist/tool.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +64 -63
- package/src/connectors.ts +132 -36
- package/src/editor.ts +31 -8
- package/src/index.ts +2 -0
- package/src/metadata.ts +30 -5
- package/src/routing.ts +35 -9
- package/src/tool.ts +47 -24
- package/src/types.ts +10 -0
package/src/tool.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { Path, Point } from '@rasamjs/core';
|
|
1
|
+
import type { AnyNode, Path, Point } from '@rasamjs/core';
|
|
2
2
|
import { Guards, Registry } from '@rasamjs/core/advanced';
|
|
3
3
|
import { observeChanges } from '@rasamjs/core/changes';
|
|
4
4
|
import type { Connectors } from './connectors.js';
|
|
5
5
|
import { isConnector } from './metadata.js';
|
|
6
|
-
import { connectorPort, endpointCenter, pathData, routeConnector } from './routing.js';
|
|
6
|
+
import { connectorPort, endpointCenter, pathData, portsOf, routeConnector } from './routing.js';
|
|
7
7
|
import { ToolSurface, svg } from './tool-surface.js';
|
|
8
8
|
import type {
|
|
9
9
|
ConnectorEndpoint,
|
|
@@ -32,8 +32,22 @@ interface Hover {
|
|
|
32
32
|
endpoint: ConnectorEndpoint;
|
|
33
33
|
}
|
|
34
34
|
const sides: readonly ConnectorSide[] = ['top', 'right', 'bottom', 'left'];
|
|
35
|
+
/** A target's ports replace its four side hints. */
|
|
36
|
+
const hints = (target: ConnectorTarget): ConnectorEndpoint[] => {
|
|
37
|
+
const ports = portsOf(target);
|
|
38
|
+
return ports.length
|
|
39
|
+
? ports.map((port) => ({ target, port: port.name }))
|
|
40
|
+
: sides.map((anchor) => ({ target, anchor }));
|
|
41
|
+
};
|
|
42
|
+
const same = (hint: ConnectorEndpoint, endpoint: ConnectorEndpoint): boolean =>
|
|
43
|
+
'port' in hint
|
|
44
|
+
? 'port' in endpoint && endpoint.port === hint.port
|
|
45
|
+
: 'anchor' in hint && 'anchor' in endpoint && endpoint.anchor === hint.anchor;
|
|
35
46
|
|
|
36
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Drag from a hinted anchor, port or free point. Within twelve client pixels of a hint,
|
|
49
|
+
* the end snaps to it. The document is written only on release.
|
|
50
|
+
*/
|
|
37
51
|
export class ConnectorTool {
|
|
38
52
|
private readonly surface: ToolSurface;
|
|
39
53
|
private active = false;
|
|
@@ -197,18 +211,23 @@ export class ConnectorTool {
|
|
|
197
211
|
const drawing = this.connectors.drawing;
|
|
198
212
|
const point = drawing.fromClient({ x: event.clientX, y: event.clientY });
|
|
199
213
|
if (!point) return null;
|
|
200
|
-
const
|
|
201
|
-
.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
(node)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
const accepts = (node: unknown): node is ConnectorTarget =>
|
|
215
|
+
Guards.isTransformable(node) &&
|
|
216
|
+
node !== drawing &&
|
|
217
|
+
drawing.el.contains(node.el) &&
|
|
218
|
+
!isConnector(node) &&
|
|
219
|
+
!node.descendants().some(isConnector) &&
|
|
220
|
+
this.options.selectable?.(node as ConnectorTarget) !== false;
|
|
221
|
+
let target: ConnectorTarget | undefined;
|
|
222
|
+
// Hit testing never reports groups, so a rejected hit such as a component's rendered
|
|
223
|
+
// child resolves to its nearest accepted ancestor.
|
|
224
|
+
for (const element of drawing.el.ownerDocument.elementsFromPoint(event.clientX, event.clientY))
|
|
225
|
+
for (
|
|
226
|
+
let node: AnyNode | null | undefined = Registry.wrapper(element as SVGElement);
|
|
227
|
+
node && !target;
|
|
228
|
+
node = node.parent
|
|
229
|
+
)
|
|
230
|
+
if (accepts(node)) target = node;
|
|
212
231
|
// The outer half of an anchor hint can lie just outside its shape's hit area.
|
|
213
232
|
const previous = this.hover?.target;
|
|
214
233
|
if (
|
|
@@ -218,8 +237,8 @@ export class ConnectorTool {
|
|
|
218
237
|
drawing.el.contains(previous.el) &&
|
|
219
238
|
this.options.selectable?.(previous) !== false
|
|
220
239
|
) {
|
|
221
|
-
const nearHint =
|
|
222
|
-
const port = connectorPort(
|
|
240
|
+
const nearHint = hints(previous).some((hint) => {
|
|
241
|
+
const port = connectorPort(hint, point);
|
|
223
242
|
const client = port && drawing.toClient(port.point);
|
|
224
243
|
return client && Math.hypot(client.x - event.clientX, client.y - event.clientY) < 12;
|
|
225
244
|
});
|
|
@@ -229,14 +248,14 @@ export class ConnectorTool {
|
|
|
229
248
|
if (!target) return { point };
|
|
230
249
|
let endpoint: ConnectorEndpoint = { target, anchor: 'auto' };
|
|
231
250
|
let nearest = 12;
|
|
232
|
-
for (const
|
|
233
|
-
const port = connectorPort(
|
|
251
|
+
for (const hint of hints(target)) {
|
|
252
|
+
const port = connectorPort(hint, point);
|
|
234
253
|
const client = port && drawing.toClient(port.point);
|
|
235
254
|
if (!client) continue;
|
|
236
255
|
const distance = Math.hypot(client.x - event.clientX, client.y - event.clientY);
|
|
237
256
|
if (distance < nearest) {
|
|
238
257
|
nearest = distance;
|
|
239
|
-
endpoint =
|
|
258
|
+
endpoint = hint;
|
|
240
259
|
}
|
|
241
260
|
}
|
|
242
261
|
this.hover = { target, endpoint };
|
|
@@ -299,20 +318,24 @@ export class ConnectorTool {
|
|
|
299
318
|
if (!this.enabled) return;
|
|
300
319
|
const document = overlay.ownerDocument;
|
|
301
320
|
if (this.hover)
|
|
302
|
-
for (const
|
|
303
|
-
const port = connectorPort(
|
|
321
|
+
for (const hint of hints(this.hover.target)) {
|
|
322
|
+
const port = connectorPort(hint, { x: 0, y: 0 });
|
|
304
323
|
const point = port && this.surface.fromRoot(port.point);
|
|
324
|
+
const active = same(hint, this.hover.endpoint);
|
|
305
325
|
if (point)
|
|
306
326
|
overlay.append(
|
|
307
327
|
svg(document, 'circle', {
|
|
308
328
|
cx: point.x,
|
|
309
329
|
cy: point.y,
|
|
310
330
|
r: 5,
|
|
311
|
-
fill: 'white',
|
|
331
|
+
fill: active ? '#2563eb' : 'white',
|
|
312
332
|
stroke: '#2563eb',
|
|
313
333
|
'stroke-width': 1.5,
|
|
314
334
|
'pointer-events': 'none',
|
|
315
|
-
'
|
|
335
|
+
...('port' in hint
|
|
336
|
+
? { 'data-connector-port': hint.port }
|
|
337
|
+
: { 'data-connector-anchor': (hint as { anchor: ConnectorSide }).anchor }),
|
|
338
|
+
...(active ? { 'data-connector-active': '' } : {}),
|
|
316
339
|
}),
|
|
317
340
|
);
|
|
318
341
|
}
|
package/src/types.ts
CHANGED
|
@@ -4,8 +4,13 @@ export type ConnectorTarget = Shape | Group | Svg;
|
|
|
4
4
|
export type ConnectorSide = 'top' | 'right' | 'bottom' | 'left';
|
|
5
5
|
/** A point is normalized within the target's local geometric bounds. */
|
|
6
6
|
export type ConnectorAnchor = 'auto' | ConnectorSide | Point;
|
|
7
|
+
/**
|
|
8
|
+
* A target with an anchor on its bounds, a target with a named port from its
|
|
9
|
+
* `portDefinitions` (for example a component's), or a loose point in drawing units.
|
|
10
|
+
*/
|
|
7
11
|
export type ConnectorEndpoint =
|
|
8
12
|
| { readonly target: ConnectorTarget; readonly anchor?: ConnectorAnchor }
|
|
13
|
+
| { readonly target: ConnectorTarget; readonly port: string }
|
|
9
14
|
| { readonly point: Point };
|
|
10
15
|
export type ConnectorRouting = 'straight' | 'elbow';
|
|
11
16
|
export interface ConnectorOptions {
|
|
@@ -22,6 +27,11 @@ export interface ConnectorState {
|
|
|
22
27
|
readonly end: ConnectorEndpoint;
|
|
23
28
|
readonly routing: ConnectorRouting;
|
|
24
29
|
}
|
|
30
|
+
/** Return at least two finite points in drawing units, or null to retain stored geometry. */
|
|
31
|
+
export type ConnectorRouter = (state: ConnectorState) => readonly Point[] | null;
|
|
32
|
+
export interface ConnectorsOptions {
|
|
33
|
+
readonly route?: ConnectorRouter;
|
|
34
|
+
}
|
|
25
35
|
export interface ConnectorPort {
|
|
26
36
|
readonly point: Point;
|
|
27
37
|
readonly direction: Point;
|