simplify event handling

This commit is contained in:
Orion Reed 2024-12-07 17:41:07 -05:00
parent 8c27e16314
commit 2465f4758b
1 changed files with 102 additions and 162 deletions

View File

@ -323,186 +323,126 @@ export class FolkShape extends HTMLElement {
} }
handleEvent(event: PointerEvent | KeyboardEvent) { handleEvent(event: PointerEvent | KeyboardEvent) {
if (event instanceof KeyboardEvent) { const focusedElement = this.#shadow.activeElement as HTMLElement | null;
const MOVEMENT_DELTA = event.shiftKey ? 20 : 2; const target = event.composedPath()[0] as HTMLElement;
const ROTATION_DELTA = event.shiftKey ? Math.PI / 12 : Math.PI / 36; // 15 or 5 degrees let handle: Handle | null = null;
if (target) {
handle = target.getAttribute('part') as Handle | null;
} else if (focusedElement) {
handle = focusedElement.getAttribute('part') as Handle | null;
}
// Get the focused element to check if it's a resize handle // Handle pointer capture setup/cleanup
const focusedElement = this.#shadow.activeElement; if (event instanceof PointerEvent) {
const handle = focusedElement?.getAttribute('part') as Handle | null; if (event.type === 'pointerdown') {
if (target !== this && !handle) return;
if (handle?.startsWith('resize')) { // Setup rotation initial state if needed
const anyChange = if (handle?.startsWith('rotation')) {
event.key === 'ArrowUp' || this.#initialRotation = this.#rect.rotation;
event.key === 'ArrowDown' || const parentRotateOrigin = this.#rect.toParentSpace({
event.key === 'ArrowLeft' || x: this.#rect.width * this.#rect.rotateOrigin.x,
event.key === 'ArrowRight'; y: this.#rect.height * this.#rect.rotateOrigin.y,
if (!anyChange) return; });
// Calculate initial angle including current rotation
const mousePos = { x: event.clientX, y: event.clientY };
this.#startAngle = Vector.angleFromOrigin(mousePos, parentRotateOrigin) - this.#rect.rotation;
}
// Get the corner coordinates of the shape for the corresponding handle // Setup pointer capture
const rect = this.#rect; target.addEventListener('pointermove', this);
target.addEventListener('lostpointercapture', this);
// Map handle names to corner points target.setPointerCapture(event.pointerId);
const HANDLE_TO_CORNER: Record<string, Point> = { this.#internals.states.add(handle || 'move');
'resize-top-left': rect.topLeft, this.focus();
'resize-top-right': rect.topRight,
'resize-bottom-right': rect.bottomRight,
'resize-bottom-left': rect.bottomLeft,
};
const currentPos = rect.toParentSpace(HANDLE_TO_CORNER[handle]);
let delta = { x: 0, y: 0 };
if (event.key === 'ArrowRight') delta.x = MOVEMENT_DELTA;
if (event.key === 'ArrowLeft') delta.x = -MOVEMENT_DELTA;
if (event.key === 'ArrowDown') delta.y = MOVEMENT_DELTA;
if (event.key === 'ArrowUp') delta.y = -MOVEMENT_DELTA;
const syntheticMouse = {
x: currentPos.x + delta.x,
y: currentPos.y + delta.y,
};
// Calculate movement based on arrow keys
// Process resize using the same logic as mouse events
this.#handleResize(handle as ResizeHandle, syntheticMouse, focusedElement as HTMLElement);
event.preventDefault();
return; return;
} }
// Handle rotation with Alt key if (event.type === 'lostpointercapture') {
if (event.altKey) { this.#internals.states.delete(handle || 'move');
switch (event.key) { target.removeEventListener('pointermove', this);
case 'ArrowLeft': target.removeEventListener('lostpointercapture', this);
this.rotation -= ROTATION_DELTA; this.#updateCursors();
event.preventDefault(); if (handle?.startsWith('rotation')) {
return; target.style.removeProperty('cursor');
case 'ArrowRight':
this.rotation += ROTATION_DELTA;
event.preventDefault();
return;
} }
return;
} }
}
switch (event.key) { // Calculate movement delta from either keyboard or pointer
case 'ArrowLeft': let moveDelta: Point | null = null;
this.x -= MOVEMENT_DELTA; if (event instanceof KeyboardEvent) {
event.preventDefault(); const MOVEMENT_MUL = event.shiftKey ? 20 : 2;
return; const arrowKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
case 'ArrowRight': if (!arrowKeys.includes(event.key)) return;
this.x += MOVEMENT_DELTA;
event.preventDefault(); moveDelta = {
return; x: (event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0) * MOVEMENT_MUL,
case 'ArrowUp': y: (event.key === 'ArrowDown' ? 1 : event.key === 'ArrowUp' ? -1 : 0) * MOVEMENT_MUL,
this.y -= MOVEMENT_DELTA; };
event.preventDefault(); } else if (event.type === 'pointermove') {
return; if (!target) return;
case 'ArrowDown': moveDelta = { x: event.movementX, y: event.movementY };
this.y += MOVEMENT_DELTA; }
event.preventDefault();
return; if (!moveDelta) return;
// Handle shape movement and rotation
if (target === this || (!handle && event instanceof KeyboardEvent)) {
if (event instanceof KeyboardEvent && event.altKey) {
const ROTATION_MUL = event.shiftKey ? Math.PI / 12 : Math.PI / 36;
const rotationDelta = moveDelta.x !== 0 ? (moveDelta.x > 0 ? ROTATION_MUL : -ROTATION_MUL) : 0;
this.rotation += rotationDelta;
} else {
this.x += moveDelta.x;
this.y += moveDelta.y;
} }
event.preventDefault();
return; return;
} }
if (event instanceof PointerEvent) { // Handle resize
switch (event.type) { if (handle?.startsWith('resize') || handle?.startsWith('resize')) {
case 'pointerdown': { const rect = this.#rect;
if (event.button !== 0 || event.ctrlKey) return; const corner = {
'resize-top-left': rect.topLeft,
'resize-top-right': rect.topRight,
'resize-bottom-right': rect.bottomRight,
'resize-bottom-left': rect.bottomLeft,
}[handle as ResizeHandle];
const target = event.composedPath()[0] as HTMLElement; const currentPos = rect.toParentSpace(corner);
const mousePos =
event instanceof KeyboardEvent
? { x: currentPos.x + moveDelta.x, y: currentPos.y + moveDelta.y }
: { x: event.clientX, y: event.clientY };
// Store initial angle on rotation start this.#handleResize(handle as ResizeHandle, mousePos, target, event instanceof PointerEvent ? event : undefined);
if (target.getAttribute('part')?.startsWith('rotation')) { event.preventDefault();
this.#initialRotation = this.#rect.rotation; return;
// Calculate the absolute rotation origin in parent space }
const parentRotateOrigin = this.#rect.toParentSpace({
x: this.#rect.width * this.#rect.rotateOrigin.x,
y: this.#rect.height * this.#rect.rotateOrigin.y,
});
this.#startAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, parentRotateOrigin);
}
// ignore interactions from slotted elements. // Handle pointer rotation
if (target !== this && !target.hasAttribute('part')) return; if (handle?.startsWith('rotation') && event instanceof PointerEvent) {
const parentRotateOrigin = this.#rect.toParentSpace({
x: this.#rect.width * this.#rect.rotateOrigin.x,
y: this.#rect.height * this.#rect.rotateOrigin.y,
});
const currentAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, parentRotateOrigin);
// Apply rotation relative to start angle
this.rotation = currentAngle - this.#startAngle;
target.addEventListener('pointermove', this); const degrees = (this.#rect.rotation * 180) / Math.PI;
target.addEventListener('lostpointercapture', this); const cursorRotation = {
target.setPointerCapture(event.pointerId); 'rotation-top-left': degrees,
'rotation-top-right': (degrees + 90) % 360,
'rotation-bottom-right': (degrees + 180) % 360,
'rotation-bottom-left': (degrees + 270) % 360,
}[handle as RotateHandle];
const interaction = target.getAttribute('part') || 'move'; target.style.setProperty('cursor', getRotateCursorUrl(cursorRotation));
this.#internals.states.add(interaction); return;
this.focus();
return;
}
case 'pointermove': {
const target = event.composedPath()[0] as HTMLElement;
if (target === null) return;
if (target === this) {
this.x += event.movementX;
this.y += event.movementY;
return;
}
const handle = target.getAttribute('part') as Handle;
if (handle === null) return;
if (handle.startsWith('resize')) {
const mouse = { x: event.clientX, y: event.clientY };
this.#handleResize(handle as ResizeHandle, mouse, target, event);
return;
}
if (handle.startsWith('rotation')) {
// Calculate the absolute rotation origin in parent space
const parentRotateOrigin = this.#rect.toParentSpace({
x: this.#rect.width * this.#rect.rotateOrigin.x,
y: this.#rect.height * this.#rect.rotateOrigin.y,
});
const currentAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, parentRotateOrigin);
const rotation = this.#initialRotation + (currentAngle - this.#startAngle);
let cursorRotation = (rotation * 180) / Math.PI;
switch (handle) {
case 'rotation-top-right':
cursorRotation = (cursorRotation + 90) % 360;
break;
case 'rotation-bottom-right':
cursorRotation = (cursorRotation + 180) % 360;
break;
case 'rotation-bottom-left':
cursorRotation = (cursorRotation + 270) % 360;
break;
// top-left handle doesn't need adjustment
}
const target = event.composedPath()[0] as HTMLElement;
const rotateCursor = getRotateCursorUrl(cursorRotation);
target.style.setProperty('cursor', rotateCursor);
this.rotation = rotation;
return;
}
return;
}
case 'lostpointercapture': {
const target = event.composedPath()[0] as HTMLElement;
const interaction = target.getAttribute('part') || 'move';
this.#internals.states.delete(interaction);
target.removeEventListener('pointermove', this);
target.removeEventListener('lostpointercapture', this);
if (target.getAttribute('part')?.startsWith('rotation')) {
target.style.removeProperty('cursor');
}
this.#updateCursors();
return;
}
}
} }
} }