1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-03-06 13:59:47 +01:00
Ian Storm Taylor 1d53c5b1ef
Add input tester example (#2068)
#### Is this adding or improving a _feature_ or fixing a _bug_?

Example.

#### What's the new behavior?

Adds a new example that is an input event logger, for more easily seeing which input/keyboard/selection events are firing when editing in a Slate editor.

![image](https://user-images.githubusercontent.com/311752/43937175-ea9231e2-9c10-11e8-8e29-2cd9a24ca78d.png)

#### Have you checked that...?

* [x] The new code matches the existing patterns and styles.
* [x] The tests pass with `yarn test`.
* [x] The linter passes with `yarn lint`. (Fix errors with `yarn prettier`.)
* [x] The relevant examples still work. (Run examples with `yarn watch`.)
2018-08-09 20:30:23 -07:00

365 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Editor, findRange } from 'slate-react'
import { Value } from 'slate'
import React from 'react'
import styled from 'react-emotion'
import initialValue from './value.json'
import { Icon } from '../components'
import { createArrayValue } from 'react-values'
const EventsValue = createArrayValue()
const Wrapper = styled('div')`
position: relative;
`
const EventsWrapper = styled('div')`
position: fixed;
left: 0;
bottom: 0;
right: 0;
max-height: 40vh;
height: 500px;
overflow: auto;
border-top: 1px solid #ccc;
background: white;
`
const EventsTable = styled('table')`
font-family: monospace;
font-size: 0.9em;
border-collapse: collapse;
border: none;
min-width: 100%;
& > * + * {
margin-top: 1px;
}
tr,
th,
td {
border: none;
}
th,
td {
text-align: left;
padding: 0.333em;
}
th {
position: sticky;
top: 0;
background-color: #eee;
border-bottom: 1px solid #ccc;
}
td {
background-color: white;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
}
`
const Pill = styled('span')`
display: inline-block;
padding: 0.25em 0.33em;
border-radius: 4px;
background-color: ${p => p.color};
`
const I = styled(Icon)`
font-size: 0.9em;
color: ${p => p.color};
`
const MissingCell = props => <I color="silver">texture</I>
const TypeCell = ({ event }) => {
switch (event.constructor.name) {
case 'CompositionEvent':
return <Pill color="thistle">{event.type}</Pill>
case 'InputEvent':
return <Pill color="lightskyblue">{event.type}</Pill>
case 'KeyboardEvent':
return <Pill color="wheat">{event.type}</Pill>
case 'Event':
return <Pill color="#ddd">{event.type}</Pill>
default:
return <Pill color="palegreen">{event.type}</Pill>
}
}
const BooleanCell = ({ value }) =>
value === true ? (
<I color="mediumseagreen">check</I>
) : value === false ? (
<I color="tomato">clear</I>
) : (
<MissingCell />
)
const StringCell = ({ value }) =>
value == null ? <MissingCell /> : JSON.stringify(value)
const RangeCell = ({ value }) =>
value == null ? (
<MissingCell />
) : (
`${value.anchor.path.toJSON()}.${
value.anchor.offset
}${value.focus.path.toJSON()}.${value.focus.offset}`
)
const EventsList = () => (
<EventsValue>
{({ value: events }) => (
<EventsWrapper>
<EventsTable>
<thead>
<tr>
<th>
<I
color="#666"
style={{ cursor: 'pointer' }}
onMouseDown={e => {
e.preventDefault()
EventsValue.clear()
}}
>
block
</I>
</th>
<th>type</th>
<th>key</th>
<th>code</th>
<th>repeat</th>
<th>inputType</th>
<th>data</th>
<th>dataTransfer</th>
<th>targetRange</th>
<th>isComposing</th>
<th>findSelection</th>
</tr>
</thead>
<tbody>
{events.map((props, i) => <Event key={i} {...props} />)}
</tbody>
</EventsTable>
</EventsWrapper>
)}
</EventsValue>
)
const Event = ({ event, targetRange, selection }) => {
return (
<tr>
<td />
<td>
<TypeCell event={event} />
</td>
<td>
<StringCell value={event.key} />
</td>
<td>
<StringCell value={event.code} />
</td>
<td>
<BooleanCell value={event.repeat} />
</td>
<td>
<StringCell value={event.inputType} />
</td>
<td>
<StringCell value={event.data} />
</td>
<td>
<StringCell
value={event.dataTransfer && event.dataTransfer.get('text/plain')}
/>
</td>
<td>
<RangeCell value={targetRange} />
</td>
<td>
<BooleanCell value={event.isComposing} />
</td>
<td>
<RangeCell value={selection} />
</td>
</tr>
)
}
class InputTester extends React.Component {
state = {
value: Value.fromJSON(initialValue),
}
componentDidMount() {
const editor = this.el.querySelector('[contenteditable="true"]')
editor.addEventListener('keydown', this.onEvent)
editor.addEventListener('keyup', this.onEvent)
editor.addEventListener('keypress', this.onEvent)
editor.addEventListener('input', this.onEvent)
editor.addEventListener('beforeinput', this.onEvent)
editor.addEventListener('compositionstart', this.onEvent)
editor.addEventListener('compositionupdate', this.onEvent)
editor.addEventListener('compositionend', this.onEvent)
window.document.addEventListener('selectionchange', this.onEvent)
}
render() {
return (
<Wrapper innerRef={this.onRef}>
<Editor
spellCheck
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
renderNode={({ attributes, children, node }) => {
switch (node.type) {
case 'block-quote':
return <blockquote {...attributes}>{children}</blockquote>
case 'bulleted-list':
return <ul {...attributes}>{children}</ul>
case 'heading-one':
return <h1 {...attributes}>{children}</h1>
case 'heading-two':
return <h2 {...attributes}>{children}</h2>
case 'list-item':
return <li {...attributes}>{children}</li>
case 'numbered-list':
return <ol {...attributes}>{children}</ol>
}
}}
renderMark={({ attributes, children, mark }) => {
switch (mark.type) {
case 'bold':
return <strong {...attributes}>{children}</strong>
case 'code':
return <code {...attributes}>{children}</code>
case 'italic':
return <em {...attributes}>{children}</em>
case 'underlined':
return <u {...attributes}>{children}</u>
}
}}
/>
<EventsList />
</Wrapper>
)
}
onRef = ref => {
this.el = ref
}
onChange = ({ value }) => {
this.setState({ value })
this.recordEvent({ type: 'change' })
this.logEvent({ type: 'change' })
}
onEvent = event => {
this.recordEvent(event)
this.logEvent(event)
}
recordEvent = event => {
const { value } = this.state
let targetRange
if (event.getTargetRanges) {
const [nativeTargetRange] = event.getTargetRanges()
targetRange = nativeTargetRange && findRange(nativeTargetRange, value)
}
const nativeSelection = window.getSelection()
const nativeRange = nativeSelection.rangeCount
? nativeSelection.getRangeAt(0)
: undefined
const selection = nativeRange && findRange(nativeRange, value)
EventsValue.push({
event,
value,
targetRange,
selection,
})
}
logEvent = event => {
const { value } = this.state
const nativeSelection = window.getSelection()
const nativeRange = nativeSelection.rangeCount
? nativeSelection.getRangeAt(0)
: undefined
const selection = nativeRange && findRange(nativeRange, value)
const {
type,
key,
code,
inputType,
data,
dataTransfer,
isComposing,
} = event
const prefix = `%c${type.padEnd(15)}`
let style = 'padding: 3px'
let details
switch (event.constructor.name) {
case 'CompositionEvent': {
style += '; background-color: thistle'
details = { data, selection, value }
break
}
case 'InputEvent': {
style += '; background-color: lightskyblue'
const [nativeTargetRange] = event.getTargetRanges()
const targetRange =
nativeTargetRange && findRange(nativeTargetRange, value)
details = {
inputType,
data,
dataTransfer,
targetRange,
isComposing,
selection,
value,
}
break
}
case 'KeyboardEvent': {
style += '; background-color: wheat'
details = { key, code, isComposing, selection, value }
break
}
case 'Event': {
style += '; background-color: #ddd'
details = { isComposing, selection, value }
break
}
default: {
style += '; background-color: palegreen'
details = { selection, value }
break
}
}
console.log(prefix, style, details) // eslint-disable-line no-console
}
}
export default InputTester