Skip to content

Note: Prevent programmatic selection on tap #2904

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/@types/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ interface State {
multicursors: Index<Path>
/** NoteFocus is true if the caret is on the note. */
noteFocus: boolean
/** NoteOffset can be used to position the caret within a note. Setting it to null disables programmatic selection using selection.set. */
noteOffset: number | null
/**
* Temporarily stores updates that need to be persisted.
* Passed to Yjs and cleared on every action.
Expand Down
3 changes: 3 additions & 0 deletions src/actions/setCursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const setCursor = (
cursorHistoryPop,
editing,
noteFocus = false,
noteOffset = null,
offset,
path,
replaceContextViews,
Expand All @@ -45,6 +46,7 @@ const setCursor = (
cursorHistoryPop?: boolean
editing?: boolean | null
noteFocus?: boolean
noteOffset?: number | null
offset?: number | null
path: Path | null
replaceContextViews?: Index<boolean>
Expand Down Expand Up @@ -148,6 +150,7 @@ const setCursor = (
cursorOffset: updatedOffset,
expanded,
noteFocus,
noteOffset,
cursorInitialized: true,
...(!preserveMulticursor
? {
Expand Down
6 changes: 5 additions & 1 deletion src/actions/setNoteFocus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import State from '../@types/State'
import Thunk from '../@types/Thunk'
import headValue from '../util/headValue'

type NoteFocusType = { value: false; offset?: never } | { value: true; offset: number | null }

/** Sets state.noteFocus to true or false, indicating if the caret is on a note. Sets state.cursorOffset to the end of the thought when disabling note focus so the selection gets placed back correctly on the thought. */
const setNoteFocus = (state: State, { value }: { value: boolean }): State => ({
const setNoteFocus = (state: State, { value, offset }: NoteFocusType): State => ({
...state,
// set the cursor offset to the end of the cursor thought
// we cannot use state.editingValue since it is set to null when the Editable is blurred
...(!value && state.cursor ? { cursorOffset: headValue(state, state.cursor)?.length } : null),
noteFocus: value,
// clear the offset when the caret leaves a note
noteOffset: value ? offset : null,
// always enter edit mode when there is note focus
// it will be set in the Note's onFocus anyway, but set it here so that we are not as dependent on what happens there
...(value ? { editing: true } : null),
Expand Down
5 changes: 4 additions & 1 deletion src/actions/toggleNote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import setDescendant from '../actions/setDescendant'
import setNoteFocus from '../actions/setNoteFocus'
import attribute from '../selectors/attribute'
import findDescendant from '../selectors/findDescendant'
import getChildren from '../selectors/getChildren'
import head from '../util/head'
import reducerFlow from '../util/reducerFlow'

/** Toggle the caret between the cursor and its note. Set the selection to the end. If the note is empty, delete it. */
const toggleNote = (state: State) => {
const thoughtId = head(state.cursor!)
const hasNote = findDescendant(state, thoughtId, '=note')
const noteChildren = hasNote ? getChildren(state, hasNote) : []
const offset = noteChildren.length ? noteChildren[0].value.length : 0

return reducerFlow([
// create an empty note if it doesn't exist
Expand All @@ -26,7 +29,7 @@ const toggleNote = (state: State) => {
: null,

// toggle state.noteFocus, which will trigger the Editable and Note to re-render and set the selection appropriately
setNoteFocus({ value: !state.noteFocus }),
state.noteFocus ? setNoteFocus({ value: false }) : setNoteFocus({ value: true, offset }),
])(state)
}

Expand Down
8 changes: 5 additions & 3 deletions src/components/Note.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const Note = React.memo(

/** Gets the value of the note. Returns null if no note exists or if the context view is active. */
const note = useSelector(state => noteValue(state, thoughtId))
const noteOffset = useSelector(state => state.noteOffset)

/** Focus Handling with useFreshCallback. */
const onFocus = useFreshCallback(() => {
Expand All @@ -48,6 +49,7 @@ const Note = React.memo(
path,
cursorHistoryClear: true,
editing: true,
noteOffset: null,
noteFocus: true,
}),
)
Expand All @@ -56,13 +58,13 @@ const Note = React.memo(
// set the caret on the note if editing this thought and noteFocus is true
useEffect(() => {
// cursor must be true if note is focused
if (hasFocus) {
selection.set(noteRef.current!, { end: true })
if (hasFocus && noteOffset !== null) {
selection.set(noteRef.current!, { offset: noteOffset })
// deleting a note, then closing the keyboard, then creating a new note could result in lack of focus,
// perhaps related to iOS Safari's internal management of selection ranges and focus
if (isTouch && isSafari()) noteRef.current?.focus()
}
}, [hasFocus])
}, [hasFocus, noteOffset])

/** Handles note keyboard shortcuts. */
const onKeyDown = useCallback(
Expand Down
1 change: 1 addition & 0 deletions src/util/initialState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const initialState = (created: Timestamp = timestamp()) => {
modals: {},
multicursors: {},
noteFocus: false,
noteOffset: null,
recentlyEdited: {},
redoPatches: [],
resourceCache: {},
Expand Down
Loading