diff --git a/demo/shapes.html b/demo/shapes.html
index acbb7d0..84c1c73 100644
--- a/demo/shapes.html
+++ b/demo/shapes.html
@@ -5,8 +5,6 @@
Shapes
diff --git a/src/elements/main.css b/src/elements/main.css
deleted file mode 100644
index faddafb..0000000
--- a/src/elements/main.css
+++ /dev/null
@@ -1,11 +0,0 @@
-spatial-canvas {
- display: block;
- position: relative;
-}
-
-spatial-geometry {
- display: block;
- position: absolute;
- cursor: pointer;
- content-visibility: auto;
-}
diff --git a/src/elements/spatial-geometry.ts b/src/elements/spatial-geometry.ts
index 223566f..0854480 100644
--- a/src/elements/spatial-geometry.ts
+++ b/src/elements/spatial-geometry.ts
@@ -3,15 +3,125 @@ export type Shape = 'rectangle' | 'circle' | 'triangle';
// Can we make adding new shapes extensible via a static property?
const shapes = new Set(['rectangle', 'circle', 'triangle']);
-export type Vector = { x: number; y: number; movementX: number; movementY: number };
+export type MoveEventDetail = { x: number; y: number; movementX: number; movementY: number };
// Should the move event bubble?
-export class MoveEvent extends CustomEvent {
- constructor(vector: Vector) {
+export class MoveEvent extends CustomEvent {
+ constructor(vector: MoveEventDetail) {
super('move', { detail: vector, cancelable: true, bubbles: true });
}
}
+const styles = new CSSStyleSheet();
+styles.replaceSync(`
+:host {
+ display: block;
+ position: absolute;
+ cursor: pointer;
+}
+
+:host(:not([selected])) [resize-handler] {
+ display: none;
+}
+
+:host([selected]) [resize-handler] {
+ display: block;
+ position: absolute;
+ box-sizing: border-box;
+ padding: 0;
+ background: hsl(210, 20%, 98%);
+
+ &[resize-handler="top-left"],
+ &[resize-handler="top-right"],
+ &[resize-handler="bottom-right"],
+ &[resize-handler="bottom-left"] {
+ width: 13px;
+ aspect-ratio: 1;
+ transform: translate(-50%, -50%);
+ border: 1.5px solid hsl(214, 84%, 56%);
+ border-radius: 2px;
+ z-index: 2;
+ }
+
+ &[resize-handler="top-left"] {
+ top: 0;
+ left: 0;
+ }
+
+ &[resize-handler="top-right"] {
+ top: 0;
+ left: 100%;
+ }
+
+ &[resize-handler="bottom-right"] {
+ top: 100%;
+ left: 100%;
+ }
+
+ &[resize-handler="bottom-left"] {
+ top: 100%;
+ left: 0;
+ }
+
+ &[resize-handler="top-left"], &[resize-handler="bottom-right"] {
+ cursor: url("data:image/svg+xml,") 16 16, pointer;
+ }
+
+ &[resize-handler="top-right"], &[resize-handler="bottom-left"] {
+ cursor: url("data:image/svg+xml,") 16 16, pointer;
+ }
+
+ &[resize-handler="top"],
+ &[resize-handler="right"],
+ &[resize-handler="bottom"],
+ &[resize-handler="left"] {
+ background-color: hsl(214, 84%, 56%);
+ background-clip: content-box;
+ border: unset;
+ z-index: 1;
+ }
+
+ &[resize-handler="top"] {
+ top: 0;
+ left: 0;
+ right: 0;
+ transform: translate(0, -50%);
+ }
+
+ &[resize-handler="right"] {
+ top: 0;
+ bottom: 0;
+ right: 0;
+ transform: translate(50%, 0);
+ }
+
+ &[resize-handler="bottom"] {
+ bottom:0;
+ left: 0;
+ right: 0;
+ transform: translate(0, 50%);
+ }
+
+ &[resize-handler="left"] {
+ top: 0;
+ bottom: 0;
+ left: 0;
+ transform: translate(-50%, 0);
+ }
+
+ &[resize-handler="top"], &[resize-handler="bottom"] {
+ height: 6px;
+ padding: 2px 0;
+ cursor: url("data:image/svg+xml,") 16 16, pointer;
+ }
+
+ &[resize-handler="right"], &[resize-handler="left"] {
+ width: 6px;
+ padding: 0 2px;
+ cursor: url("data:image/svg+xml,") 16 16, pointer;
+ }
+}`);
+
// TODO: add z coordinate?
export class SpatialGeometry extends HTMLElement {
static tagName = 'spatial-geometry';
@@ -29,6 +139,9 @@ export class SpatialGeometry extends HTMLElement {
this.addEventListener('lostpointercapture', this);
this.addEventListener('touchstart', this);
this.addEventListener('dragstart', this);
+
+ const shadowRoot = this.attachShadow({ mode: 'open' });
+ shadowRoot.adoptedStyleSheets.push(styles);
}
#type: Shape = 'rectangle';
@@ -82,29 +195,42 @@ export class SpatialGeometry extends HTMLElement {
}
// Similar to `Element.getClientBoundingRect()`, but returns an SVG path that precisely outlines the shape.
- // We might also want some kind of utility function that maps a path into an approximate set of vertices.
getBoundingPath(): string {
return '';
}
+ // We might also want some kind of utility function that maps a path into an approximate set of vertices.
+ getBoundingVertices() {
+ return [];
+ }
+
handleEvent(event: PointerEvent) {
switch (event.type) {
case 'pointerdown': {
if (event.button !== 0 || event.ctrlKey) return;
- this.addEventListener('pointermove', this);
- this.setPointerCapture(event.pointerId);
+ const target = event.composedPath()[0] as HTMLElement;
+
+ target.addEventListener('pointermove', this);
+ target.setPointerCapture(event.pointerId);
+ this.setAttribute('selected', '');
+ this.#createResizeHandlers();
this.style.userSelect = 'none';
return;
}
case 'pointermove': {
- this.x += event.movementX;
- this.y += event.movementY;
+ if (event.target === this) {
+ this.x += event.movementX;
+ this.y += event.movementY;
+ } else if ((event.target as HTMLElement).matches('[resize-handler]')) {
+ console.log('resizing');
+ }
return;
}
case 'lostpointercapture': {
this.style.userSelect = '';
- this.removeEventListener('pointermove', this);
+ const target = event.composedPath()[0] as HTMLElement;
+ target.removeEventListener('pointermove', this);
return;
}
case 'touchstart':
@@ -169,4 +295,21 @@ export class SpatialGeometry extends HTMLElement {
}
}
}
+
+ #firstSelection = true;
+ #createResizeHandlers() {
+ // lazily create resize handlers on first selection
+ if (this.#firstSelection && this.shadowRoot !== null) {
+ this.shadowRoot.innerHTML = `
+
+
+
+
+
+
+
+ `;
+ this.#firstSelection = false;
+ }
+ }
}