diff --git a/demo/arrows.html b/demo/arrows.html
new file mode 100644
index 0000000..914f331
--- /dev/null
+++ b/demo/arrows.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+ Arrows
+
+
+
+
+ Hello World
+
+
+
+
+
+
+
diff --git a/demo/chains-of-thought/main.ts b/demo/chains-of-thought/main.ts
index 3924965..99fc521 100644
--- a/demo/chains-of-thought/main.ts
+++ b/demo/chains-of-thought/main.ts
@@ -33,6 +33,7 @@ class SpatialThought extends HTMLElement {
handleEvent(event: PointerEvent): void {
if (event.type === 'click' && event.target === this.#deleteButton) {
this.#geometry.remove();
+
document
.querySelectorAll(
`spatial-connection[source="spatial-geometry[id='${this.#geometry.id}']"],
diff --git a/demo/index.html b/demo/index.html
index d0b7399..7d1c55e 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -24,7 +24,7 @@
Maps
Music
Ink
- Arrow
+ Arrows
Canvasify
Spreadsheet
Chains of thought
diff --git a/src/arrows/abstract-arrow.ts b/src/arrows/abstract-arrow.ts
index 750256d..cc54bc5 100644
--- a/src/arrows/abstract-arrow.ts
+++ b/src/arrows/abstract-arrow.ts
@@ -2,6 +2,24 @@ import { VisualObserverEntry, VisualObserverManager } from './visual-observer';
const visualObserver = new VisualObserverManager();
+interface Vertex {
+ x: number;
+ y: number;
+}
+
+const vertexRegex = /(?-?([0-9]*[.])?[0-9]+),\s*(?-?([0-9]*[.])?[0-9]+)/;
+
+function parseVertex(str: string): Vertex | null {
+ const results = vertexRegex.exec(str);
+
+ if (results === null) return null;
+
+ return {
+ x: Number(results.groups?.x),
+ y: Number(results.groups?.y),
+ };
+}
+
export class AbstractArrow extends HTMLElement {
static tagName = 'abstract-arrow';
@@ -9,8 +27,6 @@ export class AbstractArrow extends HTMLElement {
customElements.define(this.tagName, this);
}
- static observedAttributes = ['source', 'target'];
-
#source = '';
/** A CSS selector for the source of the arrow. */
get source() {
@@ -18,10 +34,15 @@ export class AbstractArrow extends HTMLElement {
}
set source(source) {
- this.setAttribute('source', source);
+ this.#source = source;
+ this.observeSource();
}
#sourceRect!: DOMRectReadOnly;
+ get sourceRect() {
+ return this.#sourceRect;
+ }
+
#sourceElement: Element | null = null;
get sourceElement() {
@@ -38,14 +59,17 @@ export class AbstractArrow extends HTMLElement {
get target() {
return this.#target;
}
-
set target(target) {
- this.setAttribute('target', target);
+ this.#target = target;
+ this.observeTarget();
}
#targetRect!: DOMRectReadOnly;
- #targetElement: Element | null = null;
+ get targetRect() {
+ return this.#targetRect;
+ }
+ #targetElement: Element | null = null;
get targetElement() {
return this.#targetElement;
}
@@ -55,14 +79,9 @@ export class AbstractArrow extends HTMLElement {
this.update();
};
- attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
- if (name === 'source') {
- this.#source = newValue;
- this.observeSource();
- } else if (name === 'target') {
- this.#target = newValue;
- this.observeTarget();
- }
+ connectedCallback() {
+ this.source = this.getAttribute('source') || '';
+ this.target = this.getAttribute('target') || '';
}
disconnectedCallback() {
@@ -70,16 +89,29 @@ export class AbstractArrow extends HTMLElement {
this.unobserveTarget();
}
+ // TODO: why reparse the vertex?
+ setSourceVertex(vertex: Vertex) {
+ this.target = `${vertex.x},${vertex.y}`;
+ }
+
observeSource() {
this.unobserveSource();
- const el = document.querySelector(this.source);
- if (el === null) {
- throw new Error('source is not a valid element');
+ const vertex = parseVertex(this.#source);
+
+ if (vertex) {
+ this.#sourceRect = DOMRectReadOnly.fromRect(vertex);
+ this.update();
+ } else {
+ const el = document.querySelector(this.source);
+
+ if (el === null) {
+ throw new Error('source is not a valid element');
+ }
+
+ this.#sourceElement = el;
+ visualObserver.observe(this.#sourceElement, this.#sourceCallback);
}
-
- this.#sourceElement = el;
- visualObserver.observe(this.#sourceElement, this.#sourceCallback);
}
unobserveSource() {
@@ -90,13 +122,21 @@ export class AbstractArrow extends HTMLElement {
observeTarget() {
this.unobserveTarget();
- this.#targetElement = document.querySelector(this.target);
- if (!this.#targetElement) {
- throw new Error('target is not a valid element');
+ const vertex = parseVertex(this.#target);
+
+ if (vertex) {
+ this.#targetRect = DOMRectReadOnly.fromRect(vertex);
+ this.update();
+ } else {
+ this.#targetElement = document.querySelector(this.#target);
+
+ if (!this.#targetElement) {
+ throw new Error('target is not a valid element');
+ }
+
+ visualObserver.observe(this.#targetElement, this.#targetCallback);
}
-
- visualObserver.observe(this.#targetElement, this.#targetCallback);
}
unobserveTarget() {
@@ -106,25 +146,10 @@ export class AbstractArrow extends HTMLElement {
}
update() {
- if (
- this.#sourceRect === undefined ||
- this.#targetRect === undefined ||
- this.#sourceElement === null ||
- this.#targetElement === null
- )
- return;
+ if (this.#sourceRect === undefined || this.#targetRect === undefined) return;
- this.render(this.#sourceRect, this.#targetRect, this.#sourceElement, this.#targetElement);
+ this.render();
}
- render(
- // @ts-ignore
- sourceRect: DOMRectReadOnly,
- // @ts-ignore
- targetRect: DOMRectReadOnly,
- // @ts-ignore
- sourceElement: Element,
- // @ts-ignore
- targetElement: Element
- ) {}
+ render() {}
}
diff --git a/src/arrows/spatial-connection.ts b/src/arrows/spatial-connection.ts
index f10806a..410616e 100644
--- a/src/arrows/spatial-connection.ts
+++ b/src/arrows/spatial-connection.ts
@@ -53,8 +53,10 @@ export class SpatialConnection extends AbstractArrow {
},
};
- render(sourceRect: DOMRectReadOnly, targetRect: DOMRectReadOnly) {
- const [sx, sy, cx, cy, ex, ey, ae] = getBoxToBoxArrow(
+ render() {
+ const { sourceRect, targetRect } = this;
+
+ const [sx, sy, cx, cy, ex, ey] = getBoxToBoxArrow(
sourceRect.x,
sourceRect.y,
sourceRect.width,