rename handles
This commit is contained in:
parent
cc33fca454
commit
9affffeab1
|
|
@ -46,10 +46,10 @@
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
|
|
||||||
&::part(rotate),
|
&::part(rotate),
|
||||||
&::part(resize-nw),
|
&::part(resize-top-left),
|
||||||
&::part(resize-ne),
|
&::part(resize-top-right),
|
||||||
&::part(resize-se),
|
&::part(resize-bottom-right),
|
||||||
&::part(resize-sw) {
|
&::part(resize-bottom-left) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@
|
||||||
folk-shape:has(record-player) {
|
folk-shape:has(record-player) {
|
||||||
box-shadow: 10px 0px 150px 0px rgba(0, 0, 0, 0.61);
|
box-shadow: 10px 0px 150px 0px rgba(0, 0, 0, 0.61);
|
||||||
|
|
||||||
&::part(resize-nw),
|
&::part(resize-top-left),
|
||||||
&::part(resize-ne),
|
&::part(resize-top-right),
|
||||||
&::part(resize-se),
|
&::part(resize-bottom-right),
|
||||||
&::part(resize-sw) {
|
&::part(resize-bottom-left) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,8 @@ export class DOMRectTransform implements DOMRect {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: these setters work but surely there's a better way
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the **top-left** corner of the rectangle in **local space**, adjusting the position, width, and height accordingly,
|
* Sets the **top-left** corner of the rectangle in **local space**, adjusting the position, width, and height accordingly,
|
||||||
* and keeps the **bottom-right corner** fixed in the **parent space**.
|
* and keeps the **bottom-right corner** fixed in the **parent space**.
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ declare global {
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserverManager();
|
const resizeObserver = new ResizeObserverManager();
|
||||||
|
|
||||||
type ResizeHandle = 'resize-nw' | 'resize-ne' | 'resize-se' | 'resize-sw';
|
type ResizeHandle = 'resize-top-left' | 'resize-top-right' | 'resize-bottom-right' | 'resize-bottom-left';
|
||||||
type RotateHandle = 'rotation-nw' | 'rotation-ne' | 'rotation-se' | 'rotation-sw';
|
type RotateHandle = 'rotation-top-left' | 'rotation-top-right' | 'rotation-bottom-right' | 'rotation-bottom-left';
|
||||||
type Handle = ResizeHandle | RotateHandle | 'move';
|
type Handle = ResizeHandle | RotateHandle | 'move';
|
||||||
export type Dimension = number | 'auto';
|
export type Dimension = number | 'auto';
|
||||||
|
|
||||||
|
|
@ -61,17 +61,17 @@ const styles = css`
|
||||||
|
|
||||||
:host(:state(move)),
|
:host(:state(move)),
|
||||||
:host(:state(rotate)),
|
:host(:state(rotate)),
|
||||||
:host(:state(resize-nw)),
|
:host(:state(resize-top-left)),
|
||||||
:host(:state(resize-ne)),
|
:host(:state(resize-top-right)),
|
||||||
:host(:state(resize-se)),
|
:host(:state(resize-bottom-right)),
|
||||||
:host(:state(resize-sw)) {
|
:host(:state(resize-bottom-left)) {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='resize-nw'],
|
[part='resize-top-left'],
|
||||||
[part='resize-ne'],
|
[part='resize-top-right'],
|
||||||
[part='resize-se'],
|
[part='resize-bottom-right'],
|
||||||
[part='resize-sw'] {
|
[part='resize-bottom-left'] {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
@ -85,33 +85,33 @@ const styles = css`
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='resize-nw'] {
|
[part='resize-top-left'] {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='resize-ne'] {
|
[part='resize-top-right'] {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 100%;
|
left: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='resize-se'] {
|
[part='resize-bottom-right'] {
|
||||||
top: 100%;
|
top: 100%;
|
||||||
left: 100%;
|
left: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='resize-sw'] {
|
[part='resize-bottom-left'] {
|
||||||
top: 100%;
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='resize-nw'],
|
[part='resize-top-left'],
|
||||||
[part='resize-se'] {
|
[part='resize-bottom-right'] {
|
||||||
cursor: var(--resize-handle-cursor-nw);
|
cursor: var(--resize-handle-cursor-nw);
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='resize-ne'],
|
[part='resize-top-right'],
|
||||||
[part='resize-sw'] {
|
[part='resize-bottom-left'] {
|
||||||
cursor: var(--resize-handle-cursor-ne);
|
cursor: var(--resize-handle-cursor-ne);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,25 +128,25 @@ const styles = css`
|
||||||
cursor: var(--fc-rotate, url('${getRotateCursorUrl(0)}') 16 16, pointer);
|
cursor: var(--fc-rotate, url('${getRotateCursorUrl(0)}') 16 16, pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='rotation-nw'] {
|
[part='rotation-top-left'] {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
translate: -100% -100%;
|
translate: -100% -100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='rotation-ne'] {
|
[part='rotation-top-right'] {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 100%;
|
left: 100%;
|
||||||
translate: 0% -100%;
|
translate: 0% -100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='rotation-se'] {
|
[part='rotation-bottom-right'] {
|
||||||
top: 100%;
|
top: 100%;
|
||||||
left: 100%;
|
left: 100%;
|
||||||
translate: 0% 0%;
|
translate: 0% 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='rotation-sw'] {
|
[part='rotation-bottom-left'] {
|
||||||
top: 100%;
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
translate: -100% 0%;
|
translate: -100% 0%;
|
||||||
|
|
@ -257,14 +257,14 @@ export class FolkShape extends HTMLElement {
|
||||||
// Ideally we would creating these lazily on first focus, but the resize handlers need to be around for delegate focus to work.
|
// Ideally we would creating these lazily on first focus, but the resize handlers need to be around for delegate focus to work.
|
||||||
// Maybe can add the first resize handler here, and lazily instantiate the rest when needed?
|
// Maybe can add the first resize handler here, and lazily instantiate the rest when needed?
|
||||||
// I can see it becoming important at scale
|
// I can see it becoming important at scale
|
||||||
this.#shadow.innerHTML = html` <button part="rotation-nw" tabindex="-1"></button>
|
this.#shadow.innerHTML = html` <button part="rotation-top-left" tabindex="-1"></button>
|
||||||
<button part="rotation-ne" tabindex="-1"></button>
|
<button part="rotation-top-right" tabindex="-1"></button>
|
||||||
<button part="rotation-se" tabindex="-1"></button>
|
<button part="rotation-bottom-right" tabindex="-1"></button>
|
||||||
<button part="rotation-sw" tabindex="-1"></button>
|
<button part="rotation-bottom-left" tabindex="-1"></button>
|
||||||
<button part="resize-nw" aria-label="Resize shape from top left"></button>
|
<button part="resize-top-left" aria-label="Resize shape from top left"></button>
|
||||||
<button part="resize-ne" aria-label="Resize shape from top right"></button>
|
<button part="resize-top-right" aria-label="Resize shape from top right"></button>
|
||||||
<button part="resize-se" aria-label="Resize shape from bottom right"></button>
|
<button part="resize-bottom-right" aria-label="Resize shape from bottom right"></button>
|
||||||
<button part="resize-sw" aria-label="Resize shape from bottom left"></button>
|
<button part="resize-bottom-left" aria-label="Resize shape from bottom left"></button>
|
||||||
<div><slot></slot></div>`;
|
<div><slot></slot></div>`;
|
||||||
|
|
||||||
this.x = Number(this.getAttribute('x')) || 0;
|
this.x = Number(this.getAttribute('x')) || 0;
|
||||||
|
|
@ -344,10 +344,10 @@ export class FolkShape extends HTMLElement {
|
||||||
|
|
||||||
// Map handle names to corner points
|
// Map handle names to corner points
|
||||||
const HANDLE_TO_CORNER: Record<string, Point> = {
|
const HANDLE_TO_CORNER: Record<string, Point> = {
|
||||||
'resize-nw': rect.topLeft,
|
'resize-top-left': rect.topLeft,
|
||||||
'resize-ne': rect.topRight,
|
'resize-top-right': rect.topRight,
|
||||||
'resize-se': rect.bottomRight,
|
'resize-bottom-right': rect.bottomRight,
|
||||||
'resize-sw': rect.bottomLeft,
|
'resize-bottom-left': rect.bottomLeft,
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentPos = rect.toParentSpace(HANDLE_TO_CORNER[handle]);
|
const currentPos = rect.toParentSpace(HANDLE_TO_CORNER[handle]);
|
||||||
|
|
@ -410,7 +410,11 @@ export class FolkShape extends HTMLElement {
|
||||||
// Store initial angle on rotation start
|
// Store initial angle on rotation start
|
||||||
if (target.getAttribute('part')?.startsWith('rotation')) {
|
if (target.getAttribute('part')?.startsWith('rotation')) {
|
||||||
this.#initialRotation = this.#rect.rotation;
|
this.#initialRotation = this.#rect.rotation;
|
||||||
const parentRotateOrigin = this.#rect.toParentSpace(this.#rect.rotateOrigin);
|
// 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);
|
this.#startAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, parentRotateOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -440,32 +444,37 @@ export class FolkShape extends HTMLElement {
|
||||||
const handle = target.getAttribute('part') as Handle;
|
const handle = target.getAttribute('part') as Handle;
|
||||||
if (handle === null) return;
|
if (handle === null) return;
|
||||||
|
|
||||||
if (handle.includes('resize')) {
|
if (handle.startsWith('resize')) {
|
||||||
const mouse = { x: event.clientX, y: event.clientY };
|
const mouse = { x: event.clientX, y: event.clientY };
|
||||||
this.#handleResize(handle as ResizeHandle, mouse, target, event);
|
this.#handleResize(handle as ResizeHandle, mouse, target, event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handle.startsWith('rotation')) {
|
if (handle.startsWith('rotation')) {
|
||||||
const parentRotateOrigin = this.#rect.toParentSpace(this.#rect.rotateOrigin);
|
// 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 currentAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, parentRotateOrigin);
|
||||||
const rotation = this.#initialRotation + (currentAngle - this.#startAngle);
|
const rotation = this.#initialRotation + (currentAngle - this.#startAngle);
|
||||||
|
|
||||||
let degrees = (rotation * 180) / Math.PI;
|
let cursorRotation = (rotation * 180) / Math.PI;
|
||||||
switch (handle) {
|
switch (handle) {
|
||||||
case 'rotation-ne':
|
case 'rotation-top-right':
|
||||||
degrees = (degrees + 90) % 360;
|
cursorRotation = (cursorRotation + 90) % 360;
|
||||||
break;
|
break;
|
||||||
case 'rotation-se':
|
case 'rotation-bottom-right':
|
||||||
degrees = (degrees + 180) % 360;
|
cursorRotation = (cursorRotation + 180) % 360;
|
||||||
break;
|
break;
|
||||||
case 'rotation-sw':
|
case 'rotation-bottom-left':
|
||||||
degrees = (degrees + 270) % 360;
|
cursorRotation = (cursorRotation + 270) % 360;
|
||||||
break;
|
break;
|
||||||
|
// top-left handle doesn't need adjustment
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = event.composedPath()[0] as HTMLElement;
|
const target = event.composedPath()[0] as HTMLElement;
|
||||||
const rotateCursor = getRotateCursorUrl(degrees);
|
const rotateCursor = getRotateCursorUrl(cursorRotation);
|
||||||
target.style.setProperty('cursor', rotateCursor);
|
target.style.setProperty('cursor', rotateCursor);
|
||||||
this.rotation = rotation;
|
this.rotation = rotation;
|
||||||
return;
|
return;
|
||||||
|
|
@ -554,29 +563,29 @@ export class FolkShape extends HTMLElement {
|
||||||
|
|
||||||
// TODO use css variables
|
// TODO use css variables
|
||||||
const dynamicStyles = `
|
const dynamicStyles = `
|
||||||
[part='resize-nw'],
|
[part='resize-top-left'],
|
||||||
[part='resize-se'] {
|
[part='resize-bottom-right'] {
|
||||||
cursor: ${resizeCursor0};
|
cursor: ${resizeCursor0};
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='resize-ne'],
|
[part='resize-top-right'],
|
||||||
[part='resize-sw'] {
|
[part='resize-bottom-left'] {
|
||||||
cursor: ${resizeCursor90};
|
cursor: ${resizeCursor90};
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='rotation-nw'] {
|
[part='rotation-top-left'] {
|
||||||
cursor: ${rotateCursor0};
|
cursor: ${rotateCursor0};
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='rotation-ne'] {
|
[part='rotation-top-right'] {
|
||||||
cursor: ${rotateCursor90};
|
cursor: ${rotateCursor90};
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='rotation-se'] {
|
[part='rotation-bottom-right'] {
|
||||||
cursor: ${rotateCursor180};
|
cursor: ${rotateCursor180};
|
||||||
}
|
}
|
||||||
|
|
||||||
[part='rotation-sw'] {
|
[part='rotation-bottom-left'] {
|
||||||
cursor: ${rotateCursor270};
|
cursor: ${rotateCursor270};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
@ -588,16 +597,16 @@ export class FolkShape extends HTMLElement {
|
||||||
const localPointer = this.#rect.toLocalSpace(pointerPos);
|
const localPointer = this.#rect.toLocalSpace(pointerPos);
|
||||||
|
|
||||||
switch (handle) {
|
switch (handle) {
|
||||||
case 'resize-se':
|
case 'resize-bottom-right':
|
||||||
this.#rect.setBottomRight(localPointer);
|
this.#rect.setBottomRight(localPointer);
|
||||||
break;
|
break;
|
||||||
case 'resize-sw':
|
case 'resize-bottom-left':
|
||||||
this.#rect.setBottomLeft(localPointer);
|
this.#rect.setBottomLeft(localPointer);
|
||||||
break;
|
break;
|
||||||
case 'resize-nw':
|
case 'resize-top-left':
|
||||||
this.#rect.setTopLeft(localPointer);
|
this.#rect.setTopLeft(localPointer);
|
||||||
break;
|
break;
|
||||||
case 'resize-ne':
|
case 'resize-top-right':
|
||||||
this.#rect.setTopRight(localPointer);
|
this.#rect.setTopRight(localPointer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -610,28 +619,28 @@ export class FolkShape extends HTMLElement {
|
||||||
if (flipWidth && flipHeight) {
|
if (flipWidth && flipHeight) {
|
||||||
// Both axes flipped
|
// Both axes flipped
|
||||||
const oppositeHandleMap: Record<ResizeHandle, ResizeHandle> = {
|
const oppositeHandleMap: Record<ResizeHandle, ResizeHandle> = {
|
||||||
'resize-se': 'resize-nw',
|
'resize-bottom-right': 'resize-top-left',
|
||||||
'resize-sw': 'resize-ne',
|
'resize-bottom-left': 'resize-top-right',
|
||||||
'resize-nw': 'resize-se',
|
'resize-top-left': 'resize-bottom-right',
|
||||||
'resize-ne': 'resize-sw',
|
'resize-top-right': 'resize-bottom-left',
|
||||||
};
|
};
|
||||||
nextHandle = oppositeHandleMap[handle];
|
nextHandle = oppositeHandleMap[handle];
|
||||||
} else if (flipWidth) {
|
} else if (flipWidth) {
|
||||||
// Only X axis flipped
|
// Only X axis flipped
|
||||||
const flipXHandleMap: Record<ResizeHandle, ResizeHandle> = {
|
const flipXHandleMap: Record<ResizeHandle, ResizeHandle> = {
|
||||||
'resize-se': 'resize-sw',
|
'resize-bottom-right': 'resize-bottom-left',
|
||||||
'resize-sw': 'resize-se',
|
'resize-bottom-left': 'resize-bottom-right',
|
||||||
'resize-nw': 'resize-ne',
|
'resize-top-left': 'resize-top-right',
|
||||||
'resize-ne': 'resize-nw',
|
'resize-top-right': 'resize-top-left',
|
||||||
};
|
};
|
||||||
nextHandle = flipXHandleMap[handle];
|
nextHandle = flipXHandleMap[handle];
|
||||||
} else if (flipHeight) {
|
} else if (flipHeight) {
|
||||||
// Only Y axis flipped
|
// Only Y axis flipped
|
||||||
const flipYHandleMap: Record<ResizeHandle, ResizeHandle> = {
|
const flipYHandleMap: Record<ResizeHandle, ResizeHandle> = {
|
||||||
'resize-se': 'resize-ne',
|
'resize-bottom-right': 'resize-top-right',
|
||||||
'resize-sw': 'resize-nw',
|
'resize-bottom-left': 'resize-top-left',
|
||||||
'resize-nw': 'resize-sw',
|
'resize-top-left': 'resize-bottom-left',
|
||||||
'resize-ne': 'resize-se',
|
'resize-top-right': 'resize-bottom-right',
|
||||||
};
|
};
|
||||||
nextHandle = flipYHandleMap[handle];
|
nextHandle = flipYHandleMap[handle];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue