diff --git a/demo/ink.html b/demo/ink.html
index ff71a96..c642afd 100644
--- a/demo/ink.html
+++ b/demo/ink.html
@@ -13,7 +13,6 @@
min-height: 100%;
position: relative;
margin: 0;
- padding: 0;
}
@@ -33,26 +32,24 @@
async function draw(e) {
if (e.target === drawButton) return;
+ // Stop the default focus and pointer capture of geometry elements
+ e.preventDefault();
+ e.stopPropagation();
+
const ink = document.createElement('spatial-ink');
- ink.style.cursor = 'var(--tracing-cursor, crosshair)';
- ink.style.position = 'fixed';
- ink.style.inset = '0 0 0 0';
- ink.style.zIndex = 'calc(infinity)';
document.body.appendChild(ink);
- const rect = await ink.draw(e);
+ await ink.draw(e);
+ const rect = ink.getPathBox();
+
+ ink.points = ink.points.map(([x, y, p]) => [x - rect.x, y - rect.y, p]);
+
const geometry = document.createElement('spatial-geometry');
geometry.x = rect.x;
geometry.y = rect.y;
geometry.height = rect.height;
geometry.width = rect.width;
-
- ink.points = ink.points.map(([x, y, p]) => [x - rect.x, y - rect.y, p]);
- ink.style.cursor = '';
- ink.style.position = '';
- ink.style.inset = '';
- ink.style.zIndex = '';
geometry.appendChild(ink);
document.body.appendChild(geometry);
@@ -65,10 +62,10 @@
isDrawing = !isDrawing;
if (isDrawing) {
- document.addEventListener('pointerdown', draw);
+ document.addEventListener('pointerdown', draw, { capture: true });
drawButton.textContent = 'Drawing';
} else {
- document.removeEventListener('pointerdown', draw);
+ document.removeEventListener('pointerdown', draw, { capture: true });
drawButton.textContent = 'Draw';
}
});
diff --git a/demo/shapes.html b/demo/shapes.html
index b7643d3..a1a4f80 100644
--- a/demo/shapes.html
+++ b/demo/shapes.html
@@ -17,10 +17,11 @@
spatial-geometry {
background: rgb(187, 178, 178);
- box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;
+ box-shadow: rgba(0, 0, 0, 0.2) 1.95px 1.95px 2.6px;
+ transition: scale 100ms ease-out, box-shadow 100ms ease-out;
}
- spatial-geometry:state(moving) {
+ spatial-geometry:state(move) {
scale: 1.05;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
}
diff --git a/src/canvas/spatial-geometry.ts b/src/canvas/spatial-geometry.ts
index 1e574fc..3815a6d 100644
--- a/src/canvas/spatial-geometry.ts
+++ b/src/canvas/spatial-geometry.ts
@@ -51,7 +51,12 @@ styles.replaceSync(`
outline: solid 2px hsl(214, 84%, 56%);
}
-:host(:state(moving)) {
+:host(:state(move)),
+:host(:state(rotate)),
+:host(:state(resize-nw)),
+:host(:state(resize-ne)),
+:host(:state(resize-se)),
+:host(:state(resize-sw)), {
user-select: none;
}
@@ -139,9 +144,6 @@ export class SpatialGeometry extends HTMLElement {
super();
this.addEventListener('pointerdown', this);
- this.addEventListener('lostpointercapture', this);
- // this.addEventListener('touchstart', this);
- // this.addEventListener('dragstart', this);
const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true });
shadowRoot.adoptedStyleSheets.push(styles);
@@ -255,8 +257,12 @@ export class SpatialGeometry extends HTMLElement {
if (target !== this && !target.hasAttribute('part')) return;
target.addEventListener('pointermove', this);
+ this.addEventListener('lostpointercapture', this);
target.setPointerCapture(event.pointerId);
- this.#internals.states.add('moving');
+
+ const interaction = target.getAttribute('part') || 'move';
+ this.#internals.states.add(interaction);
+
this.focus();
return;
}
@@ -308,14 +314,11 @@ export class SpatialGeometry extends HTMLElement {
return;
}
case 'lostpointercapture': {
- this.#internals.states.delete('moving');
const target = event.composedPath()[0] as HTMLElement;
+ const interaction = target.getAttribute('part') || 'move';
+ this.#internals.states.delete(interaction);
target.removeEventListener('pointermove', this);
- return;
- }
- case 'touchstart':
- case 'dragstart': {
- event.preventDefault();
+ this.removeEventListener('lostpointercapture', this);
return;
}
}
diff --git a/src/canvas/spatial-ink.ts b/src/canvas/spatial-ink.ts
index ee12616..f38c472 100644
--- a/src/canvas/spatial-ink.ts
+++ b/src/canvas/spatial-ink.ts
@@ -13,6 +13,13 @@ styles.replaceSync(`
height: 100%;
width: 100%;
touch-action: none;
+ pointer-events: none;
+ }
+
+ :host(:state(drawing)) {
+ position: fixed;
+ inset: 0 0 0 0;
+ cursor: var(--tracing-cursor, crosshair);
}
`);
@@ -25,18 +32,14 @@ export class SpatialInk extends HTMLElement {
#internals = this.attachInternals();
+ #svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
#path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
- #stroke: Stroke = [];
-
- #d = '';
-
#size = Number(this.getAttribute('size') || 16);
get size() {
return this.#size;
}
-
set size(size) {
this.#size = size;
this.#update();
@@ -47,7 +50,6 @@ export class SpatialInk extends HTMLElement {
get thinning() {
return this.#thinning;
}
-
set thinning(thinning) {
this.#thinning = thinning;
this.#update();
@@ -58,7 +60,6 @@ export class SpatialInk extends HTMLElement {
get smoothing() {
return this.#smoothing;
}
-
set smoothing(smoothing) {
this.#smoothing = smoothing;
this.#update();
@@ -69,7 +70,6 @@ export class SpatialInk extends HTMLElement {
get streamline() {
return this.#streamline;
}
-
set streamline(streamline) {
this.#streamline = streamline;
this.#update();
@@ -80,7 +80,6 @@ export class SpatialInk extends HTMLElement {
get simulatePressure() {
return this.#simulatePressure;
}
-
set simulatePressure(simulatePressure) {
this.#simulatePressure = simulatePressure;
this.#update();
@@ -91,7 +90,6 @@ export class SpatialInk extends HTMLElement {
get points() {
return this.#points;
}
-
set points(points) {
this.#points = points;
this.#update();
@@ -102,27 +100,32 @@ export class SpatialInk extends HTMLElement {
const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true });
shadowRoot.adoptedStyleSheets.push(styles);
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- svg.appendChild(this.#path);
- shadowRoot.appendChild(svg);
+ this.#svg.appendChild(this.#path);
+ shadowRoot.appendChild(this.#svg);
}
connectedCallback() {
this.#update();
}
- #tracingPromise: PromiseWithResolvers | null = null;
+ getPathBox() {
+ return this.#path.getBBox();
+ }
+
+ setViewBox() {
+ this.#svg.viewBox;
+ }
+
+ #tracingPromise: PromiseWithResolvers | null = null;
// TODO: cancel trace?
- async draw(event: PointerEvent): Promise {
- if (event.button !== 0 || event.ctrlKey) return;
-
- this.points = [];
- this.addPoint([event.pageX, event.pageY, event.pressure]);
- this.addEventListener('lostpointercapture', this);
- this.addEventListener('pointermove', this);
- this.setPointerCapture(event.pointerId);
- this.#tracingPromise = Promise.withResolvers();
+ draw(event?: PointerEvent) {
+ if (event?.type === 'pointerdown') {
+ this.handleEvent(event);
+ } else {
+ this.addEventListener('pointerdown', this);
+ }
+ this.#tracingPromise = Promise.withResolvers();
return this.#tracingPromise.promise;
}
@@ -133,15 +136,27 @@ export class SpatialInk extends HTMLElement {
handleEvent(event: PointerEvent) {
switch (event.type) {
+ case 'pointerdown': {
+ if (event.button !== 0 || event.ctrlKey) return;
+
+ this.points = [];
+ this.addPoint([event.offsetX, event.offsetY, event.pressure]);
+ this.addEventListener('lostpointercapture', this);
+ this.addEventListener('pointermove', this);
+ this.setPointerCapture(event.pointerId);
+ this.#internals.states.add('drawing');
+ return;
+ }
case 'pointermove': {
- this.addPoint([event.pageX, event.pageY, event.pressure]);
+ this.addPoint([event.offsetX, event.offsetY, event.pressure]);
return;
}
case 'lostpointercapture': {
- this.removeEventListener('pointermove', this);
this.removeEventListener('pointerdown', this);
+ this.removeEventListener('pointermove', this);
this.removeEventListener('lostpointercapture', this);
- this.#tracingPromise?.resolve(this.#path.getBoundingClientRect());
+ this.#internals.states.delete('drawing');
+ this.#tracingPromise?.resolve();
this.#tracingPromise = null;
return;
}
@@ -168,10 +183,7 @@ export class SpatialInk extends HTMLElement {
cap: true,
},
};
-
- this.#stroke = getStroke(this.#points, options);
- this.#d = this.#getSvgPathFromStroke(this.#stroke);
- this.#path.setAttribute('d', this.#d);
+ this.#path.setAttribute('d', this.#getSvgPathFromStroke(getStroke(this.#points, options)));
}
#getSvgPathFromStroke(stroke: Stroke): string {