diff --git a/docs/general/contributing.md b/docs/general/contributing.md
index a364645ba..ce7935e26 100644
--- a/docs/general/contributing.md
+++ b/docs/general/contributing.md
@@ -101,6 +101,10 @@ To run integrations with [Playwright](https://playwright.dev/), first run `yarn
[Here's a helpful page](https://github.com/Microsoft/vscode/wiki/IME-Test) detailing how to test various input scenarios on Windows, Mac and Linux.
+## Android tests
+
+When making changes that might affect Android compatibility, you can perform the manual Android tests at [/examples/android-tests](https://slatejs.org/examples/android-tests).
+
## Publishing Releases
**Important**: When creating releases using Lerna with the instructions below, you will be given choices around how to increase version numbers. You should always use a `major`, `minor` or `patch` release and must never use a `prerelease`. If a prerelease is used, the root package will not link to the packages in the `packages` directory creating hard to diagnose issues.
diff --git a/package.json b/package.json
index 291463871..8580a62c2 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
"build:rollup": "rollup --config ./config/rollup/rollup.config.js --bundleConfigAsCjs",
"changesetversion": "yarn changeset version && yarn install && git add .",
"clean": "rimraf './packages/*/{dist,lib,node_modules}' './site/{.next,out}' --glob",
- "fix": "yarn fix:prettier && yarn fix:eslint",
+ "fix": "yarn tsc:examples && yarn fix:prettier && yarn fix:eslint",
"fix:eslint": "yarn lint:eslint --fix",
"fix:prettier": "yarn lint:prettier --write",
"lint": "yarn lint:typescript && yarn lint:eslint && yarn lint:prettier",
diff --git a/site/examples/js/android-tests.jsx b/site/examples/js/android-tests.jsx
new file mode 100644
index 000000000..314562f4a
--- /dev/null
+++ b/site/examples/js/android-tests.jsx
@@ -0,0 +1,234 @@
+import React, { useCallback, useEffect, useMemo, useState } from 'react'
+import { createEditor } from 'slate'
+import { withHistory } from 'slate-history'
+import { Editable, Slate, withReact } from 'slate-react'
+import { css } from '@emotion/css'
+
+const TEST_CASES = [
+ {
+ id: 'split-join',
+ name: 'Split/Join',
+ instructions:
+ 'Hit enter twice then backspace twice in the following places:\n- Before "before"\n- Between the two "d"s in "middle"\n- After "after"',
+ value: [
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'One ' },
+ { text: 'before', bold: true },
+ { text: ' two ' },
+ { text: 'middle', bold: true },
+ { text: ' three ' },
+ { text: 'after', bold: true },
+ { text: ' four' },
+ ],
+ },
+ ],
+ },
+ {
+ id: 'insert',
+ name: 'Insertion',
+ instructions:
+ 'Enter text below each line of instruction, including mis-spelling "wasnt"',
+ value: [
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'Type by tapping keys: ', bold: true },
+ { text: 'It wasnt me. No.' },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'Type using glide typing: ', bold: true },
+ { text: 'Yes Sam, I am.' },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'Type using voice input: ', bold: true },
+ { text: 'The quick brown fox jumps over the lazy dog' },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: 'Write any two words using an IME', bold: true }],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ ],
+ },
+ {
+ id: 'special',
+ name: 'Special',
+ instructions: 'Follow the instructions on each line',
+ value: [
+ {
+ type: 'paragraph',
+ children: [
+ {
+ text: 'Type "it is", move cursor to "i|t" and hit enter.',
+ bold: true,
+ },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ {
+ text: 'Move cursor to "mid|dle" and press space, backspace, space, backspace.',
+ bold: true,
+ },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: 'The middle word.' }],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ {
+ text: 'Place cursor in line below. Wait for caps on keyboard to show up. If not try again. Type "It me. No." and check it doesn\'t mangle on the last period.',
+ bold: true,
+ },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ ],
+ },
+ {
+ id: 'empty',
+ name: 'Empty',
+ instructions:
+ 'Type "hello world", press enter, "hi", press enter, "bye", and then backspace over everything',
+ value: [
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ ],
+ },
+ {
+ id: 'remove',
+ name: 'Remove',
+ instructions:
+ 'Select from ANCHOR to FOCUS and press backspace. Move cursor to end. Backspace over all remaining content.',
+ value: [
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'Go and ' },
+ { text: 'select', bold: true },
+ { text: ' from this ANCHOR and then' },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: 'go and select' }],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'to this FOCUS then press ' },
+ { text: 'backspace.', bold: true },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'After you have done that move selection to very end.' },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'Then try ' },
+ { text: 'backspacing', bold: true },
+ { text: ' over all remaining text.' },
+ ],
+ },
+ ],
+ },
+]
+const AndroidTestsExample = () => {
+ const [testId, setTestId] = useState(
+ () => window.location.hash.replace('#', '') || TEST_CASES[0].id
+ )
+ useEffect(() => {
+ window.history.replaceState({}, '', `#${testId}`)
+ }, [testId])
+ const testCase = TEST_CASES.find(({ id }) => id === testId)
+ if (!testCase) {
+ throw new Error(`Could not find test case '${testId}'`)
+ }
+ return (
+ <>
+
+
+
+ {testCase.instructions}
+
+
+
+ >
+ )
+}
+const TestCase = ({ value }) => {
+ const renderLeaf = useCallback(props => , [])
+ const editor = useMemo(() => withHistory(withReact(createEditor())), [])
+ return (
+
+
+
+ )
+}
+const Leaf = ({ attributes, children, leaf }) => {
+ if (leaf.bold) {
+ children = {children}
+ }
+ return {children}
+}
+export default AndroidTestsExample
diff --git a/site/examples/js/check-lists.jsx b/site/examples/js/check-lists.jsx
index 36084fc80..317ade149 100644
--- a/site/examples/js/check-lists.jsx
+++ b/site/examples/js/check-lists.jsx
@@ -1,22 +1,22 @@
-import React, { useMemo, useCallback } from 'react'
-import {
- Slate,
- Editable,
- withReact,
- useSlateStatic,
- useReadOnly,
- ReactEditor,
-} from 'slate-react'
+import { css } from '@emotion/css'
+import React, { useCallback, useMemo } from 'react'
import {
Editor,
- Transforms,
- Range,
Point,
- createEditor,
+ Range,
Element as SlateElement,
+ Transforms,
+ createEditor,
} from 'slate'
-import { css } from '@emotion/css'
import { withHistory } from 'slate-history'
+import {
+ Editable,
+ ReactEditor,
+ Slate,
+ useReadOnly,
+ useSlateStatic,
+ withReact,
+} from 'slate-react'
const initialValue = [
{
diff --git a/site/examples/ts/android-tests.tsx b/site/examples/ts/android-tests.tsx
new file mode 100644
index 000000000..94a009e8c
--- /dev/null
+++ b/site/examples/ts/android-tests.tsx
@@ -0,0 +1,254 @@
+import React, { useCallback, useEffect, useMemo, useState } from 'react'
+import { Descendant, createEditor } from 'slate'
+import { withHistory } from 'slate-history'
+import { Editable, RenderLeafProps, Slate, withReact } from 'slate-react'
+import { css } from '@emotion/css'
+
+interface AndroidTestCase {
+ id: string
+ name: string
+ instructions: string
+ value: Descendant[]
+}
+
+const TEST_CASES: AndroidTestCase[] = [
+ {
+ id: 'split-join',
+ name: 'Split/Join',
+ instructions:
+ 'Hit enter twice then backspace twice in the following places:\n- Before "before"\n- Between the two "d"s in "middle"\n- After "after"',
+ value: [
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'One ' },
+ { text: 'before', bold: true },
+ { text: ' two ' },
+ { text: 'middle', bold: true },
+ { text: ' three ' },
+ { text: 'after', bold: true },
+ { text: ' four' },
+ ],
+ },
+ ],
+ },
+ {
+ id: 'insert',
+ name: 'Insertion',
+ instructions:
+ 'Enter text below each line of instruction, including mis-spelling "wasnt"',
+ value: [
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'Type by tapping keys: ', bold: true },
+ { text: 'It wasnt me. No.' },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'Type using glide typing: ', bold: true },
+ { text: 'Yes Sam, I am.' },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'Type using voice input: ', bold: true },
+ { text: 'The quick brown fox jumps over the lazy dog' },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: 'Write any two words using an IME', bold: true }],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ ],
+ },
+ {
+ id: 'special',
+ name: 'Special',
+ instructions: 'Follow the instructions on each line',
+ value: [
+ {
+ type: 'paragraph',
+ children: [
+ {
+ text: 'Type "it is", move cursor to "i|t" and hit enter.',
+ bold: true,
+ },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ {
+ text: 'Move cursor to "mid|dle" and press space, backspace, space, backspace.',
+ bold: true,
+ },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: 'The middle word.' }],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ {
+ text: 'Place cursor in line below. Wait for caps on keyboard to show up. If not try again. Type "It me. No." and check it doesn\'t mangle on the last period.',
+ bold: true,
+ },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ ],
+ },
+ {
+ id: 'empty',
+ name: 'Empty',
+ instructions:
+ 'Type "hello world", press enter, "hi", press enter, "bye", and then backspace over everything',
+ value: [
+ {
+ type: 'paragraph',
+ children: [{ text: '' }],
+ },
+ ],
+ },
+ {
+ id: 'remove',
+ name: 'Remove',
+ instructions:
+ 'Select from ANCHOR to FOCUS and press backspace. Move cursor to end. Backspace over all remaining content.',
+ value: [
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'Go and ' },
+ { text: 'select', bold: true },
+ { text: ' from this ANCHOR and then' },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [{ text: 'go and select' }],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'to this FOCUS then press ' },
+ { text: 'backspace.', bold: true },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'After you have done that move selection to very end.' },
+ ],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ { text: 'Then try ' },
+ { text: 'backspacing', bold: true },
+ { text: ' over all remaining text.' },
+ ],
+ },
+ ],
+ },
+]
+
+const AndroidTestsExample = () => {
+ const [testId, setTestId] = useState(
+ () => window.location.hash.replace('#', '') || TEST_CASES[0].id
+ )
+
+ useEffect(() => {
+ window.history.replaceState({}, '', `#${testId}`)
+ }, [testId])
+
+ const testCase = TEST_CASES.find(({ id }) => id === testId)
+ if (!testCase) {
+ throw new Error(`Could not find test case '${testId}'`)
+ }
+
+ return (
+ <>
+
+
+
+ {testCase.instructions}
+
+
+
+ >
+ )
+}
+
+const TestCase = ({ value }: AndroidTestCase) => {
+ const renderLeaf = useCallback(
+ (props: RenderLeafProps) => ,
+ []
+ )
+
+ const editor = useMemo(() => withHistory(withReact(createEditor())), [])
+
+ return (
+
+
+
+ )
+}
+
+const Leaf = ({ attributes, children, leaf }: RenderLeafProps) => {
+ if (leaf.bold) {
+ children = {children}
+ }
+
+ return {children}
+}
+
+export default AndroidTestsExample
diff --git a/site/pages/examples/[example].tsx b/site/pages/examples/[example].tsx
index fbc7b0b44..eb0e44d7c 100644
--- a/site/pages/examples/[example].tsx
+++ b/site/pages/examples/[example].tsx
@@ -7,6 +7,7 @@ import { ErrorBoundary } from 'react-error-boundary'
import { Icon } from '../../examples/ts/components/index'
+import AndroidTests from '../../examples/ts/android-tests'
import CheckLists from '../../examples/ts/check-lists'
import CodeHighlighting from '../../examples/ts/code-highlighting'
import EditableVoids from '../../examples/ts/editable-voids'
@@ -33,13 +34,15 @@ import CustomPlaceholder from '../../examples/ts/custom-placeholder'
// node
import { getAllExamples } from '../api'
-type ExampleTuple = [string, React.ComponentType, string]
+type ExampleTuple = [name: string, component: React.ComponentType, path: string]
const EXAMPLES: ExampleTuple[] = [
+ ['Android Tests', AndroidTests, 'android-tests'],
['Checklists', CheckLists, 'check-lists'],
+ ['Code Highlighting', CodeHighlighting, 'code-highlighting'],
+ ['Custom Placeholder', CustomPlaceholder, 'custom-placeholder'],
['Editable Voids', EditableVoids, 'editable-voids'],
['Embeds', Embeds, 'embeds'],
- ['Code Highlighting', CodeHighlighting, 'code-highlighting'],
['Forced Layout', ForcedLayout, 'forced-layout'],
['Hovering Toolbar', HoveringToolbar, 'hovering-toolbar'],
['Huge Document', HugeDocument, 'huge-document'],
@@ -51,15 +54,20 @@ const EXAMPLES: ExampleTuple[] = [
['Paste HTML', PasteHtml, 'paste-html'],
['Plain Text', PlainText, 'plaintext'],
['Read-only', ReadOnly, 'read-only'],
+ ['Rendering in iframes', IFrames, 'iframe'],
['Rich Text', RichText, 'richtext'],
['Search Highlighting', SearchHighlighting, 'search-highlighting'],
['Shadow DOM', ShadowDOM, 'shadow-dom'],
['Styling', Styling, 'styling'],
['Tables', Tables, 'tables'],
- ['Rendering in iframes', IFrames, 'iframe'],
- ['Custom placeholder', CustomPlaceholder, 'custom-placeholder'],
]
+const HIDDEN_EXAMPLES = ['android-tests']
+
+const NON_HIDDEN_EXAMPLES = EXAMPLES.filter(
+ ([, , path]) => !HIDDEN_EXAMPLES.includes(path)
+)
+
const Header = (props: React.HTMLAttributes) => (
{
- {EXAMPLES.map(([n, , p]) => (
+ {NON_HIDDEN_EXAMPLES.map(([n, , p]) => (