Skip to content

Instantly share code, notes, and snippets.

@zaydek
Created June 7, 2020 19:07
Show Gist options
  • Save zaydek/2c788ca0ce1516d64000178bf0d2bc3f to your computer and use it in GitHub Desktop.
Save zaydek/2c788ca0ce1516d64000178bf0d2bc3f to your computer and use it in GitHub Desktop.
onBeforeInput research -- onBeforeInput does not appear to be stable for int.l input; characters easily get mangled because there are _phantom_ positions for where composition starts (that do not have 1:1 correspondence with the cursor’s offsets) and is hard to maintain. Because browsers can (not tested, but probably) differ on the order of inpu…
// import App from "EditorApp/EditorApp"
// import React from "react"
// import ReactDOM from "react-dom"
//
// import "debug.css"
// import "stylesheets/prism/custom.css"
// import "stylesheets/tailwind/color-vars.css"
// import "stylesheets/tailwind/em-context.css"
// import "stylesheets/tailwind/tailwind.generated.css"
//
// // https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/src/serviceWorker.js#L131
// ;(() => {
// if ("serviceWorker" in navigator) {
// navigator.serviceWorker.ready
// .then(registration => {
// registration.unregister()
// })
// .catch(error => {
// console.error(error.message)
// })
// }
// })()
//
// ReactDOM.render(
// <App />,
// document.getElementById("root"),
// )
import React from "react"
import ReactDOM from "react-dom"
import "stylesheets/tailwind/tailwind.generated.css"
const App = () => {
const ref = React.useRef(null)
const compositionInProgressRef = React.useRef(false)
const compositionStartOffsetRef = React.useRef(0)
const compositionEndOffsetRef = React.useRef(0)
const [state, setState] = React.useState({
startOffset: 0,
endOffset: 0,
collapsed: true,
data: "hello",
})
React.useLayoutEffect(
React.useCallback(() => {
const selection = document.getSelection()
const rangeCount = selection && selection.rangeCount
ReactDOM.render(state.data, ref.current, () => {
if (!rangeCount) {
// No-op
return
}
const range = document.createRange()
range.setStart(ref.current.childNodes[0], state.startOffset)
range.collapse()
selection.removeAllRanges()
selection.addRange(range)
})
}, [state]),
[state.data],
)
return (
<div>
<div
ref={ref}
className="whitespace-pre focus:outline-none"
contentEditable
suppressContentEditableWarning
onSelect={e => {
const selection = document.getSelection()
if (!selection || !selection.rangeCount) {
// No-op; defer to end
} else {
const range = selection.getRangeAt(0)
setState({
...state,
startOffset: range.startOffset,
endOffset: range.endOffset,
})
setState(current => ({
...current,
collapsed: current.startOffset === current.endOffset,
}))
}
// console.log("onSelect")
}}
onKeyDown={e => {
let startOffset = state.startOffset
let endOffset = state.endOffset
switch (true) {
case ((e.ctrlKey && e.keyCode === 68) || e.key === "Delete"):
if (compositionInProgressRef.current) {
// No-op
return
}
e.preventDefault()
if (state.collapsed) {
let endRune = ""
if (startOffset < state.data.length) {
endRune = [...state.data.slice(startOffset)][0]
}
endOffset += endRune.length
}
setState({
...state,
data: state.data.slice(0, startOffset) + state.data.slice(endOffset),
startOffset: startOffset,
endOffset: startOffset,
})
return
case (e.key === "Backspace"):
if (compositionInProgressRef.current) {
// No-op
return
}
e.preventDefault()
if (state.collapsed) {
let endRune = ""
if (endOffset) {
endRune = [...state.data.slice(0, endOffset)].slice(-1)[0]
}
startOffset -= endRune.length
}
setState({
...state,
data: state.data.slice(0, startOffset) + state.data.slice(endOffset),
startOffset: startOffset,
endOffset: startOffset,
})
return
default:
// No-op
return
}
}}
onBeforeInput={e => {
console.log("onBeforeInput")
e.preventDefault()
const startOffset = !compositionInProgressRef.current ? state.startOffset :
compositionStartOffsetRef.current
const endOffset = !compositionInProgressRef.current ? state.endOffset :
compositionEndOffsetRef.current
setState({
...state,
data: state.data.slice(0, startOffset) + e.data + state.data.slice(endOffset),
startOffset: startOffset + e.data.length,
endOffset: startOffset + e.data.length,
})
// console.log("onBeforeInput", { data: e.data })
}}
// onCompositionEnd={e => {
// compositionInProgressRef.current = false
// }}
onInput={e => {
// if (!compositionInProgressRef.current) {
// return
// }
compositionInProgressRef.current = e.nativeEvent.isComposing
if (compositionInProgressRef.current) {
compositionStartOffsetRef.current = state.startOffset
compositionEndOffsetRef.current = state.endOffset
}
// setState({ ...state })
// console.log("onInput", { data: e.nativeEvent.data })
}}
/>
<pre style={{ fontSize: "0.875em", MozTabSize: 2, tabSize: 2 }}>
{JSON.stringify(state, null, "\t")}
</pre>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById("root"),
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment