Merge branch 'dev'

This commit is contained in:
Jeff Emmett 2026-03-03 12:03:47 -08:00
commit ff9d44b632
1 changed files with 50 additions and 0 deletions

View File

@ -340,6 +340,36 @@ export class FolkShape extends FolkElement {
this.dispatchEvent(new CustomEvent("edit-enter")); this.dispatchEvent(new CustomEvent("edit-enter"));
} }
/** Check if a screen-space point overlaps a text input inside this shape's slotted content. */
#findTextInputAtPoint(clientX: number, clientY: number): HTMLElement | null {
const slot = (this.renderRoot as ShadowRoot).querySelector("slot");
if (!slot) return null;
const TEXT_SELECTOR = 'textarea, [contenteditable="true"], input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="hidden"])';
for (const el of slot.assignedElements()) {
// Check light DOM children
const lightInputs = el.querySelectorAll<HTMLElement>(TEXT_SELECTOR);
for (const input of lightInputs) {
const rect = input.getBoundingClientRect();
if (clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom) {
return input;
}
}
// Check shadow DOM children (one level deep — covers all folk-* shapes)
if ((el as Element).shadowRoot) {
const shadowInputs = (el as Element).shadowRoot!.querySelectorAll<HTMLElement>(TEXT_SELECTOR);
for (const input of shadowInputs) {
const rect = input.getBoundingClientRect();
if (clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom) {
return input;
}
}
}
}
return null;
}
exitEditMode() { exitEditMode() {
if (!this.#editing) return; if (!this.#editing) return;
this.#editing = false; this.#editing = false;
@ -462,7 +492,15 @@ export class FolkShape extends FolkElement {
if (event.type === "touchstart" && event.touches.length === 1) { if (event.type === "touchstart" && event.touches.length === 1) {
if (!isValidDragTarget) return; if (!isValidDragTarget) return;
// Check if touch is over a text input — enter edit mode instead of dragging
const touch = event.touches[0]; const touch = event.touches[0];
const textInput = this.#findTextInputAtPoint(touch.clientX, touch.clientY);
if (textInput) {
this.enterEditMode();
requestAnimationFrame(() => textInput.focus());
return;
}
this.#lastTouchPos = { x: touch.clientX, y: touch.clientY }; this.#lastTouchPos = { x: touch.clientX, y: touch.clientY };
this.#isTouchDragging = true; this.#isTouchDragging = true;
this.#internals.states.add("move"); this.#internals.states.add("move");
@ -520,6 +558,18 @@ export class FolkShape extends FolkElement {
// Allow drag from: the host itself, a handle, or a drag handle element // Allow drag from: the host itself, a handle, or a drag handle element
if (target !== this && !handle && !isDragHandle) return; if (target !== this && !handle && !isDragHandle) return;
// If clicking on the shape body (not a handle), check if a text input
// is under the pointer — if so, enter edit mode immediately instead of dragging.
if (!handle && (target === this || isDragHandle)) {
const textInput = this.#findTextInputAtPoint(event.clientX, event.clientY);
if (textInput) {
this.enterEditMode();
// Re-dispatch click so the input receives focus after pointer-events are enabled
requestAnimationFrame(() => textInput.focus());
return;
}
}
if (handle?.startsWith("rotation")) { if (handle?.startsWith("rotation")) {
const parentRotateOrigin = this.#rect.toParentSpace({ const parentRotateOrigin = this.#rect.toParentSpace({
x: this.#rect.width * this.#rect.rotateOrigin.x, x: this.#rect.width * this.#rect.rotateOrigin.x,