Polish and add aborting to several input widgets: no Esc closing parent menus; color picker axis align; repeat on arrow buttons (#2276)

* Remove color input outline; reduce antialiasing compositing artifacts in color widgets

* Rename ColorButton to ColorInput

* Add features and aborting to several other widgets

- Prevent Esc from closing parent floating menus when aborting
- Fix missing icon regression
- Gutter resizing abort
- Color picker aborts, Shift axis alignment, improve click/drag behavior for gradient spectrum
- Scrollbar abort, repeat when held, fix directional arrows when viewport is zoomed
- Number input abort, repeat when held

* Move ColorInput into the inputs folder

* Fix tiny logo
This commit is contained in:
Keavon Chambers
2025-02-10 05:46:41 -08:00
committed by GitHub
parent 0037f5158c
commit ec8c8d6485
38 changed files with 1031 additions and 452 deletions
@@ -28,6 +28,8 @@
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte";
const BUTTON_MIDDLE = 1;
const editor = getContext<Editor>("editor");
export let tabMinWidths = false;
@@ -100,7 +102,7 @@
}}
on:auxclick={(e) => {
// Middle mouse button click
if (e.button === 1) {
if (e.button === BUTTON_MIDDLE) {
e.stopPropagation();
closeAction?.(tabIndex);
}
@@ -110,7 +112,7 @@
// https://developer.mozilla.org/en-US/docs/Web/API/Element/auxclick_event#browser_compatibility
// The downside of using mouseup is that the mousedown didn't have to originate in the same element.
// A possible future improvement could save the target element during mousedown and check if it's the same here.
if (!isEventSupported("auxclick") && e.button === 1) {
if (!isEventSupported("auxclick") && e.button === BUTTON_MIDDLE) {
e.stopPropagation();
closeAction?.(tabIndex);
}
@@ -14,15 +14,17 @@
const MIN_PANEL_SIZE = 100;
const PANEL_SIZES = {
/**/ root: 100,
/* ├─ */ content: 80,
/* │ ├── */ document: 100,
/* └─ */ details: 20,
/* ├── */ properties: 45,
/* └── */ layers: 55,
/* ├─ */ content: 80,
/* │ ─ */ document: 100,
/* └─ */ details: 20,
/* ─ */ properties: 45,
/* ─ */ layers: 55,
};
let panelSizes = PANEL_SIZES;
let documentPanel: Panel | undefined;
let gutterResizeRestore: [number, number] | undefined = undefined;
let pointerCaptureId: number | undefined = undefined;
$: documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex);
@@ -64,32 +66,68 @@
const proportionBeingResized = totalResizingSpaceOccupied / parentElementSize;
// Prevent cursor flicker as mouse temporarily leaves the gutter
gutter.setPointerCapture(e.pointerId);
pointerCaptureId = e.pointerId;
gutter.setPointerCapture(pointerCaptureId);
const mouseStart = isHorizontal ? e.clientX : e.clientY;
const updatePosition = (e: PointerEvent) => {
const abortResize = () => {
if (pointerCaptureId) gutter.releasePointerCapture(pointerCaptureId);
removeListeners();
pointerCaptureId = e.pointerId;
gutter.setPointerCapture(pointerCaptureId);
if (gutterResizeRestore !== undefined) {
panelSizes[nextSiblingName] = gutterResizeRestore[0];
panelSizes[prevSiblingName] = gutterResizeRestore[1];
gutterResizeRestore = undefined;
}
};
const onPointerMove = (e: PointerEvent) => {
const mouseCurrent = isHorizontal ? e.clientX : e.clientY;
let mouseDelta = mouseStart - mouseCurrent;
mouseDelta = Math.max(nextSiblingSize + mouseDelta, MIN_PANEL_SIZE) - nextSiblingSize;
mouseDelta = prevSiblingSize - Math.max(prevSiblingSize - mouseDelta, MIN_PANEL_SIZE);
if (gutterResizeRestore === undefined) gutterResizeRestore = [panelSizes[nextSiblingName], panelSizes[prevSiblingName]];
panelSizes[nextSiblingName] = ((nextSiblingSize + mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100;
panelSizes[prevSiblingName] = ((prevSiblingSize - mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100;
};
const cleanup = (e: PointerEvent) => {
gutter.releasePointerCapture(e.pointerId);
document.removeEventListener("pointermove", updatePosition);
document.removeEventListener("pointerleave", cleanup);
document.removeEventListener("pointerup", cleanup);
const onPointerUp = () => {
gutterResizeRestore = undefined;
if (pointerCaptureId) gutter.releasePointerCapture(pointerCaptureId);
removeListeners();
};
document.addEventListener("pointermove", updatePosition);
document.addEventListener("pointerleave", cleanup);
document.addEventListener("pointerup", cleanup);
const onMouseDown = (e: MouseEvent) => {
const BUTTONS_RIGHT = 0b0000_0010;
if (e.buttons & BUTTONS_RIGHT) abortResize();
};
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") abortResize();
};
const addListeners = () => {
document.addEventListener("pointermove", onPointerMove);
document.addEventListener("pointerup", onPointerUp);
document.addEventListener("mousedown", onMouseDown);
document.addEventListener("keydown", onKeyDown);
};
const removeListeners = () => {
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
document.removeEventListener("mousedown", onMouseDown);
document.removeEventListener("keydown", onKeyDown);
};
addListeners();
}
</script>