From 23ee5538eb602db4e97ed695c2640f95bfaf7d23 Mon Sep 17 00:00:00 2001 From: Ethan James Date: Thu, 2 Jan 2025 14:14:25 -0800 Subject: [PATCH 1/6] Bullet: Use fastClick for clickHandler --- src/components/Bullet.tsx | 12 +++++++----- src/util/fastClick.ts | 11 +++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/Bullet.tsx b/src/components/Bullet.tsx index a4ff06c6ca0..35fed6fdf08 100644 --- a/src/components/Bullet.tsx +++ b/src/components/Bullet.tsx @@ -20,6 +20,7 @@ import getThoughtById from '../selectors/getThoughtById' import isContextViewActive from '../selectors/isContextViewActive' import isMulticursorPath from '../selectors/isMulticursorPath' import rootedParentOf from '../selectors/rootedParentOf' +import fastClick, { type FastClickEvent } from '../util/fastClick' import hashPath from '../util/hashPath' import head from '../util/head' import isDivider from '../util/isDivider' @@ -531,16 +532,15 @@ const Bullet = ({ // expand or collapse on click // has some additional logic to make it work intuitively with pin true/false const clickHandler = useCallback( - (e: React.MouseEvent) => { - // stop click event from bubbling up to Content.clickOnEmptySpace - e.stopPropagation() + (e: FastClickEvent) => { // short circuit if dragHold // useLongPress stop is activated in onMouseUp but is delayed to ensure that dragHold is still true here // stopping propagation from useLongPress was not working either due to bubbling order or mismatched event type if (dragHold) return // short circuit if toggling multiselect - if (!isTouch && (isMac ? e.metaKey : e.ctrlKey)) { + // e should always be a MouseEvent if !isTouch, but getting the type system to believe it is another story + if (!isTouch && e instanceof MouseEvent && (isMac ? e.metaKey : e.ctrlKey)) { dispatch(toggleMulticursor({ path })) return } @@ -605,7 +605,9 @@ const Bullet = ({ paddingBottom: extendClickHeight + 2, width, }} - onClick={clickHandler} + {...fastClick(clickHandler)} + // stop click event from bubbling up to Content.clickOnEmptySpace + onClick={e => e.stopPropagation()} > | React.MouseEvent + // the number of pixels of scrolling or dragging from touchStart that is allowed to still trigger fastClick const MOVE_THRESHOLD = 15 @@ -12,13 +15,13 @@ const fastClick = isTouch ? ( // triggered on mouseup or touchend // cancelled if the user scroll or drags - tapUp: (e: React.TouchEvent) => void, + tapUp: (e: FastClickEvent) => void, // triggered on mousedown or touchstart - tapDown?: (e: React.TouchEvent) => void, + tapDown?: (e: FastClickEvent) => void, // triggered when tapUp is cancelled due to scrolling or dragging // does not work with drag-and-drop on desktop (onMouseUp does not trigger) - tapCancel?: (e: React.TouchEvent) => void, - // triggered with touchMove + tapCancel?: (e: FastClickEvent) => void, + // triggered with touchMove, which can never be a MouseEvent touchMove?: (e: React.TouchEvent) => void, ) => ({ onTouchStart: (e: React.TouchEvent) => { From 27dddfbee348fe1c19615390d127b27d489412df Mon Sep 17 00:00:00 2001 From: Ethan James Date: Fri, 3 Jan 2025 15:30:34 -0800 Subject: [PATCH 2/6] Editable: set cursor onTap rather than onFocus --- src/components/Editable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Editable.tsx b/src/components/Editable.tsx index 76b0d559f15..8a7428cc015 100644 --- a/src/components/Editable.tsx +++ b/src/components/Editable.tsx @@ -545,7 +545,7 @@ const Editable = ({ if ( disabled || // dragInProgress: not sure if this can happen, but I observed some glitchy behavior with the cursor moving when a drag and drop is completed so check dragInProgress to be safe - (!globals.touching && !state.dragInProgress && !state.dragHold && (!editingOrOnCursor || !isVisible)) + (isTouch && !globals.touching && !state.dragInProgress && !state.dragHold) ) { // do not set cursor on hidden thought e.preventDefault() @@ -557,7 +557,7 @@ const Editable = ({ if (state.showLetterCase) dispatch(toggleLetterCase({ value: false })) } else { - setCursorOnThought() + setCursorOnThought({ editing: editingOrOnCursor }) // When the the cursor is first set on a thought, prevent the default browser behavior to avoid activating edit mode. // Do not reset until the long tap is definitely over. From e2ab83f870d9b0337584b0840db4ad174f91c4f5 Mon Sep 17 00:00:00 2001 From: Ethan James Date: Fri, 3 Jan 2025 16:06:37 -0800 Subject: [PATCH 3/6] Editable: iOS Safari - place caret at end onTap --- src/components/Editable.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/Editable.tsx b/src/components/Editable.tsx index 8a7428cc015..ec6810e8785 100644 --- a/src/components/Editable.tsx +++ b/src/components/Editable.tsx @@ -18,7 +18,7 @@ import { setCursorActionCreator as setCursor } from '../actions/setCursor' import { toggleColorPickerActionCreator as toggleColorPicker } from '../actions/toggleColorPicker' import { toggleLetterCaseActionCreator as toggleLetterCase } from '../actions/toggleLetterCase' import { tutorialNextActionCreator as tutorialNext } from '../actions/tutorialNext' -import { isMac, isTouch } from '../browser' +import { isMac, isSafari, isTouch } from '../browser' import { commandEmitter } from '../commands' import { EDIT_THROTTLE, @@ -203,6 +203,8 @@ const Editable = ({ // set offset to null to allow the browser to set the position of the selection let offset = null + if (isTouch && isSafari() && contentRef.current?.innerHTML.length) offset = contentRef.current.innerHTML.length + // if running for the first time, restore the offset if the path matches the restored cursor if (!cursorOffsetInitialized) { const restored: { path: Path | null; offset: number | null } = storageModel.get('cursor') @@ -545,7 +547,7 @@ const Editable = ({ if ( disabled || // dragInProgress: not sure if this can happen, but I observed some glitchy behavior with the cursor moving when a drag and drop is completed so check dragInProgress to be safe - (isTouch && !globals.touching && !state.dragInProgress && !state.dragHold) + (isTouch && isSafari() && !globals.touching && !state.dragInProgress && !state.dragHold) ) { // do not set cursor on hidden thought e.preventDefault() From 5d6a76b97e606e950fdd91f5c46b2cf1a7c73389 Mon Sep 17 00:00:00 2001 From: Ethan James Date: Fri, 3 Jan 2025 16:13:19 -0800 Subject: [PATCH 4/6] useEditMode: explicitly focus contentRef on iOS --- src/components/Editable/useEditMode.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Editable/useEditMode.ts b/src/components/Editable/useEditMode.ts index 9a95a49a85f..eab7e9815b1 100644 --- a/src/components/Editable/useEditMode.ts +++ b/src/components/Editable/useEditMode.ts @@ -51,6 +51,7 @@ const useEditMode = ({ selection.clear() } else { selection.set(contentRef.current, { offset: editingCursorOffset || 0 }) + if (isTouch && isSafari()) contentRef.current?.focus() } } From a81cfd88b898324a272c91d8295c1fb89fab2b65 Mon Sep 17 00:00:00 2001 From: Ethan James Date: Wed, 8 Jan 2025 17:20:23 +0000 Subject: [PATCH 5/6] Update src/components/Bullet.tsx Co-authored-by: Trevin Hofmann --- src/components/Bullet.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Bullet.tsx b/src/components/Bullet.tsx index 35fed6fdf08..9b7fd137582 100644 --- a/src/components/Bullet.tsx +++ b/src/components/Bullet.tsx @@ -539,8 +539,7 @@ const Bullet = ({ if (dragHold) return // short circuit if toggling multiselect - // e should always be a MouseEvent if !isTouch, but getting the type system to believe it is another story - if (!isTouch && e instanceof MouseEvent && (isMac ? e.metaKey : e.ctrlKey)) { + if (!isTouch && (isMac ? e.metaKey : e.ctrlKey)) { dispatch(toggleMulticursor({ path })) return } From 394b481ef55801f4362bb6d15be5dd1429dc956f Mon Sep 17 00:00:00 2001 From: Ethan James Date: Thu, 9 Jan 2025 11:21:18 -0800 Subject: [PATCH 6/6] Revert changes to onTap, eliminate dnd touch delay --- src/components/DragAndDropContext.tsx | 2 -- src/components/Editable.tsx | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/DragAndDropContext.tsx b/src/components/DragAndDropContext.tsx index 1d4ec1e1829..5a201546206 100644 --- a/src/components/DragAndDropContext.tsx +++ b/src/components/DragAndDropContext.tsx @@ -3,7 +3,6 @@ import { DndProvider } from 'react-dnd' import { HTML5Backend } from 'react-dnd-html5-backend' import { MouseTransition, MultiBackend, TouchTransition } from 'react-dnd-multi-backend' import { TouchBackend } from 'react-dnd-touch-backend' -import { TIMEOUT_LONG_PRESS_THOUGHT } from '../constants' const options = { backends: [ @@ -15,7 +14,6 @@ const options = { { id: 'touch', backend: TouchBackend, - options: { delayTouchStart: TIMEOUT_LONG_PRESS_THOUGHT }, preview: true, transition: TouchTransition, }, diff --git a/src/components/Editable.tsx b/src/components/Editable.tsx index 26aedfddca6..be41635afd0 100644 --- a/src/components/Editable.tsx +++ b/src/components/Editable.tsx @@ -548,7 +548,7 @@ const Editable = ({ if ( disabled || // dragInProgress: not sure if this can happen, but I observed some glitchy behavior with the cursor moving when a drag and drop is completed so check dragInProgress to be safe - (isTouch && isSafari() && !globals.touching && !state.dragInProgress && !state.dragHold) + (!globals.touching && !state.dragInProgress && !state.dragHold && (!editingOrOnCursor || !isVisible)) ) { // do not set cursor on hidden thought e.preventDefault() @@ -560,7 +560,7 @@ const Editable = ({ if (state.showLetterCase) dispatch(toggleLetterCase({ value: false })) } else { - setCursorOnThought({ editing: editingOrOnCursor }) + setCursorOnThought() // When the the cursor is first set on a thought, prevent the default browser behavior to avoid activating edit mode. // Do not reset until the long tap is definitely over.