@rasamjs/connectors 0.0.0-stage → 0.1.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 +114 -0
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/RELEASING.md +25 -0
- package/dist/connectors.d.ts +26 -0
- package/dist/connectors.d.ts.map +1 -0
- package/dist/connectors.js +231 -0
- package/dist/connectors.js.map +1 -0
- package/dist/editor.d.ts +42 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +55 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata.d.ts +8 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +47 -0
- package/dist/metadata.js.map +1 -0
- package/dist/routing.d.ts +11 -0
- package/dist/routing.d.ts.map +1 -0
- package/dist/routing.js +110 -0
- package/dist/routing.js.map +1 -0
- package/dist/tool-surface.d.ts +21 -0
- package/dist/tool-surface.d.ts.map +1 -0
- package/dist/tool-surface.js +98 -0
- package/dist/tool-surface.js.map +1 -0
- package/dist/tool.d.ts +44 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +305 -0
- package/dist/tool.js.map +1 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -4
- package/src/connectors.ts +248 -0
- package/src/editor.ts +82 -0
- package/src/index.ts +14 -0
- package/src/metadata.ts +63 -0
- package/src/routing.ts +127 -0
- package/src/tool-surface.ts +96 -0
- package/src/tool.ts +344 -0
- package/src/types.ts +33 -0
package/API.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Connectors API
|
|
2
|
+
|
|
3
|
+
The root exports `Connectors`, `isConnector`, `connectorPort`, `routeConnector` and
|
|
4
|
+
connector option/state/geometry types. Optional `./tool` exports `ConnectorTool` and
|
|
5
|
+
`ConnectorToolOptions`. Only core is imported at runtime. Imports are document-free.
|
|
6
|
+
|
|
7
|
+
## Model and history
|
|
8
|
+
|
|
9
|
+
`new Connectors(drawing)` attaches one service to an owning SVG root. Existing saved
|
|
10
|
+
connectors are validated without rewriting their stored geometry. No history recorder
|
|
11
|
+
is created; supply your own if needed.
|
|
12
|
+
|
|
13
|
+
`create(options)` returns an ordinary `Path` in one labelled core change group:
|
|
14
|
+
|
|
15
|
+
- `start`, `end`: `{ target, anchor? }` or `{ point: { x, y } }`. Free points use drawing
|
|
16
|
+
units. Targets are transformable shapes/groups/viewports in the same drawing,
|
|
17
|
+
excluding the root, connectors and groups containing connectors.
|
|
18
|
+
- `anchor`: `auto` (default), `top`, `right`, `bottom`, `left`, or `{ x, y }` normalized
|
|
19
|
+
between 0 and 1 within the target's local geometric bounds. `{ x: 1, y: 0.5 }` is
|
|
20
|
+
right-center and follows resizing. Sides rotate with the target. Auto intersects the
|
|
21
|
+
ray toward the other endpoint with the local bounds rectangle.
|
|
22
|
+
- `routing`: `straight` (default) or `elbow`. Elbows use orthogonal drawing-space
|
|
23
|
+
segments with 16-unit outward leads. They do not avoid obstacles; close/opposing
|
|
24
|
+
anchors can produce backtracking. Curved routing and connector labels are deferred.
|
|
25
|
+
- `arrows`: `end` (default), `both`, or `none`. SVG markers use `auto-start-reverse`
|
|
26
|
+
orientation and `context-stroke` fill, following the connector's stroke.
|
|
27
|
+
- `stroke`: nonempty SVG paint, default `#334155`; `strokeWidth`: finite nonnegative
|
|
28
|
+
number, default 2. Edit subsequent appearance through ordinary path APIs.
|
|
29
|
+
|
|
30
|
+
`nodes` is a frozen snapshot of current paths in document order. `read(path)` returns
|
|
31
|
+
`{ path, start, end, routing }`. `update(path, { start?, end?, routing? })` changes a
|
|
32
|
+
connection in one labelled edit. `remove(path)` detaches it reversibly in one edit;
|
|
33
|
+
its marker resources remain available. Undoing creation also removes newly created
|
|
34
|
+
endpoint IDs and markers. Undo/redo retain path and endpoint identities.
|
|
35
|
+
|
|
36
|
+
Routes update synchronously through core `deriveChanges()`, before the mover's final
|
|
37
|
+
change snapshot. Shape/parent moves, resizing, rotation and reparenting join the same
|
|
38
|
+
operation/group, including ordinary setters, transformer gestures and transactions.
|
|
39
|
+
Getter-only operations do not route. Replay and rollback restore saved path data
|
|
40
|
+
without recomputing it. Camera changes with source `viewport` do not route.
|
|
41
|
+
|
|
42
|
+
Geometry uses fill bounds, excluding strokes and clipping. Curved/irregular targets
|
|
43
|
+
use their bounds rectangle for automatic anchors. Empty/unavailable geometry or an
|
|
44
|
+
unmappable automatic anchor/path rejects creation/update. Existing paths retain their last geometry
|
|
45
|
+
while targets are detached or unmappable; undo and later changes restore tracking.
|
|
46
|
+
A connector cannot be placed inside its own endpoint target, which would make routing
|
|
47
|
+
cyclic. Failed connector commands cancel partial edits. Caller setters follow core's
|
|
48
|
+
failure semantics; use a history transaction for rollback when dependent writes fail.
|
|
49
|
+
The service discovers imported/cloned paths at operation boundaries without registration.
|
|
50
|
+
|
|
51
|
+
`isDestroyed` reports disposal. `destroy()` is idempotent and stops derivation without
|
|
52
|
+
removing document paths. `onDispose(callback)` returns an unsubscribe function.
|
|
53
|
+
Drawing disposal destroys the service and its tools. Recreate for a replacement drawing.
|
|
54
|
+
|
|
55
|
+
## Persistence
|
|
56
|
+
|
|
57
|
+
Versioned routing, anchors and free points live in `data-rasam-connector`. Attached
|
|
58
|
+
targets use typed href fragments in the reserved `data-rasam-connector-start` and
|
|
59
|
+
`data-rasam-connector-end` attributes. Core recognizes them during clone, export/import
|
|
60
|
+
and ID remapping. Markers use normal typed URL references. No custom catalog is needed.
|
|
61
|
+
|
|
62
|
+
Cloning a whole group rebinds copied connectors to copied endpoints. Cloning a path
|
|
63
|
+
alone shares its external endpoints/markers by default. Exporting a connector alone
|
|
64
|
+
includes referenced shapes as dependencies in defs; a whole drawing retains visible
|
|
65
|
+
shapes and attachments. Permanently destroying a referenced target requires unbinding
|
|
66
|
+
its consumers first; editor deletion uses reversible removal. Use the matching current
|
|
67
|
+
core build while these APIs remain unreleased.
|
|
68
|
+
|
|
69
|
+
## Geometry helpers
|
|
70
|
+
|
|
71
|
+
`connectorPort(endpoint, toward)` returns `{ point, direction }` in drawing units or
|
|
72
|
+
null for unavailable geometry. Direction is the dominant orthogonal outward axis,
|
|
73
|
+
or zero for a free endpoint. `routeConnector({ start, end, routing })` returns route
|
|
74
|
+
points without editing the drawing.
|
|
75
|
+
|
|
76
|
+
## Connector tool
|
|
77
|
+
|
|
78
|
+
`new ConnectorTool(connectors, options = {})` creates a disabled overlay outside the
|
|
79
|
+
managed SVG. Set `enabled = true` to draw primary-pointer drags. Options accept the
|
|
80
|
+
same routing/arrows/stroke/strokeWidth settings, plus:
|
|
81
|
+
|
|
82
|
+
- `selectable(target)`: filter hover targets, for example with layer lock rules.
|
|
83
|
+
- `onCreate(path)`: select the committed node or switch tools after creation.
|
|
84
|
+
- `onGesture(active)`: suspend camera input during a drag; false on completion/cancel.
|
|
85
|
+
- `onError(error)`: receives pointer/callback errors; otherwise errors propagate.
|
|
86
|
+
- `onExit()`: runs after Escape exits an idle enabled tool.
|
|
87
|
+
|
|
88
|
+
Hover shows four side hints, five overlay pixels in radius at every zoom. Press within
|
|
89
|
+
twelve client pixels of a side to choose it; body presses use auto anchors. Empty-space
|
|
90
|
+
presses/releases use free points. Previews never edit the document. Release creates one
|
|
91
|
+
labelled edit; coincident endpoints create nothing. The tool stays enabled so the app
|
|
92
|
+
can choose keep-tool behavior.
|
|
93
|
+
|
|
94
|
+
Escape cancels an active drag; another Escape exits the tool. Blur, pointer cancellation,
|
|
95
|
+
lost capture, disabling, endpoint removal, service destruction and drawing destruction
|
|
96
|
+
release captures/previews. Editable controls and dialogs retain their keys. `update()`
|
|
97
|
+
refreshes the overlay and returns the tool. `isDestroyed` reports disposal. `destroy()`
|
|
98
|
+
is idempotent and releases listeners, frames, observers and DOM. Disable the tool when
|
|
99
|
+
switching other application modes.
|
|
100
|
+
|
|
101
|
+
## Editor integration entry
|
|
102
|
+
|
|
103
|
+
`@rasamjs/connectors/editor` exports `connectorEditing()`, a structural extension
|
|
104
|
+
factory. Its value is the current `Connectors` service. It registers
|
|
105
|
+
`connector-straight` (C) and `connector-elbow` (Shift+C), with matching command IDs,
|
|
106
|
+
active-tool metadata, current selection/layer restrictions, paint, keep-tool and
|
|
107
|
+
camera/capture cleanup. Escape cancels a drag first and exits an idle tool;
|
|
108
|
+
document replacement, editing/dialog actions, tool changes and disposal interrupt
|
|
109
|
+
sessions. Read-only camera commands retain an idle connector mode.
|
|
110
|
+
|
|
111
|
+
`ConnectorTool.paint` reads an immutable `{ stroke, strokeWidth }` snapshot and
|
|
112
|
+
accepts a validated replacement. Updates repaint an active preview and apply to
|
|
113
|
+
the committed path. The adapter forwards editor paint changes through this API.
|
|
114
|
+
The adapter imports only connector modules and core, with no editor peer.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Add `./editor` connector integration with registered tool metadata, live paint,
|
|
6
|
+
keep-tool and scoped camera/input cleanup. Add `ConnectorTool.paint` for preview updates.
|
|
7
|
+
|
|
8
|
+
- Add attached straight/elbow paths, automatic/side/normalized-point anchors, free
|
|
9
|
+
endpoints, marker arrows and saved typed references.
|
|
10
|
+
- Derive routing inside the mover's original operation/group; undo/redo restore stored
|
|
11
|
+
geometry. Support transformed parents, resizing, reparenting and cloned connections.
|
|
12
|
+
- Add optional `./tool` with hover anchors, previews, capture, cancellation and cleanup.
|
|
13
|
+
- Include API, documentation, size/isolation and packed-consumer checks.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rasam contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @rasamjs/connectors
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The optional [editor adapter](API.md#editor-integration-entry) packages connector
|
|
4
|
+
services and registered straight/elbow tools, including live paint and input cleanup.
|
|
5
|
+
|
|
6
|
+
Attached straight and elbow lines and marker arrows for rasam SVG drawings. Core is
|
|
7
|
+
the only runtime peer. This package is unreleased and remains private.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { SVG } from '@rasamjs/core';
|
|
11
|
+
import { Connectors } from '@rasamjs/connectors';
|
|
12
|
+
import { ConnectorTool } from '@rasamjs/connectors/tool';
|
|
13
|
+
|
|
14
|
+
const drawing = SVG(host, { width: 700, height: 500 });
|
|
15
|
+
const a = drawing.rect({ x: 40, y: 40, width: 80, height: 50 });
|
|
16
|
+
const b = drawing.rect({ x: 300, y: 180, width: 80, height: 50 });
|
|
17
|
+
const connectors = new Connectors(drawing);
|
|
18
|
+
const path = connectors.create({
|
|
19
|
+
start: { target: a, anchor: 'right' },
|
|
20
|
+
end: { target: b, anchor: 'auto' },
|
|
21
|
+
routing: 'elbow',
|
|
22
|
+
});
|
|
23
|
+
const tool = new ConnectorTool(connectors, { routing: 'straight' });
|
|
24
|
+
tool.enabled = true;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Moving or resizing an endpoint, or its parent, updates the connector inside the same
|
|
28
|
+
model operation and change group. History records one undo step for a move and all
|
|
29
|
+
its attached lines. Replay restores stored geometry without routing again.
|
|
30
|
+
|
|
31
|
+
The optional tool previews on an overlay, shows side anchors on hover, and writes
|
|
32
|
+
one connector on release. Escape, pointer cancellation and blur discard the preview.
|
|
33
|
+
The playground exposes Connector (`C`) and Elbow connector (`Shift+C`) tools.
|
|
34
|
+
|
|
35
|
+
Attachments are typed references on ordinary paths, so saved SVG, clones, imports,
|
|
36
|
+
clipboard and history retain them. No custom element registration is needed.
|
|
37
|
+
Both entries can be imported without a document. See [API.md](./API.md) for anchor
|
|
38
|
+
coordinates, lifecycle, routing limits and persistence behavior.
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
bun run --cwd packages/connectors test
|
|
42
|
+
bun run --cwd packages/connectors test:browser
|
|
43
|
+
bun run --cwd packages/connectors typecheck
|
|
44
|
+
bun run --cwd packages/connectors build
|
|
45
|
+
```
|
package/RELEASING.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Releasing connectors
|
|
2
|
+
|
|
3
|
+
The package remains private and unreleased. Phase 10 does not authorize publishing,
|
|
4
|
+
pushing or changing an existing approved size cap.
|
|
5
|
+
|
|
6
|
+
Run scoped format, typecheck, unit, build, API, docs, browser, consumer and size checks.
|
|
7
|
+
Run `api:update` only after reviewing the API diff. The root's 5 KiB gzip and optional
|
|
8
|
+
tool's 4 KiB budgets keep core external and UI code isolated from the model entry.
|
|
9
|
+
|
|
10
|
+
The separately requested `release:check` uses the shared workspace gate and records
|
|
11
|
+
source hashes/readiness. Build core first: connectors need its current derivation and
|
|
12
|
+
reserved-reference APIs. The consumer check packs only core and connectors, validates
|
|
13
|
+
all three entries, declarations, maps, document-free imports and absence of dependencies.
|
|
14
|
+
|
|
15
|
+
Phase 13 adds `@rasamjs/connectors/editor`, a structural service/tool adapter at
|
|
16
|
+
5,781/8,192 B gzip. It keeps the existing core-only peer graph; editor is not a
|
|
17
|
+
runtime dependency or peer. Root/tool caps remain unchanged. The tool accepts live
|
|
18
|
+
paint updates, and editor/playground matrices cover registered shortcuts, metadata,
|
|
19
|
+
keep-tool, captures and camera cleanup. Scoped checks pass 18 unit tests and 12
|
|
20
|
+
three-engine browser cases, typecheck/build, formatting, API/docs, size/isolation
|
|
21
|
+
and the packed consumer. The full release gate, new benchmarks and native-device
|
|
22
|
+
checks were not run.
|
|
23
|
+
|
|
24
|
+
Before separately authorized publication, follow the workspace/core release procedure
|
|
25
|
+
and validate the matching core version. No npm publication is performed here.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Path } from '@rasamjs/core';
|
|
2
|
+
import type { Svg } from '@rasamjs/core';
|
|
3
|
+
import type { ConnectorOptions, ConnectorState } from './types.js';
|
|
4
|
+
export declare class Connectors {
|
|
5
|
+
readonly drawing: Svg;
|
|
6
|
+
private disposed;
|
|
7
|
+
private readonly cleanups;
|
|
8
|
+
private readonly disposals;
|
|
9
|
+
constructor(drawing: Svg);
|
|
10
|
+
get isDestroyed(): boolean;
|
|
11
|
+
onDispose(listener: () => void): () => void;
|
|
12
|
+
get nodes(): readonly Path[];
|
|
13
|
+
read(path: Path): ConnectorState;
|
|
14
|
+
create(options: ConnectorOptions): Path;
|
|
15
|
+
update(path: Path, patch: Partial<Pick<ConnectorOptions, 'start' | 'end' | 'routing'>>): void;
|
|
16
|
+
remove(path: Path): void;
|
|
17
|
+
destroy(): void;
|
|
18
|
+
private assertLive;
|
|
19
|
+
private assertPath;
|
|
20
|
+
private validate;
|
|
21
|
+
private validateEndpoint;
|
|
22
|
+
private affects;
|
|
23
|
+
private route;
|
|
24
|
+
private edit;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=connectors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectors.d.ts","sourceRoot":"","sources":["../src/connectors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,KAAK,EAAE,GAAG,EAAW,MAAM,eAAe,CAAC;AAYlD,OAAO,KAAK,EAAqB,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAkBtF,qBAAa,UAAU;IAIT,QAAQ,CAAC,OAAO,EAAE,GAAG;IAHjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,YAAqB,OAAO,EAAE,GAAG,EAuBhC;IACD,IAAI,WAAW,IAAI,OAAO,CAEzB;IACD,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAO1C;IACD,IAAI,KAAK,IAAI,SAAS,IAAI,EAAE,CAG3B;IACD,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,CAG/B;IACD,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAgCtC;IACD,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,KAAK,GAAG,SAAS,CAAC,CAAC,GAAG,IAAI,CAS5F;IACD,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAGvB;IACD,OAAO,IAAI,IAAI,CAcd;IACD,OAAO,CAAC,UAAU;IAGlB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,KAAK;IAuBb,OAAO,CAAC,IAAI;CAgBb"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Path } from '@rasamjs/core';
|
|
2
|
+
import { Guards } from '@rasamjs/core/advanced';
|
|
3
|
+
import { beginChangeGroup, deriveChanges } from '@rasamjs/core/changes';
|
|
4
|
+
import { isConnector, readConnector, writeConnector } from './metadata.js';
|
|
5
|
+
import { connectorPort, endpointCenter, pathData, routeConnector, validateAnchor, validatePoint, } from './routing.js';
|
|
6
|
+
const owners = new WeakMap();
|
|
7
|
+
const drawableTags = new Set([
|
|
8
|
+
'rect',
|
|
9
|
+
'circle',
|
|
10
|
+
'ellipse',
|
|
11
|
+
'line',
|
|
12
|
+
'polyline',
|
|
13
|
+
'polygon',
|
|
14
|
+
'path',
|
|
15
|
+
'text',
|
|
16
|
+
'image',
|
|
17
|
+
'foreignObject',
|
|
18
|
+
'use',
|
|
19
|
+
'g',
|
|
20
|
+
'svg',
|
|
21
|
+
]);
|
|
22
|
+
export class Connectors {
|
|
23
|
+
drawing;
|
|
24
|
+
disposed = false;
|
|
25
|
+
cleanups = [];
|
|
26
|
+
disposals = new Set();
|
|
27
|
+
constructor(drawing) {
|
|
28
|
+
this.drawing = drawing;
|
|
29
|
+
if (!Guards.isTransformable(drawing) ||
|
|
30
|
+
drawing.isDestroyed ||
|
|
31
|
+
drawing !== drawing.context.root ||
|
|
32
|
+
drawing.el.localName !== 'svg')
|
|
33
|
+
throw new TypeError('Connectors needs a live SVG drawing root');
|
|
34
|
+
if (owners.has(drawing))
|
|
35
|
+
throw new Error('Connectors is already attached to this drawing');
|
|
36
|
+
// Validate saved attachments without changing stored routing during attachment or replay.
|
|
37
|
+
this.nodes.forEach(readConnector);
|
|
38
|
+
owners.set(drawing, this);
|
|
39
|
+
this.cleanups.push(deriveChanges(drawing, (event) => {
|
|
40
|
+
if (event.source === 'viewport')
|
|
41
|
+
return;
|
|
42
|
+
for (const path of this.nodes) {
|
|
43
|
+
const state = readConnector(path);
|
|
44
|
+
if (event.nodes.some((changed) => changed === path || this.affects(changed, state)))
|
|
45
|
+
this.route(state);
|
|
46
|
+
}
|
|
47
|
+
}), drawing.onDispose(() => this.destroy()));
|
|
48
|
+
}
|
|
49
|
+
get isDestroyed() {
|
|
50
|
+
return this.disposed;
|
|
51
|
+
}
|
|
52
|
+
onDispose(listener) {
|
|
53
|
+
this.assertLive();
|
|
54
|
+
if (typeof listener !== 'function')
|
|
55
|
+
throw new TypeError('Disposal listener must be a function');
|
|
56
|
+
this.disposals.add(listener);
|
|
57
|
+
return () => {
|
|
58
|
+
this.disposals.delete(listener);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
get nodes() {
|
|
62
|
+
this.assertLive();
|
|
63
|
+
return Object.freeze(this.drawing.descendants().filter(isConnector));
|
|
64
|
+
}
|
|
65
|
+
read(path) {
|
|
66
|
+
this.assertPath(path);
|
|
67
|
+
return readConnector(path);
|
|
68
|
+
}
|
|
69
|
+
create(options) {
|
|
70
|
+
this.assertLive();
|
|
71
|
+
this.validate(options);
|
|
72
|
+
return this.edit('Create connector', () => {
|
|
73
|
+
const path = this.drawing.path({
|
|
74
|
+
d: '',
|
|
75
|
+
fill: 'none',
|
|
76
|
+
stroke: options.stroke ?? '#334155',
|
|
77
|
+
'stroke-width': options.strokeWidth ?? 2,
|
|
78
|
+
});
|
|
79
|
+
writeConnector(path, {
|
|
80
|
+
start: options.start,
|
|
81
|
+
end: options.end,
|
|
82
|
+
routing: options.routing ?? 'straight',
|
|
83
|
+
});
|
|
84
|
+
if ((options.arrows ?? 'end') !== 'none') {
|
|
85
|
+
const marker = this.drawing.createMarker({
|
|
86
|
+
viewBox: '0 0 10 10',
|
|
87
|
+
markerWidth: 6,
|
|
88
|
+
markerHeight: 6,
|
|
89
|
+
refX: 9,
|
|
90
|
+
refY: 5,
|
|
91
|
+
orient: 'auto-start-reverse',
|
|
92
|
+
});
|
|
93
|
+
marker.path({ d: 'M0 0 L10 5 L0 10 Z', fill: 'context-stroke', stroke: 'none' });
|
|
94
|
+
path.marker('end', marker);
|
|
95
|
+
if (options.arrows === 'both')
|
|
96
|
+
path.marker('start', marker);
|
|
97
|
+
}
|
|
98
|
+
if (!this.route(readConnector(path)))
|
|
99
|
+
throw new Error('Connector needs available bounds and invertible transforms');
|
|
100
|
+
return path;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
update(path, patch) {
|
|
104
|
+
this.assertPath(path);
|
|
105
|
+
const state = { ...readConnector(path), ...patch };
|
|
106
|
+
this.validate(state);
|
|
107
|
+
this.edit('Edit connector', () => {
|
|
108
|
+
writeConnector(path, state);
|
|
109
|
+
if (!this.route(state))
|
|
110
|
+
throw new Error('Connector needs available bounds and invertible transforms');
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
remove(path) {
|
|
114
|
+
this.assertPath(path);
|
|
115
|
+
this.edit('Delete connector', () => path.remove());
|
|
116
|
+
}
|
|
117
|
+
destroy() {
|
|
118
|
+
if (this.disposed)
|
|
119
|
+
return;
|
|
120
|
+
this.disposed = true;
|
|
121
|
+
owners.delete(this.drawing);
|
|
122
|
+
const failures = [];
|
|
123
|
+
for (const stop of [...this.cleanups.splice(0).reverse(), ...this.disposals]) {
|
|
124
|
+
try {
|
|
125
|
+
stop();
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
failures.push(error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
this.disposals.clear();
|
|
132
|
+
if (failures.length)
|
|
133
|
+
throw new AggregateError(failures, 'Connector cleanup failed');
|
|
134
|
+
}
|
|
135
|
+
assertLive() {
|
|
136
|
+
if (this.disposed)
|
|
137
|
+
throw new Error('Connectors is destroyed');
|
|
138
|
+
}
|
|
139
|
+
assertPath(path) {
|
|
140
|
+
this.assertLive();
|
|
141
|
+
if (!isConnector(path) ||
|
|
142
|
+
path.isDestroyed ||
|
|
143
|
+
path.context !== this.drawing.context ||
|
|
144
|
+
!this.drawing.descendants().includes(path))
|
|
145
|
+
throw new TypeError('Expected a connector in this drawing');
|
|
146
|
+
}
|
|
147
|
+
validate(options) {
|
|
148
|
+
if (!options || typeof options !== 'object')
|
|
149
|
+
throw new TypeError('Connector options must be an object');
|
|
150
|
+
for (const endpoint of [options.start, options.end])
|
|
151
|
+
this.validateEndpoint(endpoint);
|
|
152
|
+
if (options.routing !== undefined && !['straight', 'elbow'].includes(options.routing))
|
|
153
|
+
throw new TypeError('Unknown connector routing');
|
|
154
|
+
if (options.arrows !== undefined && !['none', 'end', 'both'].includes(options.arrows))
|
|
155
|
+
throw new TypeError('Unknown connector arrows');
|
|
156
|
+
if (options.stroke !== undefined &&
|
|
157
|
+
(typeof options.stroke !== 'string' || !options.stroke.trim()))
|
|
158
|
+
throw new TypeError('Connector stroke must be nonempty');
|
|
159
|
+
if (options.strokeWidth !== undefined &&
|
|
160
|
+
(!Number.isFinite(options.strokeWidth) || options.strokeWidth < 0))
|
|
161
|
+
throw new TypeError('Connector stroke width must be nonnegative');
|
|
162
|
+
}
|
|
163
|
+
validateEndpoint(endpoint) {
|
|
164
|
+
if (!endpoint || typeof endpoint !== 'object' || 'target' in endpoint === 'point' in endpoint)
|
|
165
|
+
throw new TypeError('Endpoint needs either a target or a point');
|
|
166
|
+
if ('point' in endpoint) {
|
|
167
|
+
validatePoint(endpoint.point);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const target = endpoint.target;
|
|
171
|
+
if (!Guards.isTransformable(target) ||
|
|
172
|
+
target.isDestroyed ||
|
|
173
|
+
target === this.drawing ||
|
|
174
|
+
target.context !== this.drawing.context ||
|
|
175
|
+
!this.drawing.descendants().includes(target) ||
|
|
176
|
+
target.descendants().some(isConnector))
|
|
177
|
+
throw new TypeError('Endpoint target must be a drawable shape without connectors in this drawing');
|
|
178
|
+
for (let node = target; node && node !== this.drawing; node = node.parent)
|
|
179
|
+
if (!drawableTags.has(node.el.localName))
|
|
180
|
+
throw new TypeError('Connector targets cannot be inside definitions');
|
|
181
|
+
validateAnchor(endpoint.anchor ?? 'auto');
|
|
182
|
+
}
|
|
183
|
+
affects(changed, { path, start, end }) {
|
|
184
|
+
// Parents can move, resize, reparent or change a group's local bounds through a descendant.
|
|
185
|
+
return [
|
|
186
|
+
path,
|
|
187
|
+
...[start, end].flatMap((endpoint) => ('target' in endpoint ? [endpoint.target] : [])),
|
|
188
|
+
].some((node) => node === changed ||
|
|
189
|
+
node.descendants().includes(changed) ||
|
|
190
|
+
changed.descendants().includes(node));
|
|
191
|
+
}
|
|
192
|
+
route(state) {
|
|
193
|
+
const { path, start, end } = state;
|
|
194
|
+
for (const endpoint of [start, end])
|
|
195
|
+
if ('target' in endpoint) {
|
|
196
|
+
if (endpoint.target.isDestroyed || !this.drawing.descendants().includes(endpoint.target))
|
|
197
|
+
return false;
|
|
198
|
+
if (endpoint.target.descendants().includes(path))
|
|
199
|
+
throw new TypeError('A connector cannot be inside its endpoint target');
|
|
200
|
+
}
|
|
201
|
+
const a = endpointCenter(start), b = endpointCenter(end);
|
|
202
|
+
const from = a && b ? connectorPort(start, b) : null;
|
|
203
|
+
const to = a && b ? connectorPort(end, a) : null;
|
|
204
|
+
const inverse = path.worldMatrix().inverse();
|
|
205
|
+
if (!from || !to || !inverse)
|
|
206
|
+
return false;
|
|
207
|
+
const d = pathData(routeConnector({ start: from, end: to, routing: state.routing }).map((point) => inverse.apply(point)));
|
|
208
|
+
if (path.getAttributeValue('d') !== d)
|
|
209
|
+
path.attrs({ d });
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
edit(label, run) {
|
|
213
|
+
const group = beginChangeGroup(this.drawing, { source: 'connectors', label });
|
|
214
|
+
let result;
|
|
215
|
+
try {
|
|
216
|
+
result = group.run(run);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
try {
|
|
220
|
+
group.cancel();
|
|
221
|
+
}
|
|
222
|
+
catch (cleanup) {
|
|
223
|
+
throw new AggregateError([error, cleanup], 'Connector edit and rollback failed');
|
|
224
|
+
}
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
group.end();
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=connectors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectors.js","sourceRoot":"","sources":["../src/connectors.ts"],"names":[],"mappings":"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"}
|
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AnyNode, Svg } from '@rasamjs/core';
|
|
2
|
+
import { Connectors } from './connectors.js';
|
|
3
|
+
export interface ConnectorScope {
|
|
4
|
+
readonly drawing: Svg;
|
|
5
|
+
readonly editor: {
|
|
6
|
+
readonly paint: {
|
|
7
|
+
readonly stroke: string;
|
|
8
|
+
readonly strokeWidth: number;
|
|
9
|
+
};
|
|
10
|
+
readonly selection: {
|
|
11
|
+
canSelect(node: AnyNode): boolean;
|
|
12
|
+
};
|
|
13
|
+
readonly layers: {
|
|
14
|
+
isSelectable(node: AnyNode): boolean;
|
|
15
|
+
};
|
|
16
|
+
fail(action: string, error: unknown): void;
|
|
17
|
+
};
|
|
18
|
+
use<Value extends {
|
|
19
|
+
destroy(): void;
|
|
20
|
+
}>(value: Value): Value;
|
|
21
|
+
registerTool(definition: {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
key: string;
|
|
25
|
+
hint: string;
|
|
26
|
+
commandId: string;
|
|
27
|
+
activate(scope: ConnectorScope): {
|
|
28
|
+
destroy(): void;
|
|
29
|
+
key?(event: KeyboardEvent): boolean;
|
|
30
|
+
};
|
|
31
|
+
}): () => void;
|
|
32
|
+
cleanup(callback: () => void): void;
|
|
33
|
+
on(type: 'camera' | 'paint', callback: () => void): void;
|
|
34
|
+
gesture(): () => void;
|
|
35
|
+
created(node: AnyNode): void;
|
|
36
|
+
exit(): void;
|
|
37
|
+
}
|
|
38
|
+
export declare function connectorEditing(): {
|
|
39
|
+
id: string;
|
|
40
|
+
create(scope: ConnectorScope): Connectors;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,CAAC,KAAK,EAAE;YAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1E,QAAQ,CAAC,SAAS,EAAE;YAAE,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;SAAE,CAAC;QAC1D,QAAQ,CAAC,MAAM,EAAE;YAAE,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;SAAE,CAAC;QAC1D,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;KAC5C,CAAC;IACF,GAAG,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,IAAI,CAAA;KAAE,EAAE,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC;IAC5D,YAAY,CAAC,UAAU,EAAE;QACvB,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG;YAAE,OAAO,IAAI,IAAI,CAAC;YAAC,GAAG,CAAC,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAA;SAAE,CAAC;KAC3F,GAAG,MAAM,IAAI,CAAC;IACf,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACpC,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACzD,OAAO,IAAI,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,IAAI,IAAI,IAAI,CAAC;CACd;AACD,wBAAgB,gBAAgB;IAE5B,EAAE;IACF,MAAM,QAAQ,cAAc;EAmD/B"}
|
package/dist/editor.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Connectors } from './connectors.js';
|
|
2
|
+
import { ConnectorTool } from './tool.js';
|
|
3
|
+
export function connectorEditing() {
|
|
4
|
+
return {
|
|
5
|
+
id: 'connectors',
|
|
6
|
+
create(scope) {
|
|
7
|
+
const service = scope.use(new Connectors(scope.drawing));
|
|
8
|
+
for (const routing of ['straight', 'elbow'])
|
|
9
|
+
scope.registerTool({
|
|
10
|
+
id: `connector-${routing}`,
|
|
11
|
+
commandId: `connector-${routing}`,
|
|
12
|
+
label: routing === 'straight' ? 'Connector' : 'Elbow connector',
|
|
13
|
+
key: routing === 'straight' ? 'C' : 'Shift+C',
|
|
14
|
+
hint: 'Drag between shapes to connect; hover to choose an anchor; Escape cancels.',
|
|
15
|
+
activate(current) {
|
|
16
|
+
const editor = current.editor;
|
|
17
|
+
let release = () => { }, dragging = false;
|
|
18
|
+
current.cleanup(() => release());
|
|
19
|
+
const tool = current.use(new ConnectorTool(service, {
|
|
20
|
+
routing,
|
|
21
|
+
...editor.paint,
|
|
22
|
+
selectable: (node) => editor.selection.canSelect(node) && editor.layers.isSelectable(node),
|
|
23
|
+
onGesture(active) {
|
|
24
|
+
dragging = active;
|
|
25
|
+
release();
|
|
26
|
+
release = active ? current.gesture() : () => { };
|
|
27
|
+
},
|
|
28
|
+
onCreate: (path) => current.created(path),
|
|
29
|
+
onError: (error) => {
|
|
30
|
+
editor.fail('Draw connector', error);
|
|
31
|
+
current.exit();
|
|
32
|
+
},
|
|
33
|
+
onExit: () => current.exit(),
|
|
34
|
+
}));
|
|
35
|
+
current.on('camera', () => tool.update());
|
|
36
|
+
current.on('paint', () => {
|
|
37
|
+
tool.paint = editor.paint;
|
|
38
|
+
});
|
|
39
|
+
tool.enabled = true;
|
|
40
|
+
return {
|
|
41
|
+
destroy() { },
|
|
42
|
+
key(event) {
|
|
43
|
+
if (event.type !== 'keydown' || event.key !== 'Escape' || !dragging)
|
|
44
|
+
return false;
|
|
45
|
+
tool.cancel();
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
return service;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAyB1C,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,EAAE,EAAE,YAAY;QAChB,MAAM,CAAC,KAAqB;YAC1B,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YACzD,KAAK,MAAM,OAAO,IAAI,CAAC,UAAU,EAAE,OAAO,CAAU;gBAClD,KAAK,CAAC,YAAY,CAAC;oBACjB,EAAE,EAAE,aAAa,OAAO,EAAE;oBAC1B,SAAS,EAAE,aAAa,OAAO,EAAE;oBACjC,KAAK,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,iBAAiB;oBAC/D,GAAG,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;oBAC7C,IAAI,EAAE,4EAA4E;oBAClF,QAAQ,CAAC,OAAO;wBACd,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;wBAC9B,IAAI,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,EACpB,QAAQ,GAAG,KAAK,CAAC;wBACnB,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;wBACjC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CACtB,IAAI,aAAa,CAAC,OAAO,EAAE;4BACzB,OAAO;4BACP,GAAG,MAAM,CAAC,KAAK;4BACf,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CACnB,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC;4BACtE,SAAS,CAAC,MAAM;gCACd,QAAQ,GAAG,MAAM,CAAC;gCAClB,OAAO,EAAE,CAAC;gCACV,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;4BAClD,CAAC;4BACD,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;4BACzC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gCACjB,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;gCACrC,OAAO,CAAC,IAAI,EAAE,CAAC;4BACjB,CAAC;4BACD,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE;yBAC7B,CAAC,CACH,CAAC;wBACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;wBAC1C,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;4BACvB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;wBAC5B,CAAC,CAAC,CAAC;wBACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;wBACpB,OAAO;4BACL,OAAO,KAAI,CAAC;4BACZ,GAAG,CAAC,KAAoB;gCACtB,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,QAAQ;oCAAE,OAAO,KAAK,CAAC;gCAClF,IAAI,CAAC,MAAM,EAAE,CAAC;gCACd,OAAO,IAAI,CAAC;4BACd,CAAC;yBACF,CAAC;oBACJ,CAAC;iBACF,CAAC,CAAC;YACL,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Connectors } from './connectors.js';
|
|
2
|
+
export { isConnector } from './metadata.js';
|
|
3
|
+
export { connectorPort, routeConnector } from './routing.js';
|
|
4
|
+
export type { ConnectorTarget, ConnectorAnchor, ConnectorSide, ConnectorEndpoint, ConnectorRouting, ConnectorOptions, ConnectorState, ConnectorPort, ConnectorRoute, } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7D,YAAY,EACV,eAAe,EACf,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,cAAc,GACf,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Path } from '@rasamjs/core';
|
|
2
|
+
import type { AnyNode } from '@rasamjs/core';
|
|
3
|
+
import type { ConnectorState } from './types.js';
|
|
4
|
+
export declare const endpointAttribute: (end: 'start' | 'end') => string;
|
|
5
|
+
export declare function isConnector(node: AnyNode): node is Path;
|
|
6
|
+
export declare function readConnector(path: Path): ConnectorState;
|
|
7
|
+
export declare function writeConnector(path: Path, state: Omit<ConnectorState, 'path'>): void;
|
|
8
|
+
//# sourceMappingURL=metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAIV,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,iBAAiB,QAAS,OAAO,GAAG,KAAK,WAAkC,CAAC;AAYzF,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,IAAI,CAEvD;AACD,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,CAsBxD;AACD,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG,IAAI,CAcpF"}
|