` paragraph will remove the blockquote and apply the heading. -- **Blocking** - a style that is blocked cannot be toggled at all while the blocking style is active. For example: `` is blocked inside ``, so the bold cannot be applied where codeblock is active. - -These states are reported via the [onChangeState](docs/INPUT_API_REFERENCE.md#onchangestate) event (`isConflicting` and `isBlocking` properties). - -### Inline tags - -| Style | HTML tag | Conflicts with | Blocked by | -| ------------- | ----------- | ---------------------------- | ---------------------- | -| Bold | `` | -- | ` ` | -| Italic | `` | -- | ` ` | -| Underline | `` | -- | ` ` | -| Strikethrough | ` ` | -- | `` | -| Inline code | ` ` | ``, `` | ` `, ` ` | -| Link | `` | `
`, ``, `` | ` `, ` ` | -| Mention | `
` | ` `, `` | ``, ` ` | -| Image | `
` | ``, `
` | ` ` | - -> [!NOTE] -> Headings also block bold when `bold: true` is set on the heading style in the [htmlStyle](docs/INPUT_API_REFERENCE.md#htmlstyle) prop. In that case, the heading itself renders as bold, so toggling bold on top of it is redundant and therefore blocked. - -### Paragraph tags - -Some paragraph styles are container elements that wrap each line of text inside them with an **inner content tag**. For example: each line inside `` is wrapped in `
- ` and each line inside `
` is wrapped in ` `. - -Only one paragraph-level style can be active per paragraph - all paragraph styles conflict with each other. - -| Style | HTML tag | Inner content tag | Conflicts with | Blocked by | -| -------------- | --------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| Heading 1 | `
` | -- | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `` | -- | -| Heading 2 | ` ` | -- | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `` | -- | -| Heading 3 | ` ` | -- | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `` | -- | -| Heading 4 | ` ` | -- | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `` | -- | -| Heading 5 | ` ` | -- | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `` | -- | -| Heading 6 | ` ` | -- | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `` | -- | -| Unordered list | ` ` | `
- ` | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `` | -- | -| Ordered list | ` ` | `
- ` | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `` | -- | -| Checkbox list | ` ` | `
- ` / `
- ` | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `` | -- | -| Blockquote | ` ` | `` | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
` | -- | -| Codeblock | ` ` | ` ` | `
`, `
`, `
`, `
`, `
`, `
`, ``, ``, ``, `
`, ``, `
`, `
`, `
`, ``, ``, `` | -- | - -Plain text paragraphs are wrapped in ` ` tags. Empty paragraphs are represented as `
`. - -## Non Parametrized Styles - -Supported styles: - -- bold -- italic -- underline -- strikethrough -- inline code -- H1, H2, H3, H4, H5 and H6 headings -- codeblock -- blockquote -- ordered list -- unordered list -- checkbox list - -Each of the styles can be toggled the same way as in the example from [usage section](#usage); call a proper `toggle` function on the component ref. - -Each call toggles the style within the current text selection. We can still divide styles into two categories based on how they treat the selection: +Check out the detailed [installation instructions](https://enriched.swmansion.com/html/). -- Inline styles (bold, italic, underline, strikethrough, inline code). They are being toggled on exactly the character range that is currently selected. When toggling the style with just the cursor in place (no selection), the style is ready to be used and will be applied to the next characters that the user inputs. +## Compatibility -- Paragraph styles (headings, codeblock, blockquote, lists). They are being toggled on the entire paragraph that the selection is in. By paragraph, we mean a part of the text between two newlines (enters) or the text's beginning/ending. - If the selection spans more than one paragraph, logically more of them will be affected by the toggle. Toggling these styles with the cursor in place (no selection) makes changes to the very paragraph the cursor is in. +At the moment, the library offers stable support for iOS, Android and Web. -## Links +The full list of compatible React Native versions is available [here](https://enriched.swmansion.com/html/). -The links are here, just like in any other editor, a piece of text with a URL attributed to it. They can be added in two ways: automatically or manually. +## Documentation -### Automatic links detection +To find more about `react-native-enriched-html` including usage guides, API references, known limitations or our contributing guide, visit the official [documentation](https://enriched.swmansion.com/html/). -`react-native-enriched-html` automatically detects words that appear to be some URLs and makes them links. -You can customize this behavior by providing your own regular expression via [linkRegex](docs/INPUT_API_REFERENCE.md#linkregex) prop. +## Examples -### Applying links manually +The source code for the example mobile and web apps is under the [`apps`](https://github.com/software-mansion/react-native-enriched-html/tree/main/apps) directory. +You can also see the live preview of the library's capabilities by visiting the [official documentation](https://enriched.swmansion.com/html/) filled with interactive examples. -Links can also be added by calling [`setLink`](docs/INPUT_API_REFERENCE.md#setlink) method on the input ref: - -The `start`, `end` and `text` arguments for the method can be easily taken from [onChangeSelection](docs/INPUT_API_REFERENCE.md#onchangeselection) event payload as it returns exact `start` and `end` of the selection and the `text` it spans. This way, you just set the underlying URL to whatever is selected in there. - -Passing a different `text` than the one in the selection will properly replace it before applying the link. - -A complete example of a setup that supports both setting links on the selected text, as well as putting them in the place of cursor and editing existing links can be found in the example app code. - -## Mentions - -Mentions are meant to be a customisable style that lets you put mentioning phrases in the input, e.g. `@someone` or `#some_channel` or `[any_character_you_like]something`. - -### Mention Indicators - -There is a [mentionIndicators](docs/INPUT_API_REFERENCE.md#mentionindicators) prop that lets you define what characters can start a mention. By default, it is set to `[ @ ]`, meaning that typing a `@` character in the input will start the creation of a mention. - -### Starting a mention - -There are two ways in which a mention can be started; either by typing one of the `mentionIndicators` set or by calling a [startMention](docs/INPUT_API_REFERENCE.md#startmention) method on the input ref. - -### Mention related events - -`react-native-enriched-html` emits 3 different events that help handling mentions' editing: - -- [onStartMention](docs/INPUT_API_REFERENCE.md#onstartmention) is emitted whenever mention is started in one of the ways from the [previous section](#starting-a-mention) or the user has come back (moved selection) to some unfinished mention they have started. It can be used for opening proper tools you use in the app to edit a mention (e.g. a list for choosing from users or channels that the mention will affect). -- [onChangeMention](docs/INPUT_API_REFERENCE.md#onchangemention) is emitted whenever user put or removed some characters after a mention indicator. This way you can react to active mention editing by, for example, filtering users in your displayed list based on the typed text. -- [onEndMention](docs/INPUT_API_REFERENCE.md#onendmention) is emitted whenever user is no longer editing a mention: they might have put a space or changed the cursor position to be no longer near the indicator. You can use it to hide appropriate tools that were used for mention editing. - -### Setting a mention - -Whenever you feel ready with the currently edited mention (so most likely user chooses something from your additional mention editor), you can complete it by calling [setMention](docs/INPUT_API_REFERENCE.md#setmention) ref method. - -## Inline images - -You can insert an image into the input using [setImage](docs/INPUT_API_REFERENCE.md#setimage) ref method. - -The image will be put into a single line in the input and will affect the line's height as well as input's height. Keep in mind, that image will replace currently selected text or insert into the cursor position if there is no text selection. - -## Style Detection - -All of the above styles can be detected with the use of [onChangeState](docs/INPUT_API_REFERENCE.md#onchangestate) event payload. - -You can find some examples in the [usage section](#usage) or in the example app. - -## Other Events - -`react-native-enriched-html` emits a few more events that may be of use: - -- [onFocus](docs/INPUT_API_REFERENCE.md#onfocus) - emits whenever input focuses. -- [onBlur](docs/INPUT_API_REFERENCE.md) - emits whenever input blurs. -- [onChangeText](docs/INPUT_API_REFERENCE.md#onchangetext) - returns the input's text anytime it changes. -- [onChangeHtml](docs/INPUT_API_REFERENCE.md#onchangehtml) - returns HTML string parsed from current input text and styles anytime it would change. As parsing the HTML on each input change is a pretty expensive operation, not assigning the event's callback will speed up iOS input a bit. -- [onChangeSelection](docs/INPUT_API_REFERENCE.md#onchangeselection) - returns all the data needed for working with selections (as of now it's mainly useful for [links](#links)). -- [onLinkDetected](docs/INPUT_API_REFERENCE.md#onlinkdetected) - returns link's detailed info whenever user selection is near one. -- [onMentionDetected](docs/INPUT_API_REFERENCE.md#onmentiondetected) - returns mention's detailed info whenever user selection is near one. -- [onKeyPress](docs/INPUT_API_REFERENCE.md#onkeypress) - emits whenever a key is pressed. Follows react-native TextInput's onKeyPress event [spec](https://reactnative.dev/docs/textinput#onkeypress). -- [onPasteImages](docs/INPUT_API_REFERENCE.md#onpasteimages) - returns an array of images details whenever an image/GIF is pasted into the input. - -## Context Menu Items - -> **Note:** This feature is currently supported on Android and iOS 16+. - -You can extend the native text editing menu with custom items using the [contextMenuItems](docs/INPUT_API_REFERENCE.md#contextmenuitems) prop. Each item has a `text` (title), `visible` flag and an `onPress` callback. Items appear in the specified order, before the system actions. - -```tsx -{ - if (!styleState.link.isBlocking) { - ref.current?.setLink(selection.start, selection.end, text, url); - } - }, - visible: true, - }, - ]} -/> -``` - -## Customizing \ styles - -`react-native-enriched-html` allows customizing styles of the ` ` component. See [htmlStyle](docs/INPUT_API_REFERENCE.md#htmlstyle) prop. - -## EnrichedText component - -`react-native-enriched-html` provides an `EnrichedText` component that renders the HTML output of `EnrichedTextInput` with all supported styles, interactive links, and mentions. - -```tsx -import { EnrichedText } from 'react-native-enriched-html'; -import type { - OnLinkPressEvent, - OnMentionPressEvent, -} from 'react-native-enriched-html'; -import { StyleSheet } from 'react-native'; - -export default function App() { - const html = - ' Hello, world! Visit Software Mansion.
'; - - const handleLinkPress = (e: OnLinkPressEvent) => { - console.log('Link pressed:', e.url); - }; - - const handleMentionPress = (e: OnMentionPressEvent) => { - console.log('Mention pressed:', e.text, e.indicator, e.attributes); - }; - - return ( -- {html} - - ); -} - -const styles = StyleSheet.create({ - text: { - fontSize: 16, - color: 'black', - }, -}); -``` - -## API Reference - -See the [EnrichedTextInput API Reference](docs/INPUT_API_REFERENCE.md) for a detailed overview of all the props, methods, and events available for `EnrichedTextInput`. - -See the [EnrichedText API Reference](docs/TEXT_API_REFERENCE.md) for the `EnrichedText` component. - -## Known limitations +## License -- Only one level of lists is supported. We currently do not support nested lists. +`react-native-enriched-html` library is licensed under [The MIT License](./LICENSE). -## Contributing +## Community Discord -See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. +[Join the Software Mansion Community Discord](https://discord.swmansion.com) to chat about `react-native-enriched-html` or other Software Mansion libraries. -## License +## Created by Software Mansion -`react-native-enriched-html` library is licensed under [The MIT License](./LICENSE). +Since 2012 [Software Mansion](https://swmansion.com) is a software agency with experience in building web and mobile apps. We are Core React Native Contributors and experts in dealing with all kinds of React Native issues. +We can help you build your next dream product – +[Hire us](https://swmansion.com/contact/projects?utm_source=react-native-enriched-html&utm_medium=readme). --- @@ -392,6 +58,4 @@ Built by [Software Mansion](https://swmansion.com/) and sponsored by [Filament]( [](https://swmansion.com/) -
- [
](https://filament.dm/) diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..694be6078 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,23 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Yarn +.yarn/ + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/docs/.prettierrc.js b/docs/.prettierrc.js new file mode 100644 index 000000000..2ae7b381e --- /dev/null +++ b/docs/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + arrowParens: 'avoid', + bracketSameLine: true, + bracketSpacing: true, + singleQuote: true, + trailingComma: 'all', +}; diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md new file mode 100644 index 000000000..f4881d461 --- /dev/null +++ b/docs/CLAUDE.md @@ -0,0 +1,56 @@ +# React Native Enriched HTML docs site + +Docusaurus site for React Native Enriched HTML, scaffolded from +[react-native-screens-docs](https://github.com/software-mansion/react-native-screens-docs). +Deployed to `https://docs.swmansion.com/react-native-enriched-html/` via GitHub Pages. + +## Working in this folder + +- `yarn && yarn start` runs the dev server. `yarn build` must pass before + merging; broken links and anchors fail the build (`onBrokenLinks: throw`). +- This folder is a standalone yarn project with its own `yarn.lock`, + separate from the repo workspace. Repo-level eslint and tsc exclude it. +- Never commit `build/`, `.docusaurus/`, or `node_modules/`. + +## Content + +- Pages live in `docs/`, navigation in `sidebars.js`. Docs are served at the + site root (`routeBasePath: '/'`); `docs/fundamentals/intro.md` has `slug: /` + and is the homepage. There is no landing page - the product landing is + https://enriched.swmansion.com. +- Filenames are kebab-case, including component reference pages. +- The current structure (Fundamentals / Guides / API Reference) is a default; + restructure freely, keeping `sidebars.js` in sync. + +## MDX components + +Registered in `src/theme/MDXComponents.js`, same conventions as the +react-native-reanimated docs (without interactive examples): +`PlatformCompatibility`, `CollapsibleCode`, `Optional`, `Required`, `Yes`, `No`, +`Version`, `Spacer`, `Row`, `Grid`, `Indent`, `ExampleVideo`, `ThemedVideo`, +`Badges` (t-rex-ui), plus restyled admonitions, `
`, Tabs, diff and +highlighted code blocks, and Mermaid diagrams. + +## Site configuration (`docusaurus.config.js`) + +- Version label: `presets` > `docs.versions.current.label` (now `1.x`). +- `static/robots.txt` allows indexing and declares the generated sitemap. +- Google Tag Manager is commented out in `plugins`; add a container id to + enable analytics. +- Algolia DocSearch credentials are placeholders. Apply for DocSearch only + after the site is public, indexable, and on its final URL. + +## Deploy + +`.github/workflows/docs-publish.yml` deploys to the `gh-pages` branch on +every push to `main` that touches `docs/**`. GitHub Pages must be enabled in +the repo settings (serve from `gh-pages`). `docs-build.yml` builds on PRs. + +## Theme gotchas + +- The theme is `@swmansion/t-rex-ui`. It treats the page at the site root as + a "landing page": `navbar.hideOnScroll` must stay `false` and the + `navbarLanding` override in `src/css/overrides.css` must stay, otherwise + the sidebar covers the navbar logo on the homepage. +- Navbar logos are `static/img/logo.svg` / `logo-dark.svg` (from the + enriched landing repo branding). diff --git a/docs/ENRICHED_INPUT_STYLE.md b/docs/ENRICHED_INPUT_STYLE.md deleted file mode 100644 index dc65a5280..000000000 --- a/docs/ENRICHED_INPUT_STYLE.md +++ /dev/null @@ -1,171 +0,0 @@ -# EnrichedInputStyle - -Defines the [`style`](API_REFERENCE.md#style) prop’s shape. This type is a subset of React Native’s [TextStyle](https://reactnative.dev/docs/text-style-props), meaning some properties are not supported by this component (e.g. `textAlign`, `textDecorationLine`, `justifyContent`). Certain properties are platform-specific and include an `@platform` directive indicating where they are supported. - - -```ts -export interface EnrichedInputStyle { - // Layout / FlexStyle - alignSelf?: TextStyle['alignSelf']; - aspectRatio?: number | string; - borderBottomWidth?: number; - borderEndWidth?: number; - borderLeftWidth?: number; - borderRightWidth?: number; - borderStartWidth?: number; - borderTopWidth?: number; - borderWidth?: number; - bottom?: DimensionValue; - boxSizing?: TextStyle['boxSizing']; - display?: TextStyle['display']; - end?: DimensionValue; - flex?: number; - flexBasis?: DimensionValue; - flexGrow?: number; - flexShrink?: number; - height?: DimensionValue; - inset?: DimensionValue; - insetBlock?: DimensionValue; - insetBlockEnd?: DimensionValue; - insetBlockStart?: DimensionValue; - insetInline?: DimensionValue; - insetInlineEnd?: DimensionValue; - insetInlineStart?: DimensionValue; - left?: DimensionValue; - margin?: DimensionValue; - marginBlock?: DimensionValue; - marginBlockEnd?: DimensionValue; - marginBlockStart?: DimensionValue; - marginBottom?: DimensionValue; - marginEnd?: DimensionValue; - marginHorizontal?: DimensionValue; - marginInline?: DimensionValue; - marginInlineEnd?: DimensionValue; - marginInlineStart?: DimensionValue; - marginLeft?: DimensionValue; - marginRight?: DimensionValue; - marginStart?: DimensionValue; - marginTop?: DimensionValue; - marginVertical?: DimensionValue; - maxHeight?: DimensionValue; - maxWidth?: DimensionValue; - minHeight?: DimensionValue; - minWidth?: DimensionValue; - padding?: DimensionValue; - paddingBlock?: DimensionValue; - paddingBlockEnd?: DimensionValue; - paddingBlockStart?: DimensionValue; - paddingBottom?: DimensionValue; - paddingEnd?: DimensionValue; - paddingHorizontal?: DimensionValue; - paddingInline?: DimensionValue; - paddingInlineEnd?: DimensionValue; - paddingInlineStart?: DimensionValue; - paddingLeft?: DimensionValue; - paddingRight?: DimensionValue; - paddingStart?: DimensionValue; - paddingTop?: DimensionValue; - paddingVertical?: DimensionValue; - position?: TextStyle['position']; - right?: DimensionValue; - start?: DimensionValue; - top?: DimensionValue; - width?: DimensionValue; - zIndex?: number; - - // Shadows - /** @platform ios */ - shadowColor?: ColorValue; - /** @platform ios */ - shadowOffset?: TextStyle['shadowOffset']; - /** @platform ios */ - shadowOpacity?: TextStyle['shadowOpacity']; - /** @platform ios */ - shadowRadius?: number; - - // Transforms - transform?: TextStyle['transform']; - transformOrigin?: TextStyle['transformOrigin']; - - // View appearance - /** @platform ios web */ - backfaceVisibility?: TextStyle['backfaceVisibility']; - backgroundColor?: ColorValue; - /** @platform ios web */ - borderBlockColor?: ColorValue; - /** @platform ios web */ - borderBlockEndColor?: ColorValue; - /** @platform ios web */ - borderBlockStartColor?: ColorValue; - /** @platform ios web */ - borderBottomColor?: ColorValue; - /** @platform ios web */ - borderBottomEndRadius?: TextStyle['borderBottomEndRadius']; - /** @platform ios web */ - borderBottomLeftRadius?: TextStyle['borderBottomLeftRadius']; - /** @platform ios web */ - borderBottomRightRadius?: TextStyle['borderBottomRightRadius']; - /** @platform ios web */ - borderBottomStartRadius?: TextStyle['borderBottomStartRadius']; - /** @platform ios web */ - borderColor?: ColorValue; - /** @platform ios web */ - borderEndColor?: ColorValue; - /** @platform ios web */ - borderEndEndRadius?: TextStyle['borderEndEndRadius']; - /** @platform ios web */ - borderEndStartRadius?: TextStyle['borderEndStartRadius']; - /** @platform ios web */ - borderLeftColor?: ColorValue; - /** @platform ios web */ - borderRadius?: TextStyle['borderRadius']; - /** @platform ios web */ - borderRightColor?: ColorValue; - /** @platform ios web */ - borderStartColor?: ColorValue; - /** @platform ios web */ - borderStartEndRadius?: TextStyle['borderStartEndRadius']; - /** @platform ios web */ - borderStartStartRadius?: TextStyle['borderStartStartRadius']; - /** @platform ios web */ - borderStyle?: TextStyle['borderStyle']; - /** @platform ios web */ - borderTopColor?: ColorValue; - /** @platform ios web */ - borderTopEndRadius?: TextStyle['borderTopEndRadius']; - /** @platform ios web */ - borderTopLeftRadius?: TextStyle['borderTopLeftRadius']; - /** @platform ios web */ - borderTopRightRadius?: TextStyle['borderTopRightRadius']; - /** @platform ios web */ - borderTopStartRadius?: TextStyle['borderTopStartRadius']; - boxShadow?: TextStyle['boxShadow']; - /** @platform web */ - cursor?: TextStyle['cursor']; - /** @platform android */ - elevation?: number; - /** @platform android web */ - filter?: TextStyle['filter']; - /** @platform android web */ - mixBlendMode?: TextStyle['mixBlendMode']; - opacity?: TextStyle['opacity']; - /** @platform ios web */ - outlineColor?: ColorValue; - outlineOffset?: TextStyle['outlineOffset']; - /** @platform android web */ - outlineStyle?: TextStyle['outlineStyle']; - outlineWidth?: TextStyle['outlineWidth']; - /** @platform ios web */ - pointerEvents?: TextStyle['pointerEvents']; - - // Typography - color?: ColorValue; - fontFamily?: string; - fontSize?: number; - fontStyle?: TextStyle['fontStyle']; - fontWeight?: TextStyle['fontWeight']; - lineHeight?: number; - /** @platform web */ - letterSpacing?: number; -} -``` diff --git a/docs/INPUT_API_REFERENCE.md b/docs/INPUT_API_REFERENCE.md deleted file mode 100644 index 12d1341b3..000000000 --- a/docs/INPUT_API_REFERENCE.md +++ /dev/null @@ -1,1073 +0,0 @@ -# API Reference - -## Props - -### `allowFontScaling` - -If `true`, the input respects the system's accessibility font scaling settings. - -| Type | Default Value | Platform | -| ------ | ------------- | ------------ | -| `bool` | `true` | iOS, Android | - -### `autoFocus` - -If `true`, focuses the input. - -| Type | Default Value | Platform | -| ------ | ------------- | ----------------- | -| `bool` | `false` | iOS, Android, Web | - -### `autoCapitalize` - -Tells input to automatically capitalize certain characters. - -- `characters`: all characters. -- `words`: first letter of each word. -- `sentences`: first letter of each sentence. -- `none`: don't auto capitalize anything. - -| Type | Default Value | Platform | -| -------------------------------------------------- | ------------- | ----------------- | -| `'none' \| 'sentences' \| 'words' \| 'characters'` | `'sentences'` | iOS, Android, Web | - -### `contextMenuItems` - -An array of custom items to display in the native text editing menu. Each item specifies a title, visibility flag, and a callback that fires when the item is tapped. - -The `onPress` callback receives a single object argument with the following properties: - -- `text` - the currently selected text. -- `selection` - an object with `start` and `end` indices of the current selection. -- `styleState` - the latest `OnChangeStateEvent` payload reflecting active styles at the time of the tap. - -Item type: - -```ts -interface ContextMenuItem { - text: string; - onPress: (args: { - text: string; - selection: { start: number; end: number }; - styleState: OnChangeStateEvent; - }) => void; - visible?: boolean; -} -``` - -- `text` is the title displayed in the menu. -- `onPress` is the callback invoked when the item is tapped. -- `visible` controls whether the item is shown. Defaults to `true`. - -| Type | Default Value | Platform | -| ------------------- | ------------- | ------------ | -| `ContextMenuItem[]` | [] | iOS, Android | - -> [!NOTE] -> On iOS items appear in array order, before the system items (Copy/Paste/Cut). -> On Android, there is no guaranteed order and custom items may be displayed in a submenu, depending on the device manufacturer. - -### `cursorColor` - -When provided it will set the color of the cursor (or "caret") in the component. - -| Type | Default Value | Platform | -| ---------------------------------------------- | -------------- | ------------ | -| [`color`](https://reactnative.dev/docs/colors) | system default | Android, Web | - -### `defaultValue` - -Provides an initial value for the input. If the string is a valid HTML output of the `EnrichedTextInput` component (or other HTML that the parser will accept), proper styles will be applied. - -| Type | Default Value | Platform | -| -------- | ------------- | ----------------- | -| `string` | - | iOS, Android, Web | - -### `editable` - -If `false`, text is not editable. - -| Type | Default Value | Platform | -| ------ | ------------- | ----------------- | -| `bool` | `true` | iOS, Android, Web | - -> [!NOTE] -> Setting `editable` to `false` will disable all user interactions with the input. -> However, some programmatic changes (like toggling styles or changing value imperatively) via ref methods will still work. - -### `htmlStyle` - -A prop for customizing styles appearances. - -| Type | Default Value | Platform | -| ------------------------------ | -------------------------------------------------- | ----------------- | -| [`HtmlStyle`](#htmlstyle-type) | default values from [`HtmlStyle`](#htmlstyle-type) | iOS, Android, Web | - -### `mentionIndicators` - -The recognized mention indicators. Each item needs to be a 1 character long string. - -| Type | Default Value | Platform | -| ----------------- | ------------- | ----------------- | -| array of `string` | `['@']` | iOS, Android, Web | - -### `linkRegex` - -A custom regex pattern for detecting links in the input. If not provided, a default regex will be used. -With this approach you can customize what patterns should be recognized as links, for example you can make it so that only links starting with `https://` are detected, or you can support custom schemes. -Keep in mind that not all JS regex features are supported, for example variable-width lookbehinds won't work. - -| Type | Default Value | Platform | -| -------- | ----------------------------- | ------------ | -| `RegExp` | default native platform regex | iOS, Android | - -> [!TIP] -> With this approach you can also disable link detection completely by providing a `null` value as the prop. - -### `onBlur` - -Callback that's called whenever the input loses focus (is blurred). - -| Type | Platform | -| ------------ | ----------------- | -| `() => void` | iOS, Android, Web | - -### `onChangeHtml` - -Callback that is called when input's HTML changes. - -Payload interface: - -```ts -interface OnChangeHtmlEvent { - value: string; -} -``` - -- `value` is the new HTML. - -| Type | Platform | -| ---------------------------------------------------------- | ----------------- | -| `(event: NativeSyntheticEvent) => void` | iOS, Android, Web | - -> [!TIP] -> Specifying `onChangeHtml` may have performance implications, especially with large documents, as it requires continuous HTML parsing. -> If you only need the HTML content at specific moments (e.g., when saving), consider using the [`getHTML`](#gethtml) ref method instead. -> When `onChangeHtml` is not provided, the component optimizes performance by avoiding unnecessary HTML parsing. - -### `onChangeMention` - -Callback that gets called anytime user makes some changes to a mention that is being edited. - -Payload interface: - -```ts -interface OnChangeMentionEvent { - indicator: string; - text: string; -} -``` - -- `indicator` is the indicator of the currently edited mention. -- `text` contains whole text that has been typed after the indicator. - -| Type | Platform | -| --------------------------------------- | ----------------- | -| `(event: OnChangeMentionEvent) => void` | iOS, Android, Web | - -### `onChangeSelection` - -Callback that is called each time user changes selection or moves the cursor in the input. - -Payload interface: - -```ts -interface OnChangeSelectionEvent { - start: Int32; - end: Int32; - text: string; -} -``` - -- `start` is the index of the selection's beginning. -- `end` is the first index after the selection's ending. For just a cursor in place (no selection), `start` equals `end`. -- `text` is the input's text in the current selection. - -| Type | Platform | -| --------------------------------------------------------------- | ----------------- | -| `(event: NativeSyntheticEvent ) => void` | iOS, Android, Web | - -### `onChangeState` - -Callback that gets called when any of the styles within the selection changes. - -Payload interface: - -```ts -interface OnChangeStateEvent { - bold: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - italic: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - underline: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - strikeThrough: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - inlineCode: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - h1: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - h2: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - h3: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - h4: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - h5: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - h6: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - codeBlock: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - blockQuote: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - orderedList: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - unorderedList: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - link: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - image: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - mention: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - checkboxList: { - isActive: boolean; - isConflicting: boolean; - isBlocking: boolean; - }; - alignment: string; -} -``` - -- `isActive` indicates if the style is active within current selection. -- `isBlocking` indicates if the style is blocked by other currently active, meaning it can't be toggled. -- `isConflicting` indicates if the style is in conflict with other currently active styles, meaning toggling it will remove conflicting style. -- `alignment` indicates the current text alignment of the paragraph at the cursor position. Possible values: `'left'`, `'center'`, `'right'`, `'justify'`, `'auto'`. - -> [!NOTE] -> On Android, `'justify'` is not supported. It is accepted in the type signature but has no justified layout effect — text is shown with natural alignment instead, the same as `'auto'`. On iOS, justified alignment works as expected. - -| Type | Platform | -| ----------------------------------------------------------- | ----------------- | -| `(event: NativeSyntheticEvent ) => void` | iOS, Android, Web | - -### `onChangeText` - -Callback called when any text changes occur in the input. - -Payload interface: - -```ts -interface OnChangeTextEvent { - value: string; -} -``` - -- `value` is the new text value of the input. - -| Type | Platform | -| ---------------------------------------------------------- | ----------------- | -| `(event: NativeSyntheticEvent ) => void` | iOS, Android, Web | - -> [!TIP] -> If you don't need the plain text value do not specify `onChangeText`, as it may have performance implications due to continuous text extraction. - -### `onEndMention` - -Callback that is called when the user no longer edits a mention actively - has moved the cursor somewhere else or put a space and the cursor isn't within the edited mention. - -- `indicator` is the indicator of the mention that was being edited. - -| Type | Platform | -| ----------------------------- | ----------------- | -| `(indicator: string) => void` | iOS, Android, Web | - -### `onFocus` - -Callback that's called whenever the input is focused. - -| Type | Platform | -| ------------ | ----------------- | -| `() => void` | iOS, Android, Web | - -### `onLinkDetected` - -Callback that gets called when either a new link has been added or the user has moved the cursor/selection to some link. - -Payload interface contains all the useful link data: - -```ts -interface OnLinkDetected { - text: string; - url: string; - start: Int32; - end: Int32; -} -``` - -- `text` is the link's displayed text. -- `url` is the underlying link's URL. -- `start` is the starting index of the link. -- `end` is the first index after the ending index of the link. - -| Type | Platform | -| --------------------------------- | ----------------- | -| `(event: OnLinkDetected) => void` | iOS, Android, Web | - -### `onMentionDetected` - -Callback called when mention has been detected - either a new mention has been added or the user has moved the cursor/selection to some mention. - -Payload interface contains all the useful mention data: - -```ts -interface OnMentionDetected { - text: string; - indicator: string; - attributes: Record ; -} -``` - -- `text` is the mention's displayed text. -- `indicator` is the indicator of the mention. -- `attributes` are the additional user-defined attributes that are being stored with the mention. - -| Type | Platform | -| ------------------------------------ | ----------------- | -| `(event: OnMentionDetected) => void` | iOS, Android, Web | - -### `onStartMention` - -Callback that gets called whenever a mention editing starts (after placing the indicator). - -- `indicator` is the indicator of the mention that begins editing. - -| Type | Platform | -| ----------------------------- | ----------------- | -| `(indicator: string) => void` | iOS, Android, Web | - -### `onKeyPress` - -Callback that is called when a key is pressed. See [TextInput onKeyPress](https://reactnative.dev/docs/textinput#onkeypress) for more details. - -```ts -export interface OnKeyPressEvent { - key: string; -} -``` - -| Type | Platform | -| -------------------------------------------------------- | ----------------- | -| `(event: NativeSyntheticEvent ) => void` | iOS, Android, Web | - -### `onSubmitEditing` - -Callback called when the user submits the input (presses the return/enter key while `submitBehavior` is `'submit'` or `'blurAndSubmit'`). - -Payload interface: - -```ts -interface OnSubmitEditing { - text: string; -} -``` - -- `text` is the current plain-text content of the input at submission time. - -| Type | Platform | -| -------------------------------------------------------- | ----------------- | -| `(event: NativeSyntheticEvent ) => void` | iOS, Android, Web | - -### `onPasteImages` - -Callback invoked when the user pastes one or more images or GIFs into the input. - -- `images` - is an array of objects containing the details (URI, MIME type, and dimensions) for each pasted image/GIF. -- **Web:** each `uri` is a `blob:` URL (`URL.createObjectURL`). If you retain URIs, call `URL.revokeObjectURL` when finished so blobs can be released. - -```ts -export interface OnPasteImagesEvent { - images: { - uri: string; - type: string; - width: Float; - height: Float; - }[]; -} -``` - -| Type | Platform | -| ----------------------------------------------------------- | ----------------- | -| `(event: NativeSyntheticEvent ) => void` | iOS, Android, Web | - -> [!NOTE] -> On Web, `uri` is a blob URL (`blob:...`). Blob URLs hold memory until explicitly released. -> Call `URL.revokeObjectURL(uri)` once you no longer need the image (e.g., after the upload completes). - -### `placeholder` - -The placeholder text that is displayed in the input if nothing has been typed yet. Disappears when something is typed. - -| Type | Default Value | Platform | -| -------- | ------------- | ----------------- | -| `string` | `''` | iOS, Android, Web | - -### `placeholderTextColor` - -Input placeholder's text color. - -| Type | Default Value | Platform | -| ---------------------------------------------- | ----------------------- | ----------------- | -| [`color`](https://reactnative.dev/docs/colors) | input's [color](#style) | iOS, Android, Web | - -### `ref` - -A React ref that lets you call any ref methods on the input. - -| Type | Default Value | Platform | -| ---------------------------------------------- | ------------- | ----------------- | -| `RefObject ` | - | iOS, Android, Web | - -### `returnKeyLabel` - -Overrides the return key label with a custom string. Not supported on iOS. - -| Type | Default Value | Platform | -| -------- | ------------- | -------- | -| `string` | - | Android | - -### `returnKeyType` - -Specifies the label or icon shown on the keyboard's return key. - -On Android, this prop is accepted but ignored, as `returnKeyType` doesn't work with multiline inputs. - -Accepts the standard React Native `ReturnKeyTypeOptions` values: `'go' | 'next' | 'search' | 'send' | 'done' | 'default' | 'google' | 'join' | 'route' | 'yahoo' | 'emergency-call' | 'previous' | 'none'`. - -| Type | Platform | -| ------------------ | -------- | -| `'go'` | iOS, Web | -| `'next'` | iOS, Web | -| `'search'` | iOS, Web | -| `'send'` | iOS, Web | -| `'done'` | iOS, Web | -| `'default'` | iOS | -| `'google'` | iOS | -| `'join'` | iOS | -| `'route'` | iOS | -| `'yahoo'` | iOS | -| `'emergency-call'` | iOS | -| `'previous'` | Web | - -| Type | Default Value | Platform | -| ---------------------- | ------------- | -------- | -| `ReturnKeyTypeOptions` | `'default'` | iOS, Web | - -> [!NOTE] -> On Web, this maps to the [`enterkeyhint`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint) attribute on the editor element. -> Only the values the browser recognises (`'enter'`, `'done'`, `'go'`, `'next'`, `'previous'`, `'search'`, `'send'`) have a visible effect; unsupported values are silently ignored and fall back to `'enter'`. - -### `selectionColor` - -Color of the selection rectangle that gets drawn over the selected text. On iOS, the cursor (caret) also gets set to this color. - -| Type | Default Value | Platform | -| ---------------------------------------------- | -------------- | ----------------- | -| [`color`](https://reactnative.dev/docs/colors) | system default | iOS, Android, Web | - -### `style` - -The `style` prop controls the layout, dimensions, typography, borders, shadows, opacity, and similar container-level appearance of the editable content container. The exact supported properties are listed under [EnrichedInputStyle](ENRICHED_INPUT_STYLE.md). - -| Type | Default Value | Platform | -| --------------------------------------------- | ------------- | ----------------- | -| [EnrichedInputStyle](ENRICHED_INPUT_STYLE.md) | - | iOS, Android, Web | - -### `submitBehavior` - -Controls what happens when the user presses the return/enter key. - -- `'newline'` — inserts a new line (default for multiline inputs). -- `'submit'` — fires `onSubmitEditing` without inserting a new line. -- `'blurAndSubmit'` — fires `onSubmitEditing` and blurs the input. - -| Type | Default Value | Platform | -| ------------------------------------------ | ------------- | ----------------- | -| `'submit' \| 'blurAndSubmit' \| 'newline'` | `'newline'` | iOS, Android, Web | - -### `textShortcuts` - -An array of shortcuts that auto-convert typed patterns into styles. Each entry maps a `trigger` string to a `style`. -These shortcuts allow users to format text similarly to modern Markdown editors by typing familiar patterns directly in the input. - -Item type: - -```ts -interface TextShortcut { - trigger: string; - style: TextShortcutStyle; -} - -type TextShortcutStyle = - | 'bold' - | 'italic' - | 'underline' - | 'strikethrough' - | 'inline_code' - | 'h1' - | 'h2' - | 'h3' - | 'h4' - | 'h5' - | 'h6' - | 'blockquote' - | 'codeblock' - | 'unordered_list' - | 'ordered_list' - | 'checkbox_list'; -``` - -- `trigger` is the typed pattern that activates the shortcut. -- `style` is the style to apply when the trigger completes. - -**[Paragraph styles](../README.md#paragraph-tags)** fire at the start of a paragraph (e.g. `# ` → H1, `- ` → unordered list). Supported styles: `h1`–`h6`, `blockquote`, `codeblock`, `unordered_list`, `ordered_list`, `checkbox_list`. - -> [!NOTE] -> Paragraph shortcuts are only effective on plain paragraphs. If the paragraph already has an active paragraph style (e.g. it is already a heading or a list item), typing the trigger pattern has no effect. - -**[Inline styles](../README.md#inline-tags)** fire when a closing delimiter is typed around text (e.g. `**text**` → bold). The trigger is the delimiter string (e.g. `**`, `*`, `~~`). Supported styles: `bold`, `italic`, `underline`, `strikethrough`, `inline_code`. - -> [!NOTE] -> Style rules still apply to shortcut-triggered styles: if the target style is **blocked** by another currently active style (e.g. bold inside a codeblock), the shortcut has no effect. If the target style **conflicts** with another active style, the conflicting style is removed when the new one is applied. See the [inline](../README.md#inline-tags) and [paragraph](../README.md#paragraph-tags) tag tables for the full conflict and blocking rules. - -Default value: - -```ts -[ - { trigger: '- ', style: 'unordered_list' }, - { trigger: '1. ', style: 'ordered_list' }, -]; -``` - -| Type | Default Value | Platform | -| ---------------- | ------------- | ----------------- | -| `TextShortcut[]` | see above | iOS, Android, Web | - -> [!NOTE] -> Pass an empty array to disable all shortcuts. - -### `ViewProps` - -The input inherits [ViewProps](https://reactnative.dev/docs/view#props), but keep in mind that some of the props may not be supported. - -| Platform | -| ------------ | -| iOS, Android | - -### `androidExperimentalSynchronousEvents` - EXPERIMENTAL - -If true, Android will use experimental synchronous events. This will prevent from input flickering when updating component size. However, this is an experimental feature, which has not been thoroughly tested. We may decide to enable it by default in a future release. - -| Type | Default Value | Platform | -| ------ | ------------- | -------- | -| `bool` | `false` | Android | - -### `useHtmlNormalizer` - -If true, external HTML pasted/inserted into the input (e.g. from Google Docs, Word, or web pages) will be normalized into the canonical tag subset that the enriched parser understands. - -| Type | Default Value | Platform | -| ------ | ------------- | ----------------- | -| `bool` | `true` | iOS, Android, Web | - -## Ref Methods - -All the methods should be called on the input's [ref](#ref). - -### `.blur()` - -```ts -blur: () => void; -``` - -Blurs the input. - -### `.focus()` - -```ts -focus: () => void; -``` - -Focuses the input. - -### `.getHTML()` - -```ts -getHTML: () => Promise ; -``` - -Returns a Promise that resolves with the current HTML content of the input. This is useful when you need to get the HTML on-demand (e.g., when saving) without the performance overhead of continuous HTML parsing via `onChangeHtml`. - -### `.setImage()` - -```ts -setImage: (src: string, width: number, height: number) => void; -``` - -Sets the [inline image](../README.md#inline-images) at the current selection. - -- `src: string` - absolute path to a file or remote image address. -- `width: number` - width of the image. -- `height: number` - height of the image. - -> [!NOTE] -> It's developer responsibility to provide proper width and height, which may require calculating aspect ratio. -> Also, keep in mind that in case of providing incorrect image source, static placeholder will be displayed. -> We may consider adding automatic image size detection and improved error handling in future releases. - -### `.setLink()` - -```ts -setLink: ( - start: number, - end: number, - text: string, - url: string -) => void; -``` - -Sets the link at the given place with a given displayed text and URL. Link will replace any text if there was some between `start` and `end` indexes. Setting a link with `start` equal to `end` will just insert it in place. - -- `start: number` - the starting index where the link should be. -- `end: number` - first index behind the new link's ending index. -- `text: string` - displayed text of the link. -- `url: string` - URL of the link. - -### `.removeLink()` - -```ts -removeLink: (start: number, end: number) => void; -``` - -Removes link styling from any links found within the given range. The text content is preserved, only the link attributes are stripped. Out-of-bounds values are clamped to valid range. - -- `start: number` - the starting index of the range to remove links from. -- `end: number` - first index behind the range's ending index. - -### `.setMention()` - -```ts -setMention: ( - indicator: string, - text: string, - attributes?: Record -) => void; -``` - -Sets the currently edited mention with a given indicator, displayed text and custom attributes. - -- `indicator: string` - the indicator of the set mention. -- `text: string` - the text that should be displayed for the mention. Anything the user typed gets replaced by that text. The mention indicator isn't added to that text. -- `attributes?: Record ` - additional, custom attributes for the mention that can be passed as a TypeScript record. They are properly preserved through parsing from and to the HTML format. - -### `.setValue()` - -```ts -setValue: (value: string) => void; -``` - -Sets the input's value. - -- `value: string` - value to set, it can either be `react-native-enriched-html` supported HTML string or raw text. - -### `.setSelection()` - -```ts -setSelection: (start: number, end: number) => void; -``` - -Sets the selection at the given indexes. - -- `start: number` - starting index of the selection. -- `end: number` - first index after the selection's ending index. For just a cursor in place (no selection), `start` equals `end`. - -### `.setTextAlignment()` - -```ts -setTextAlignment: (alignment: 'left' | 'center' | 'right' | 'justify' | 'auto') => void; -``` - -Sets text alignment for the paragraph(s) at the current selection. When inside a list, the alignment is applied to all contiguous list items. - -- `alignment` - the desired text alignment. Use `'auto'` to reset to the system natural alignment. - -> [!NOTE] -> On Android, `'justify'` is not supported. Calling `setTextAlignment('justify')` does not apply justified text — the paragraph ends up with natural alignment, the same as `'auto'`. On iOS, justified alignment works as expected. - -### `.startMention()` - -```ts -startMention: (indicator: string) => void; -``` - -Starts a mention with the given indicator. It gets put at the cursor/selection. - -- `indicator: string` - the indicator that starts the new mention. - -### `.toggleBlockQuote()` - -```ts -toggleBlockQuote: () => void; -``` - -Toggles blockquote style at the current selection. - -### `.toggleBold()` - -```ts -toggleBold: () => void; -``` - -Toggles bold formatting at the current selection. - -### `.toggleCodeBlock()` - -```ts -toggleCodeBlock: () => void; -``` - -Toggles codeblock formatting at the current selection. - -### `.toggleH1()` - -```ts -toggleH1: () => void; -``` - -Toggles heading 1 (H1) style at the current selection. - -### `.toggleH2()` - -```ts -toggleH2: () => void; -``` - -Toggles heading 2 (H2) style at the current selection. - -### `.toggleH3()` - -```ts -toggleH3: () => void; -``` - -Toggles heading 3 (H3) style at the current selection. - -### `.toggleH4()` - -```ts -toggleH4: () => void; -``` - -Toggles heading 4 (H4) style at the current selection. - -### `.toggleH5()` - -```ts -toggleH5: () => void; -``` - -Toggles heading 5 (H5) style at the current selection. - -### `.toggleH6()` - -```ts -toggleH6: () => void; -``` - -Toggles heading 6 (H6) style at the current selection. - -### `.toggleInlineCode()` - -```ts -toggleInlineCode: () => void; -``` - -Applies inline code formatting to the current selection. - -### `.toggleItalic()` - -```ts -toggleItalic: () => void; -``` - -Toggles italic formatting at the current selection. - -### `.toggleOrderedList()` - -```ts -toggleOrderedList: () => void; -``` - -Converts current selection into an ordered list. - -### `.toggleStrikeThrough()` - -```ts -toggleStrikeThrough: () => void; -``` - -Applies strikethrough formatting to the current selection. - -### `.toggleUnderline()` - -```ts -toggleUnderline: () => void; -``` - -Applies underline formatting to the current selection. - -### `.toggleUnorderedList()` - -```ts -toggleUnorderedList: () => void; -``` - -Converts current selection into an unordered list. - -### `.toggleCheckboxList()` - -```ts -toggleCheckboxList: (checked: boolean) => void; -``` - -Converts current selection into an unordered list with checkboxes as items. Each checkbox can be either checked or unchecked. -User can later toggle each checkbox individually by tapping on it. - -- `checked: boolean` - defines whether the checkboxes should be checked or unchecked by default. - -## Web Keyboard Shortcuts - -The following keyboard shortcuts are available on Web. `Mod` is `⌘` on macOS and `Ctrl` on Windows/Linux. - -| Action | Mac | Windows / Linux | -| ------------------- | ----------------- | ----------------------- | -| Bold | ⌘ B | Ctrl+B | -| Italic | ⌘ I | Ctrl+I | -| Underline | ⌘ U | Ctrl+U | -| Strikethrough | ⌘ Shift+X | Ctrl+Shift+X | -| Inline code | ⌘ Shift+C | Ctrl+Shift+C | -| Code block | ⌘ Alt Shift+C | Ctrl+Alt+Shift+C | -| Normal paragraph | ⌘ Alt+0 | Ctrl+Alt+0 | -| Heading 1–6 | ⌘ Alt+1 … ⌘ Alt+6 | Ctrl+Alt+1 … Ctrl+Alt+6 | -| Numbered list | ⌘ Shift+7 | Ctrl+Shift+7 | -| Bulleted list | ⌘ Shift+8 | Ctrl+Shift+8 | -| Checkbox list | ⌘ Shift+9 | Ctrl+Shift+9 | -| Paste as plain text | ⌘ Shift+V | Ctrl+Shift+V | -| Undo | ⌘ Z | Ctrl+Z | -| Redo | ⌘ Shift+Z | Ctrl+Shift+Z | -| Select all | ⌘ A | Ctrl+A | - -## HtmlStyle type - -Allows customizing HTML styles. - -```ts -interface HtmlStyle { - h1?: { - fontSize?: number; - bold?: boolean; - }; - h2?: { - fontSize?: number; - bold?: boolean; - }; - h3?: { - fontSize?: number; - bold?: boolean; - }; - h4?: { - fontSize?: number; - bold?: boolean; - }; - h5?: { - fontSize?: number; - bold?: boolean; - }; - h6?: { - fontSize?: number; - bold?: boolean; - }; - blockquote?: { - borderColor?: ColorValue; - borderWidth?: number; - gapWidth?: number; - color?: ColorValue; - }; - codeblock?: { - color?: ColorValue; - borderRadius?: number; - backgroundColor?: ColorValue; - }; - code?: { - color?: ColorValue; - backgroundColor?: ColorValue; - }; - a?: { - color?: ColorValue; - textDecorationLine?: 'underline' | 'none'; - }; - mention?: Record | MentionStyleProperties; - ol?: { - gapWidth?: number; - marginLeft?: number; - markerFontWeight?: TextStyle['fontWeight']; - markerColor?: ColorValue; - }; - ul?: { - bulletColor?: ColorValue; - bulletSize?: number; - marginLeft?: number; - gapWidth?: number; - }; - ulCheckbox?: { - boxColor?: ColorValue; - boxSize?: number; - marginLeft?: number; - gapWidth?: number; - }; -} - -interface MentionStyleProperties { - color?: ColorValue; - backgroundColor?: ColorValue; - textDecorationLine?: 'underline' | 'none'; -} -``` - -### h1/h2/h3/h4/h5/h6 (headings) - -- `fontSize` is the size of the heading's font. Defaults to `32` for `H1`, `24` for `H2`, `20` for `H3`, `16` for `H4`, `14` for `H5`, `12` for `H6`. -- `bold` defines whether the heading should be bolded, defaults to `false`. - -### blockquote - -- `borderColor` defines the color of the rectangular border drawn to the left of blockquote text. Takes [color](https://reactnative.dev/docs/colors) value, defaults to `darkgray`. -- `borderWidth` sets the width of the said border, defaults to `4`. -- `gapWidth` sets the width of the gap between the border and the blockquote text, defaults to `16`. -- `color` defines the color of blockquote's text. Takes [color](https://reactnative.dev/docs/colors) value, if not set makes the blockquote text the same color as the input's [color prop](#style). - -### codeblock - -- `color` defines the color of codeblock text, takes [color](https://reactnative.dev/docs/colors) value and defaults to `black`. -- `borderRadius` sets the radius of codeblock's border, defaults to 8. -- `backgroundColor` is the codeblock's background color, takes [color](https://reactnative.dev/docs/colors) value and defaults to `darkgray`. - -### code (inline code) - -- `color` defines the color of inline code's text, takes [color](https://reactnative.dev/docs/colors) value and defaults to `red`. -- `backgroundColor` is the inline code's background color, takes [color](https://reactnative.dev/docs/colors) value and defaults to `darkgray`. - -### a (link) - -- `color` defines the color of link's text, takes [color](https://reactnative.dev/docs/colors) value and defaults to `blue`. -- `textDecorationLine` decides if the links are underlined or not, takes either `underline` or `none` and defaults to `underline` - -### mention - -If only a single config is given, the style applies to all mention types. You can also set a different config for each mentionIndicator that has been defined, then the prop should be a record with indicators as keys and configs as their values. Additionally, you can define a style using the `'default'` key, which will act as a base that the rest of your defined styles will fallback on. - -- `color` defines the color of mention's text, takes [color](https://reactnative.dev/docs/colors) value and defaults to `blue`. -- `backgroundColor` is the mention's background color, takes [color](https://reactnative.dev/docs/colors) value and defaults to `yellow`. -- `textDecorationLine` decides if the mentions are underlined or not, takes either `underline` or `none` and defaults to `underline`. - -### ol (ordered list) - -By marker, we mean the number that denotes next lines of the list. - -- `gapWidth` sets the gap between the marker and the list item's text, defaults to `16`. -- `marginLeft` sets the margin to the left of the marker (between the marker and input's left edge), defaults to `16`. -- `markerFontWeight` defines the font weight of the marker, takes a [fontWeight](https://reactnative.dev/docs/text-style-props#fontweight) value and if not set, defaults to the same font weight as input's [fontWeight prop](#style). -- `markerColor` sets the text color of the marker, takes [color](https://reactnative.dev/docs/colors) value and if not set, defaults to the same color as input's [color prop](#style). - -### ul (unordered list) - -By bullet, we mean the dot that begins each line of the list. - -- `bulletColor` defines the color of the bullet, takes [color](https://reactnative.dev/docs/colors) value and defaults to `black`. -- `bulletSize` sets both the height and the width of the bullet, defaults to `8`. -- `marginLeft` is the margin to the left of the bullet (between the bullet and input's left edge), defaults to `16`. -- `gapWidth` sets the gap between the bullet and the list item's text, defaults to `16`. - -### ulCheckbox (checkbox list) - -Allows using unordered list with checkboxes instead of bullets. - -- `boxColor` defines the color of the checkbox, takes [color](https://reactnative.dev/docs/colors) value and defaults to `blue`. -- `boxSize` sets both the height and the width of the checkbox, defaults to `24`. -- `marginLeft` is the margin to the left of the checkbox (between the checkbox and input's left edge), defaults to `16`. -- `gapWidth` sets the gap between the checkbox and the list item's text, defaults to `16`. diff --git a/docs/RELEASE.md b/docs/RELEASE.md deleted file mode 100644 index 5deda901e..000000000 --- a/docs/RELEASE.md +++ /dev/null @@ -1,21 +0,0 @@ -# Release guide - -This document describes the steps to release the library. - -## Quick overview - -1. Create a release branch following convention: `@your_username/release-x.y.z`. -2. Update version number in `package.json`. -3. Run example apps to verify everything works. You will notice that iOS example app `Podfile.lock` has been updated. Remember to commit this change. - ```sh - cd apps/example/ios - pod install - cd ../../../ - yarn example ios - yarn example android - ``` -4. Commit changes, push branch, and open a pull request. -5. Once PR is approved and merged, run `publish` GitHub action. First run should be a dry run. -6. If everything looks fine (included files, library version and size), run the `publish` action again without dry run. -7. Install the new version from npm and verify everything works as expected. -8. After publishing, create a GitHub release with release notes. diff --git a/docs/TEXT_API_REFERENCE.md b/docs/TEXT_API_REFERENCE.md deleted file mode 100644 index 60761ff5d..000000000 --- a/docs/TEXT_API_REFERENCE.md +++ /dev/null @@ -1,156 +0,0 @@ -# EnrichedText API Reference - -## Props - -### `allowFontScaling` - -If `true`, the text respects the system's accessibility font scaling settings. - -| Type | Default Value | Platform | -| ------ | ------------- | ------------ | -| `bool` | `true` | iOS, Android | - -### `children` - -The HTML string to render. Accepts the HTML format produced by `EnrichedTextInput`. - -| Type | Default Value | Platform | -| -------- | ------------- | ----------------- | -| `string` | - | iOS, Android, Web | - -### `style` - -Standard React Native `TextStyle` applied to the text. - -| Type | Default Value | Platform | -| ----------- | ------------- | ----------------- | -| `TextStyle` | - | iOS, Android, Web | - -### `htmlStyle` - -A prop for customizing styles of HTML elements, including press colors for interactive elements. - -| Type | Default Value | Platform | -| ------------------------------------------------------ | ------------- | ----------------- | -| [`EnrichedTextHtmlStyle`](#enrichedtexthtmlstyle-type) | - | iOS, Android, Web | - -### `useHtmlNormalizer` - -If `true`, external HTML (e.g. from Google Docs, Word, web pages) will be normalized before rendering. This converts arbitrary HTML into the canonical tag subset that the enriched parser understands. - -| Type | Default Value | Platform | -| ------ | ------------- | ----------------- | -| `bool` | `true` | iOS, Android, Web | - -### `ellipsizeMode` - -How the text should be truncated when `numberOfLines` is set and the text overflows. - -- `head` – truncates at the beginning, e.g. `...wxyz`. -- `middle` – truncates in the middle, e.g. `ab...yz`. -- `tail` – truncates at the end, e.g. `abcd...`. -- `clip` – clips the text without inserting an ellipsis. - -| Type | Default Value | Platform | -| ---------------------------------------- | ------------- | ----------------- | -| `'head' \| 'middle' \| 'tail' \| 'clip'` | `'tail'` | iOS, Android, Web | - -> [!NOTE] -> On Android, when numberOfLines is set to a value higher than 1, only tail value will work correctly. - -> [!NOTE] -> On web, truncation runs on the client after hydration, which can cause a flash under SSR. See [Web Support](./WEB.md#enriched-text). - -### `numberOfLines` - -Limits the number of displayed lines. Set to `0` for unlimited lines. - -| Type | Default Value | Platform | -| -------- | ------------- | ----------------- | -| `number` | `0` | iOS, Android, Web | - -### `selectable` - -If `true`, the text can be selected by the user (e.g. for copy/paste). - -| Type | Default Value | Platform | -| ------ | ------------- | ----------------- | -| `bool` | `false` | iOS, Android, Web | - -### `selectionColor` - -The color of the text selection highlight. - -| Type | Default Value | Platform | -| ---------------------------------------------- | -------------- | ----------------- | -| [`color`](https://reactnative.dev/docs/colors) | system default | iOS, Android, Web | - -### `onLinkPress` - -Called when the user presses a link element. Receives an `OnLinkPressEvent` containing the link's URL. - -```ts -interface OnLinkPressEvent { - url: string; -} -``` - -| Type | Default Value | Platform | -| ----------------------------------- | ------------- | ----------------- | -| `(event: OnLinkPressEvent) => void` | - | iOS, Android, Web | - -### `onMentionPress` - -Called when the user presses a mention element. Receives an `OnMentionPressEvent` with the mention's text, indicator character, and custom attributes. - -```ts -interface OnMentionPressEvent { - text: string; - indicator: string; - attributes: Record ; -} -``` - -| Type | Default Value | Platform | -| -------------------------------------- | ------------- | ----------------- | -| `(event: OnMentionPressEvent) => void` | - | iOS, Android, Web | - -## EnrichedTextHtmlStyle type - -Extends [`HtmlStyle`](API_REFERENCE.md#htmlstyle-type) with additional press-state styling for interactive elements. All properties from `HtmlStyle` are supported except `a` and `mention`, which are replaced by the extended versions below. - -```ts -interface EnrichedTextHtmlStyle extends Omit { - a?: { - color?: ColorValue; - textDecorationLine?: 'underline' | 'none'; - pressColor?: ColorValue; - }; - mention?: - | Record - | EnrichedTextMentionStyleProperties; -} - -interface EnrichedTextMentionStyleProperties { - color?: ColorValue; - backgroundColor?: ColorValue; - textDecorationLine?: 'underline' | 'none'; - pressColor?: ColorValue; - pressBackgroundColor?: ColorValue; -} -``` - -### a (link) - -Inherits all properties from [`HtmlStyle`'s `a`](API_REFERENCE.md#a-link) and adds: - -- `pressColor` – the color of the link text while it is being pressed. Takes a [color](https://reactnative.dev/docs/colors) value. - -### mention - -Inherits all properties from [`HtmlStyle`'s `mention`](API_REFERENCE.md#mention) and adds: - -- `pressColor` – the color of the mention text while it is being pressed. Takes a [color](https://reactnative.dev/docs/colors) value. -- `pressBackgroundColor` – the background color of the mention while it is being pressed. Takes a [color](https://reactnative.dev/docs/colors) value. - -Same as in `HtmlStyle`, if only a single config is given the style applies to all mention types. To style each indicator separately, pass a record with indicators as keys and configs as values. diff --git a/docs/WEB.md b/docs/WEB.md deleted file mode 100644 index bc69f67fb..000000000 --- a/docs/WEB.md +++ /dev/null @@ -1,59 +0,0 @@ -# Web Support - -## Enriched Text Input - -### Keyboard shortcuts - -See [Web Keyboard Shortcuts](./INPUT_API_REFERENCE.md#web-keyboard-shortcuts) for the up-to-date list of Web keyboard shortcuts. - -### Unsupported - -- **`returnKeyLabel`**: ignored on web, it's not possible to set it inside a browser. -- **Context menu**: `contextMenuItems` is ignored. -- **RN layout ref methods**: `measure`, `measureInWindow`, `measureLayout`, and `setNativeProps` are no-ops. -- **`ViewProps`**: Props inherited from `View` beyond the implemented subset are not forwarded. - -## Enriched Text - -### Unsupported - -- **RN layout ref methods**: `measure`, `measureInWindow`, `measureLayout`, and `setNativeProps` are no-ops. - -## HTML sanitization - -On web, HTML is sanitized automatically with [DOMPurify](https://github.com/cure53/DOMPurify) on both input and output. This reduces XSS risk, but you should still treat untrusted HTML with caution and apply your own server-side sanitization. - -- **`EnrichedText`** sanitizes its `children` before rendering. -- **`EnrichedTextInput`** sanitizes every HTML entry point — `defaultValue`, the `setValue` ref method, and pasted HTML — as well as its output from `getHTML` and the `onChangeHtml` callback. - -### Allowing custom link protocols - -By default, sanitization strips links with non-standard protocols (e.g. `custom://…`). Both `EnrichedText` and `EnrichedTextInput` accept a web-only `sanitizationConfig` prop whose `linkRegex` field lets you control which link URIs survive. - -`linkRegex` maps directly to DOMPurify's [`ALLOWED_URI_REGEXP`](https://github.com/cure53/DOMPurify#can-i-configure-dompurify), so it **replaces** the default allow-list rather than extending it. Because this regex affects all URI-containing attributes (e.g. `src` in ` `), remember to keep the standard protocols you still want to permit: - -```tsx -
- {html} - -``` - -When `sanitizationConfig` is omitted, DOMPurify's built-in default is used. - -> Note: `sanitizationConfig.linkRegex` only controls what sanitization keeps. It is independent of the top-level `linkRegex` prop, which controls autolink detection while typing. To both autolink and preserve a custom protocol, configure both. - -### Custom mention attributes - -To attach custom data to a mention, use the `data-` prefix (e.g. `data-user-id`) to make sure they survive sanitization. Attributes passed to the `setMention` ref method are properly sanitized. - -## Client-only rendering (no SSR) - -Both `EnrichedText` and `EnrichedTextInput` are **client-only** components. They rely on browser-only APIs (`DOMParser`, `DOMPurify`, `TipTap`) and are **not designed for server-side rendering (SSR)**. - -If your application uses SSR (Next.js, Remix, Gatsby, etc.), make sure these components only render on the client. diff --git a/docs/babel.config.js b/docs/babel.config.js new file mode 100644 index 000000000..e00595dae --- /dev/null +++ b/docs/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/docs/docs/api-reference/_category_.json b/docs/docs/api-reference/_category_.json new file mode 100644 index 000000000..cc5acae89 --- /dev/null +++ b/docs/docs/api-reference/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "API reference", + "position": 50, + "link": { + "type": "generated-index" + } +} diff --git a/docs/docs/api-reference/enriched-text-input.md b/docs/docs/api-reference/enriched-text-input.md new file mode 100644 index 000000000..28a896346 --- /dev/null +++ b/docs/docs/api-reference/enriched-text-input.md @@ -0,0 +1,1452 @@ +--- +sidebar_position: 1 +--- + +# EnrichedTextInput + +`EnrichedTextInput` is a rich text editor that styles text live as you type. +See [Core concepts](/fundamentals/core-concepts) for the mental model. + +## Reference + +```tsx +import { useRef } from 'react'; +import { EnrichedTextInput } from 'react-native-enriched-html'; +import type { EnrichedTextInputInstance } from 'react-native-enriched-html'; + +function App() { + const ref = useRef(null); + + return ( + { + // e.nativeEvent.bold.isActive, ... + }} + /> + ); +} +``` + + ++ +## Props + +All props are optional. + +### `allowFontScaling` {#allowfontscaling} + +If `true`, the input respects the system's accessibility font scaling settings. + +| Type | Default | Platforms | +| --------- | ------- | ------------ | +| `boolean` | `true` | Android, iOS | + +### `autoFocus` {#autofocus} + +If `true`, focuses the input when it mounts. + +| Type | Default | Platforms | +| --------- | ------- | ----------------- | +| `boolean` | `false` | Android, iOS, Web | + +### `autoCapitalize` {#autocapitalize} + +Tells the input to automatically capitalize certain characters. + +- `characters` - all characters +- `words` - first letter of each word +- `sentences` - first letter of each sentence +- `none` - don't auto-capitalize anything + +| Type | Default | Platforms | +| -------------------------------------------------- | ------------- | ----------------- | +| `'none' \| 'sentences' \| 'words' \| 'characters'` | `'sentences'` | Android, iOS, Web | + +### `contextMenuItems` {#contextmenuitems} + +An array of custom items to display in the native text editing menu. Each item +specifies a title, visibility flag, and a callback that fires when the item is +tapped. + +```ts +interface ContextMenuItem { + text: string; + onPress: (args: { + text: string; + selection: { start: number; end: number }; + styleState: OnChangeStateEvent; + }) => void; + visible?: boolean; +} +``` + +- `text` is the title displayed in the menu +- `onPress` is the callback invoked when the item is tapped +- `visible` controls whether the item is shown; defaults to `true` + +The `onPress` callback receives a single object argument with: + +- `text` - the currently selected text +- `selection` - an object with `start` and `end` indices of the current selection +- `styleState` - the latest `OnChangeStateEvent` payload reflecting active styles + at the time of the tap + +| Type | Default | Platforms | +| ------------------- | ------- | ------------ | +| `ContextMenuItem[]` | - | Android, iOS | + +:::note + +On iOS, items appear in array order, before the system items (Copy/Paste/Cut). +On Android, there is no guaranteed order and custom items may be displayed in a +submenu, depending on the device manufacturer. + +::: + +See [Custom context menu](/guides/custom-context-menu) for a full example. + +### `cursorColor` {#cursorcolor} + +Sets the color of the cursor (caret) in the component. + +| Type | Default | Platforms | +| ---------------------------------------------- | -------------- | ------------ | +| [`color`](https://reactnative.dev/docs/colors) | system default | Android, Web | + +### `defaultValue` {#defaultvalue} + +Provides an initial value for the input. If the string is a valid HTML output of +`EnrichedTextInput` (or other HTML that the parser will accept), proper styles +are applied. + +| Type | Default | Platforms | +| -------- | ------- | ----------------- | +| `string` | - | Android, iOS, Web | + +### `editable` {#editable} + +If `false`, text is not editable. + +| Type | Default | Platforms | +| --------- | ------- | ----------------- | +| `boolean` | `true` | Android, iOS, Web | + +:::note + +Setting `editable` to `false` disables all user interactions with the input. +Some programmatic changes (like toggling styles or changing value imperatively) +via ref methods still work. + +::: + +### `htmlStyle` {#htmlstyle} + +Customizes the appearance of HTML elements inside the editor. See +[`HtmlStyle`](#htmlstyle-type). + +| Type | Default | Platforms | +| ----------- | -------------------------------------------------- | ----------------- | +| `HtmlStyle` | default values from [`HtmlStyle`](#htmlstyle-type) | Android, iOS, Web | + +### `mentionIndicators` {#mentionindicators} + +The recognized mention indicators. Each item must be a 1-character string. See +[Mentions](/rich-text-formatting/mentions) for how indicators are used. + +| Type | Default | Platforms | +| ---------- | ------- | ----------------- | +| `string[]` | `['@']` | Android, iOS, Web | + +### `linkRegex` {#linkregex} + +A custom regex pattern for detecting links in the input. If not provided, a +default regex is used. You can customize which patterns are recognized as links + +- for example only `https://` URLs, or custom schemes. + +Not all JS regex features are supported; for example variable-width lookbehinds +won't work. + +| Type | Default | Platforms | +| ---------------- | ----------------------------- | ----------------- | +| `RegExp \| null` | default native platform regex | Android, iOS, Web | + +:::tip + +Pass `null` to disable link detection completely. + +::: + +:::caution + +Links might get stripped if sanitization is not configured properly. +For details, see [sanitization](/core-functionalities/web-support). + +::: + +### `onBlur` {#onblur} + +Called whenever the input loses focus. + +| Type | Platforms | +| ------------------------ | ----------------- | +| `(e: BlurEvent) => void` | Android, iOS, Web | + +### `onChangeHtml` {#onchangehtml} + +Called when the input's HTML changes. + +```ts +interface OnChangeHtmlEvent { + value: string; +} +``` + +- `value` is the new HTML + +| Type | Platforms | +| ---------------------------------------------------------- | ----------------- | +| `(event: NativeSyntheticEventType definitions
+ +```ts +interface EnrichedTextInputProps extends Omit{ + ref?: RefObject ; + autoFocus?: boolean; + editable?: boolean; + mentionIndicators?: string[]; + defaultValue?: string; + placeholder?: string; + placeholderTextColor?: ColorValue; + cursorColor?: ColorValue; + selectionColor?: ColorValue; + autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters'; + htmlStyle?: HtmlStyle; + style?: EnrichedInputStyle; + scrollEnabled?: boolean; + linkRegex?: RegExp | null; + sanitizationConfig?: SanitizationConfig; + returnKeyType?: ReturnKeyTypeOptions; + returnKeyLabel?: string; + submitBehavior?: 'submit' | 'blurAndSubmit' | 'newline'; + onFocus?: (e: FocusEvent) => void; + onBlur?: (e: BlurEvent) => void; + onChangeText?: (e: NativeSyntheticEvent ) => void; + onChangeHtml?: (e: NativeSyntheticEvent ) => void; + onChangeState?: (e: NativeSyntheticEvent ) => void; + onLinkDetected?: (e: OnLinkDetected) => void; + onMentionDetected?: (e: OnMentionDetected) => void; + onStartMention?: (indicator: string) => void; + onChangeMention?: (e: OnChangeMentionEvent) => void; + onEndMention?: (indicator: string) => void; + onChangeSelection?: (e: NativeSyntheticEvent ) => void; + onKeyPress?: (e: NativeSyntheticEvent ) => void; + onSubmitEditing?: (e: NativeSyntheticEvent ) => void; + onPasteImages?: (e: NativeSyntheticEvent ) => void; + contextMenuItems?: ContextMenuItem[]; + textShortcuts?: TextShortcut[]; + androidExperimentalSynchronousEvents?: boolean; + useHtmlNormalizer?: boolean; + allowFontScaling?: boolean; +} +``` + + ) => void` | Android, iOS, Web | + +:::tip + +Specifying `onChangeHtml` may have performance implications, especially with +large documents, as it requires continuous HTML parsing. If you only need the +HTML at specific moments (for example when saving), use the +[`getHTML`](#gethtml) ref method instead. + +::: + +### `onChangeMention` {#onchangemention} + +Called whenever the text typed after the mention indicator changes while a mention is being edited. + +```ts +interface OnChangeMentionEvent { + indicator: string; + text: string; +} +``` + +- `indicator` is the indicator of the currently edited mention +- `text` contains the whole text typed after the indicator + +| Type | Platforms | +| --------------------------------------- | ----------------- | +| `(event: OnChangeMentionEvent) => void` | Android, iOS, Web | + +### `onChangeSelection` {#onchangeselection} + +Called each time the user changes the selection or moves the cursor. + +```ts +interface OnChangeSelectionEvent { + start: number; + end: number; + text: string; +} +``` + +- `start` is the index of the selection's beginning +- `end` is the first index after the selection's ending; for a cursor with no + selection, `start` equals `end` +- `text` is the input's text in the current selection + +| Type | Platforms | +| --------------------------------------------------------------- | ----------------- | +| `(event: NativeSyntheticEvent ) => void` | Android, iOS, Web | + +### `onChangeState` {#onchangestate} + +Called when any of the styles within the selection changes. Use this to drive +toolbar button state. See +[The style state model](/fundamentals/core-concepts#the-style-state-model). + +```ts +interface StyleState { + isActive: boolean; + isConflicting: boolean; + isBlocking: boolean; +} + +interface OnChangeStateEvent { + bold: StyleState; + italic: StyleState; + underline: StyleState; + strikeThrough: StyleState; + inlineCode: StyleState; + h1: StyleState; + h2: StyleState; + h3: StyleState; + h4: StyleState; + h5: StyleState; + h6: StyleState; + codeBlock: StyleState; + blockQuote: StyleState; + orderedList: StyleState; + unorderedList: StyleState; + link: StyleState; + image: StyleState; + mention: StyleState; + checkboxList: StyleState; + alignment: string; +} +``` + +- `isActive` - the style is active within the current selection +- `isBlocking` - the style is blocked by another currently active style, so it + can't be toggled +- `isConflicting` - toggling the style removes a conflicting active style +- `alignment` - current text alignment of the paragraph at the cursor: + `'left'`, `'center'`, `'right'`, `'justify'`, or `'auto'` + +:::note + +On Android, `'justify'` is not supported. It is accepted in the type signature +but has no justified layout effect - text is shown with natural alignment +instead, the same as `'auto'`. + +::: + +| Type | Platforms | +| ----------------------------------------------------------- | ----------------- | +| `(event: NativeSyntheticEvent ) => void` | Android, iOS, Web | + +### `onChangeText` {#onchangetext} + +Called when any text changes occur in the input. + +```ts +interface OnChangeTextEvent { + value: string; +} +``` + +- `value` is the new plain-text value of the input + +| Type | Platforms | +| ---------------------------------------------------------- | ----------------- | +| `(event: NativeSyntheticEvent ) => void` | Android, iOS, Web | + +### `onEndMention` {#onendmention} + +Called when the user is no longer editing a mention actively - they moved the +cursor elsewhere, or typed a space and the cursor is no longer within the edited +mention. + +- `indicator` is the indicator of the mention that was being edited + +| Type | Platforms | +| ----------------------------- | ----------------- | +| `(indicator: string) => void` | Android, iOS, Web | + +### `onFocus` {#onfocus} + +Called whenever the input is focused. + +| Type | Platforms | +| ------------------------- | ----------------- | +| `(e: FocusEvent) => void` | Android, iOS, Web | + +### `onLinkDetected` {#onlinkdetected} + +Called when the user has moved the +cursor/selection onto a link. + +```ts +interface OnLinkDetected { + text: string; + url: string; + start: number; + end: number; +} +``` + +- `text` is the link's displayed text +- `url` is the underlying URL +- `start` is the starting index of the link +- `end` is the first index after the ending index of the link + +| Type | Platforms | +| --------------------------------- | ----------------- | +| `(event: OnLinkDetected) => void` | Android, iOS, Web | + +### `onMentionDetected` {#onmentiondetected} + +Called when the +user moved the cursor/selection onto a mention. + +```ts +interface OnMentionDetected { + text: string; + indicator: string; + attributes: Record ; +} +``` + +- `text` is the mention's displayed text +- `indicator` is the indicator of the mention +- `attributes` are the additional user-defined attributes stored with the mention + +| Type | Platforms | +| ------------------------------------ | ----------------- | +| `(event: OnMentionDetected) => void` | Android, iOS, Web | + +### `onStartMention` {#onstartmention} + +Called whenever mention editing starts. + +- `indicator` is the indicator of the mention that begins editing + +| Type | Platforms | +| ----------------------------- | ----------------- | +| `(indicator: string) => void` | Android, iOS, Web | + +### `onKeyPress` {#onkeypress} + +Called when a key is pressed. See +[TextInput onKeyPress](https://reactnative.dev/docs/textinput#onkeypress) for +more details. + +```ts +interface OnKeyPressEvent { + key: string; +} +``` + +| Type | Platforms | +| -------------------------------------------------------- | ----------------- | +| `(event: NativeSyntheticEvent ) => void` | Android, iOS, Web | + +### `onSubmitEditing` {#onsubmitediting} + +Called when the user submits the input (presses the return/enter key while +`submitBehavior` is `'submit'` or `'blurAndSubmit'`). + +```ts +interface OnSubmitEditing { + text: string; +} +``` + +- `text` is the current plain-text content of the input at submission time + +| Type | Platforms | +| -------------------------------------------------------- | ----------------- | +| `(event: NativeSyntheticEvent ) => void` | Android, iOS, Web | + +### `onPasteImages` {#onpasteimages} + +Called when the user pastes one or more images or GIFs into the input. See +[Inline images](/rich-text-formatting/inline-images) for the full picture. + +- `images` - an array of objects with URI, MIME type, and dimensions for each + pasted image/GIF + +```ts +interface OnPasteImagesEvent { + images: { + uri: string; + type: string; + width: number; + height: number; + }[]; +} +``` + +| Type | Platforms | +| ----------------------------------------------------------- | ----------------- | +| `(event: NativeSyntheticEvent ) => void` | Android, iOS, Web | + +:::note + +On Web, `uri` is a blob URL (`blob:...`). Blob URLs hold memory until explicitly +released. Call `URL.revokeObjectURL(uri)` once you no longer need the image +(for example after the upload completes). + +::: + +### `placeholder` {#placeholder} + +The placeholder text displayed when nothing has been typed yet. Disappears when +something is typed. + +| Type | Default | Platforms | +| -------- | ------- | ----------------- | +| `string` | `''` | Android, iOS, Web | + +### `placeholderTextColor` {#placeholdertextcolor} + +Input placeholder's text color. + +| Type | Default | Platforms | +| ---------------------------------------------- | ----------------------- | ----------------- | +| [`color`](https://reactnative.dev/docs/colors) | input's [color](#style) | Android, iOS, Web | + +### `ref` {#ref} + +A React ref that lets you call any [ref methods](#ref-methods) on the input. + +| Type | Default | Platforms | +| ---------------------------------------------- | ------- | ----------------- | +| `RefObject ` | - | Android, iOS, Web | + +### `returnKeyLabel` {#returnkeylabel} + +Overrides the return key label with a custom string. + +| Type | Default | Platforms | +| -------- | ------- | --------- | +| `string` | - | Android | + +### `returnKeyType` {#returnkeytype} + +Specifies the label or icon shown on the keyboard's return key. + +On Android, this prop is accepted but ignored, as `returnKeyType` doesn't work +with multiline inputs. + +Accepts the standard React Native `ReturnKeyTypeOptions` values: +`'go' | 'next' | 'search' | 'send' | 'done' | 'default' | 'google' | 'join' | 'route' | 'yahoo' | 'emergency-call' | 'previous' | 'none'`. + +| Type | Default | Platforms | +| ---------------------- | ----------- | --------- | +| `ReturnKeyTypeOptions` | `'default'` | iOS, Web | + +:::note + +On Web, this maps to the +[`enterkeyhint`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint) +attribute on the editor element. Only the values the browser recognises +(`'enter'`, `'done'`, `'go'`, `'next'`, `'previous'`, `'search'`, `'send'`) have +a visible effect; unsupported values are silently ignored and fall back to +`'enter'`. + +::: + +### `sanitizationConfig` {#sanitizationconfig} + +Web-only configuration for the HTML sanitization step, which runs on every HTML +entry and exit point — `defaultValue`, `.setValue()`, pasted HTML, `.getHTML()`, +and [`onChangeHtml`](#onchangehtml). + +```ts +interface SanitizationConfig { + linkRegex?: RegExp; +} +``` + +- `linkRegex` - a regular expression deciding which link URIs survive + sanitization. + +:::caution + +`linkRegex` maps directly to DOMPurify's [`ALLOWED_URI_REGEXP`](https://github.com/cure53/DOMPurify#can-i-configure-dompurify), so it **replaces** the default allow-list rather than extending it. Because this regex affects all URI-containing attributes (e.g. `src` in ` `), remember to keep the standard protocols you still want to permit. + +::: + +```tsx +
+``` + +| Type | Default | Platforms | +| -------------------- | ------- | --------- | +| `SanitizationConfig` | - | Web | + +:::note + +This only controls what sanitization keeps. It is independent of the +[`linkRegex`](#linkregex) prop, which controls autolink detection while typing. +To both autolink and preserve a custom protocol, configure both. + +::: + +### `scrollEnabled` {#scrollenabled} + +If `false`, the editor's internal scroll view is disabled and the component +expands to fit all content. + +| Type | Default | Platforms | +| --------- | ------- | ----------------- | +| `boolean` | `true` | Android, iOS, Web | + +### `selectionColor` {#selectioncolor} + +Color of the selection rectangle drawn over the selected text. On iOS, the +cursor (caret) also uses this color. + +| Type | Default | Platforms | +| ---------------------------------------------- | -------------- | ----------------- | +| [`color`](https://reactnative.dev/docs/colors) | system default | Android, iOS, Web | + +### `style` {#style} + +Controls the layout, dimensions, typography, borders, shadows, opacity, and +similar container-level appearance of the editable content container. See +[`EnrichedInputStyle`](#enrichedinputstyle-type) and +[Styling the input](/core-functionalities/styling-the-input). + +| Type | Default | Platforms | +| -------------------- | ------- | ----------------- | +| `EnrichedInputStyle` | - | Android, iOS, Web | + +### `submitBehavior` {#submitbehavior} + +Controls what happens when the user presses the return/enter key. + +- `'newline'` - inserts a new line (default for multiline inputs) +- `'submit'` - fires `onSubmitEditing` without inserting a new line +- `'blurAndSubmit'` - fires `onSubmitEditing` and blurs the input + +| Type | Default | Platforms | +| ------------------------------------------ | ----------- | ----------------- | +| `'submit' \| 'blurAndSubmit' \| 'newline'` | `'newline'` | Android, iOS, Web | + +### `textShortcuts` {#textshortcuts} + +An array of shortcuts that auto-convert typed patterns into styles. Each entry +maps a `trigger` string to a `style`. These shortcuts allow users to format text +similarly to modern Markdown editors by typing familiar patterns directly in the +input. See [Text shortcuts](/rich-text-formatting/text-shortcuts) for more +details. + +```ts +interface TextShortcut { + trigger: string; + style: TextShortcutStyle; +} + +type TextShortcutStyle = + | 'bold' + | 'italic' + | 'underline' + | 'strikethrough' + | 'inline_code' + | 'h1' + | 'h2' + | 'h3' + | 'h4' + | 'h5' + | 'h6' + | 'blockquote' + | 'codeblock' + | 'unordered_list' + | 'ordered_list' + | 'checkbox_list'; +``` + +- `trigger` is the typed pattern that activates the shortcut +- `style` is the style to apply when the trigger completes + +**[Paragraph styles](/fundamentals/html-format-and-supported-tags#paragraph-tags)** +fire at the start of a paragraph (e.g. `# ` → H1, `- ` → unordered list). +Supported styles: `h1`–`h6`, `blockquote`, `codeblock`, `unordered_list`, +`ordered_list`, `checkbox_list`. + +:::note + +Paragraph shortcuts are only effective on plain paragraphs. If the paragraph +already has an active paragraph style (for example it is already a heading or a +list item), typing the trigger pattern has no effect. + +::: + +**[Inline styles](/fundamentals/html-format-and-supported-tags#inline-tags)** +fire when a closing delimiter is typed around text (e.g. `**text**` → bold). The +trigger is the delimiter string (e.g. `**`, `*`, `~~`). Supported styles: +`bold`, `italic`, `underline`, `strikethrough`, `inline_code`. + +:::note + +Style rules still apply to shortcut-triggered styles: if the target style is +**blocked** by another currently active style (e.g. bold inside a codeblock), +the shortcut has no effect. If the target style **conflicts** with another +active inline style, the conflicting style is removed when the new one is +applied. See [Supported tags](/fundamentals/html-format-and-supported-tags) for the full +conflict and blocking rules. + +::: + +Default value: + +```ts +[ + { trigger: '- ', style: 'unordered_list' }, + { trigger: '1. ', style: 'ordered_list' }, +]; +``` + +| Type | Default | Platforms | +| ---------------- | --------- | ----------------- | +| `TextShortcut[]` | see above | Android, iOS, Web | + +:::tip + +Pass an empty array to disable all shortcuts. + +::: + +### `useHtmlNormalizer` {#usehtmlnormalizer} + +If `true`, external HTML pasted or inserted into the input (for example from +Google Docs, Word, or web pages) is normalized into the canonical tag subset +that the enriched parser understands. See +[Normalization](/fundamentals/core-concepts#normalization). + +| Type | Default | Platforms | +| --------- | ------- | ----------------- | +| `boolean` | `true` | Android, iOS, Web | + +### `androidExperimentalSynchronousEvents` {#androidexperimentalsynchronousevents} + +:::caution + +Experimental. This feature has not been thoroughly tested. It may be enabled by +default in a future release. + +::: + +If `true`, Android uses experimental synchronous events. This can prevent input +flickering when updating component size. + +| Type | Default | Platforms | +| --------- | ------- | --------- | +| `boolean` | `false` | Android | + +### `ViewProps` {#viewprops} + +The input inherits +[ViewProps](https://reactnative.dev/docs/view#props), but some of those props +may not be supported. + +| Type | Default | Platforms | +| ----------- | ------- | ------------ | +| `ViewProps` | - | Android, iOS | + +## Ref methods + +All methods should be called on the input's [`ref`](#ref). + +### `.blur()` + +```ts +blur: () => void; +``` + +Blurs the input. + +### `.focus()` + +```ts +focus: () => void; +``` + +Focuses the input. + +### `.getHTML()` + +```ts +getHTML: () => Promise ; +``` + +Returns a Promise that resolves with the current HTML content of the input. +Useful when you need the HTML on demand (for example when saving) without the +performance overhead of continuous parsing via `onChangeHtml`. + +### `.setImage()` + +```ts +setImage: (src: string, width: number, height: number) => void; +``` + +Sets an inline image at the current selection. See +[Inline images](/rich-text-formatting/inline-images) for more details. + +- `src: string` - absolute path to a file or remote image address +- `width: number` - width of the image +- `height: number` - height of the image + +:::note + +It's the developer's responsibility to provide proper width and height, which +may require calculating aspect ratio. If the image source is incorrect, a static +placeholder is displayed. + +::: + +### `.setLink()` + +```ts +setLink: (start: number, end: number, text: string, url: string) => void; +``` + +Sets a link at the given place with the given displayed text and URL. The link +replaces any text between `start` and `end`. Setting a link with `start` equal +to `end` inserts it in place. See [Links](/rich-text-formatting/links) for more +details. + +- `start: number` - starting index where the link should be +- `end: number` - first index behind the new link's ending index +- `text: string` - displayed text of the link +- `url: string` - URL of the link + +### `.removeLink()` + +```ts +removeLink: (start: number, end: number) => void; +``` + +Removes link styling from any links found within the given range. The text +content is preserved; only the link attributes are stripped. + +- `start: number` - starting index of the range to remove links from +- `end: number` - first index behind the range's ending index + +### `.setMention()` + +```ts +setMention: ( + indicator: string, + text: string, + attributes?: Record +) => void; +``` + +Sets the currently edited mention with a given indicator, displayed text, and +custom attributes. See [Mentions](/rich-text-formatting/mentions) for more +details. + +- `indicator: string` - indicator of the set mention +- `text: string` - text displayed for the mention; anything the user typed is + replaced by that text. The mention indicator isn't added to that text +- `attributes?: Record ` - additional custom attributes for the + mention, preserved through parsing to and from HTML + +:::caution + +The attributes you pass to `setMention` ride along in the HTML +and survive a round-trip through `getHTML` / `setValue`. Prefix custom keys with +`data-` if they need to outlive a sanitizer - see the note in +[Mentions](/rich-text-formatting/mentions). + +::: + +### `.setValue()` + +```ts +setValue: (value: string) => void; +``` + +Sets the input's value. + +- `value: string` - value to set; either a supported HTML string or raw text + +### `.setSelection()` + +```ts +setSelection: (start: number, end: number) => void; +``` + +Sets the selection at the given indexes. + +- `start: number` - starting index of the selection +- `end: number` - first index after the selection's ending index; for a cursor + with no selection, `start` equals `end` + +### `.setTextAlignment()` + +```ts +setTextAlignment: ( + alignment: 'left' | 'center' | 'right' | 'justify' | 'auto' +) => void; +``` + +Sets text alignment for the paragraph(s) at the current selection. When inside a +list, the alignment is applied to all contiguous list items. See +[Text alignment](/rich-text-formatting/text-alignment) for more details. + +- `alignment` - desired text alignment; use `'auto'` to reset to the system + natural alignment + +:::note + +On Android, `'justify'` is not supported. Calling +`setTextAlignment('justify')` does not apply justified text - the paragraph ends +up with natural alignment, the same as `'auto'`. + +::: + +### `.startMention()` + +```ts +startMention: (indicator: string) => void; +``` + +Starts a mention with the given indicator at the cursor/selection. + +- `indicator: string` - indicator that starts the new mention + +### `.toggleBlockQuote()` + +```ts +toggleBlockQuote: () => void; +``` + +Toggles blockquote style at the current selection. + +### `.toggleBold()` + +```ts +toggleBold: () => void; +``` + +Toggles bold formatting at the current selection. + +### `.toggleCodeBlock()` + +```ts +toggleCodeBlock: () => void; +``` + +Toggles codeblock formatting at the current selection. + +### `.toggleH1()` + +```ts +toggleH1: () => void; +``` + +Toggles heading 1 (H1) style at the current selection. + +### `.toggleH2()` + +```ts +toggleH2: () => void; +``` + +Toggles heading 2 (H2) style at the current selection. + +### `.toggleH3()` + +```ts +toggleH3: () => void; +``` + +Toggles heading 3 (H3) style at the current selection. + +### `.toggleH4()` + +```ts +toggleH4: () => void; +``` + +Toggles heading 4 (H4) style at the current selection. + +### `.toggleH5()` + +```ts +toggleH5: () => void; +``` + +Toggles heading 5 (H5) style at the current selection. + +### `.toggleH6()` + +```ts +toggleH6: () => void; +``` + +Toggles heading 6 (H6) style at the current selection. + +### `.toggleInlineCode()` + +```ts +toggleInlineCode: () => void; +``` + +Applies inline code formatting to the current selection. + +### `.toggleItalic()` + +```ts +toggleItalic: () => void; +``` + +Toggles italic formatting at the current selection. + +### `.toggleOrderedList()` + +```ts +toggleOrderedList: () => void; +``` + +Converts the current selection into an ordered list. + +### `.toggleStrikeThrough()` + +```ts +toggleStrikeThrough: () => void; +``` + +Applies strikethrough formatting to the current selection. + +### `.toggleUnderline()` + +```ts +toggleUnderline: () => void; +``` + +Applies underline formatting to the current selection. + +### `.toggleUnorderedList()` + +```ts +toggleUnorderedList: () => void; +``` + +Converts the current selection into an unordered list. + +### `.toggleCheckboxList()` + +```ts +toggleCheckboxList: (checked: boolean) => void; +``` + +Converts the current selection into an unordered list with checkboxes as items. +Each checkbox can be checked or unchecked. The user can later toggle each +checkbox individually by tapping on it. + +- `checked: boolean` - whether the checkboxes should be checked or unchecked by + default + +See [Lists](/rich-text-formatting/lists) for more on all list types. + +## HtmlStyle type + +Allows customizing HTML styles inside the editor. + +```ts +interface HtmlStyle { + h1?: { + fontSize?: number; + bold?: boolean; + }; + h2?: { + fontSize?: number; + bold?: boolean; + }; + h3?: { + fontSize?: number; + bold?: boolean; + }; + h4?: { + fontSize?: number; + bold?: boolean; + }; + h5?: { + fontSize?: number; + bold?: boolean; + }; + h6?: { + fontSize?: number; + bold?: boolean; + }; + blockquote?: { + borderColor?: ColorValue; + borderWidth?: number; + gapWidth?: number; + color?: ColorValue; + }; + codeblock?: { + color?: ColorValue; + borderRadius?: number; + backgroundColor?: ColorValue; + }; + code?: { + color?: ColorValue; + backgroundColor?: ColorValue; + }; + a?: { + color?: ColorValue; + textDecorationLine?: 'underline' | 'none'; + }; + mention?: Record | MentionStyleProperties; + ol?: { + gapWidth?: number; + marginLeft?: number; + markerFontWeight?: TextStyle['fontWeight']; + markerColor?: ColorValue; + }; + ul?: { + bulletColor?: ColorValue; + bulletSize?: number; + marginLeft?: number; + gapWidth?: number; + }; + ulCheckbox?: { + boxColor?: ColorValue; + boxSize?: number; + marginLeft?: number; + gapWidth?: number; + }; +} + +interface MentionStyleProperties { + color?: ColorValue; + backgroundColor?: ColorValue; + textDecorationLine?: 'underline' | 'none'; +} +``` + +### h1/h2/h3/h4/h5/h6 (headings) + +- `fontSize` - size of the heading's font. Defaults to `32` for `H1`, `24` for + `H2`, `20` for `H3`, `16` for `H4`, `14` for `H5`, `12` for `H6` +- `bold` - whether the heading should be bolded; defaults to `false` + +### blockquote + +- `borderColor` - color of the rectangular border drawn to the left of + blockquote text; takes a [color](https://reactnative.dev/docs/colors) value, + defaults to `darkgray` +- `borderWidth` - width of that border; defaults to `4` +- `gapWidth` - width of the gap between the border and the blockquote text; + defaults to `16` +- `color` - color of blockquote text; takes a + [color](https://reactnative.dev/docs/colors) value; if not set, uses the + input's [color](#style) + +### codeblock + +- `color` - color of codeblock text; takes a + [color](https://reactnative.dev/docs/colors) value, defaults to `black` +- `borderRadius` - radius of the codeblock's border; defaults to `8` +- `backgroundColor` - codeblock background color; takes a + [color](https://reactnative.dev/docs/colors) value, defaults to `darkgray` + +### code (inline code) + +- `color` - color of inline code text; takes a + [color](https://reactnative.dev/docs/colors) value, defaults to `red` +- `backgroundColor` - inline code background color; takes a + [color](https://reactnative.dev/docs/colors) value, defaults to `darkgray` + +### a (link) + +- `color` - color of link text; takes a + [color](https://reactnative.dev/docs/colors) value, defaults to `blue` +- `textDecorationLine` - whether links are underlined; `'underline'` or + `'none'`, defaults to `'underline'` + +### mention + +If only a single config is given, the style applies to all mention types. You +can also set a different config for each `mentionIndicator`; then the prop +should be a record with indicators as keys and configs as values. + +- `color` - color of mention text; takes a + [color](https://reactnative.dev/docs/colors) value, defaults to `blue` +- `backgroundColor` - mention background color; takes a + [color](https://reactnative.dev/docs/colors) value, defaults to `yellow` +- `textDecorationLine` - whether mentions are underlined; `'underline'` or + `'none'`, defaults to `'underline'` + +:::tip + +You can also create a default `mention` style config, by using the `'default'` key. + +```tsx +htmlStyle={{ + mention: { + 'default': { color: '#2563eb', backgroundColor: '#dbeafe' }, + '#': { color: '#16a34a', backgroundColor: '#dcfce7' }, + }, +}} +``` + +This way you can create a style for any mention indicator to fallback if it doesn't have one fully defined. + +::: + +### ol (ordered list) + +By marker, we mean the number that denotes consecutive lines of the list. + +- `gapWidth` - gap between the marker and the list item's text; defaults to `16` +- `marginLeft` - margin to the left of the marker (between the marker and the + input's left edge); defaults to `16` +- `markerFontWeight` - font weight of the marker; takes a + [fontWeight](https://reactnative.dev/docs/text-style-props#fontweight) value; + if not set, defaults to the input's [fontWeight](#style) +- `markerColor` - text color of the marker; takes a + [color](https://reactnative.dev/docs/colors) value; if not set, defaults to + the input's [color](#style) + +### ul (unordered list) + +By bullet, we mean the dot that begins each line of the list. + +- `bulletColor` - color of the bullet; takes a + [color](https://reactnative.dev/docs/colors) value, defaults to `black` +- `bulletSize` - height and width of the bullet; defaults to `8` +- `marginLeft` - margin to the left of the bullet; defaults to `16` +- `gapWidth` - gap between the bullet and the list item's text; defaults to `16` + +### ulCheckbox (checkbox list) + +Unordered list with checkboxes instead of bullets. + +- `boxColor` - color of the checkbox; takes a + [color](https://reactnative.dev/docs/colors) value, defaults to `blue` +- `boxSize` - height and width of the checkbox; defaults to `24` +- `marginLeft` - margin to the left of the checkbox; defaults to `16` +- `gapWidth` - gap between the checkbox and the list item's text; defaults to + `16` + +## EnrichedInputStyle type + +Defines the [`style`](#style) prop's shape. This type is a subset of React +Native's [TextStyle](https://reactnative.dev/docs/text-style-props) - some +properties are not supported (for example `textAlign`, `textDecorationLine`, +`justifyContent`). Certain properties are platform-specific and include an +`@platform` directive in the type definition. + +Type definition + + ++ +## Remarks + +- The input is uncontrolled. Change content and formatting through ref methods; + observe changes through events. See + [Core concepts](/fundamentals/core-concepts#the-input-is-uncontrolled). +- Prefer [`getHTML`](#gethtml) over continuous `onChangeHtml` when you only need + HTML at specific moments. +- Sanitizing HTML is your responsibility. See + [Core concepts](/fundamentals/core-concepts#html-is-the-source-of-truth). +- For the full list of supported tags and style conflicts, see + [HTML format and supported tags](/fundamentals/html-format-and-supported-tags). diff --git a/docs/docs/api-reference/enriched-text.md b/docs/docs/api-reference/enriched-text.md new file mode 100644 index 000000000..8ea1963b8 --- /dev/null +++ b/docs/docs/api-reference/enriched-text.md @@ -0,0 +1,268 @@ +--- +sidebar_position: 2 +--- + +# EnrichedText + +`EnrichedText` is a read-only component that renders rich text from an HTML string. +It accepts the same HTML format produced by [`EnrichedTextInput`](/api-reference/enriched-text-input). + +## Reference + +```tsx +import { EnrichedText } from 'react-native-enriched-html'; + +function App() { + return ( +Type definition
+ +```ts +export interface EnrichedInputStyle { + // Layout / FlexStyle + alignSelf?: FlexStyle['alignSelf']; + aspectRatio?: number | string; + borderBottomWidth?: number; + borderEndWidth?: number; + borderLeftWidth?: number; + borderRightWidth?: number; + borderStartWidth?: number; + borderTopWidth?: number; + borderWidth?: number; + bottom?: DimensionValue; + boxSizing?: TextStyle['boxSizing']; + display?: TextStyle['display']; + end?: DimensionValue; + flex?: number; + flexBasis?: DimensionValue; + flexGrow?: number; + flexShrink?: number; + height?: DimensionValue; + inset?: DimensionValue; + insetBlock?: DimensionValue; + insetBlockEnd?: DimensionValue; + insetBlockStart?: DimensionValue; + insetInline?: DimensionValue; + insetInlineEnd?: DimensionValue; + insetInlineStart?: DimensionValue; + left?: DimensionValue; + margin?: DimensionValue; + marginBlock?: DimensionValue; + marginBlockEnd?: DimensionValue; + marginBlockStart?: DimensionValue; + marginBottom?: DimensionValue; + marginEnd?: DimensionValue; + marginHorizontal?: DimensionValue; + marginInline?: DimensionValue; + marginInlineEnd?: DimensionValue; + marginInlineStart?: DimensionValue; + marginLeft?: DimensionValue; + marginRight?: DimensionValue; + marginStart?: DimensionValue; + marginTop?: DimensionValue; + marginVertical?: DimensionValue; + maxHeight?: DimensionValue; + maxWidth?: DimensionValue; + minHeight?: DimensionValue; + minWidth?: DimensionValue; + padding?: DimensionValue; + paddingBlock?: DimensionValue; + paddingBlockEnd?: DimensionValue; + paddingBlockStart?: DimensionValue; + paddingBottom?: DimensionValue; + paddingEnd?: DimensionValue; + paddingHorizontal?: DimensionValue; + paddingInline?: DimensionValue; + paddingInlineEnd?: DimensionValue; + paddingInlineStart?: DimensionValue; + paddingLeft?: DimensionValue; + paddingRight?: DimensionValue; + paddingStart?: DimensionValue; + paddingTop?: DimensionValue; + paddingVertical?: DimensionValue; + position?: FlexStyle['position']; + right?: DimensionValue; + start?: DimensionValue; + top?: DimensionValue; + width?: DimensionValue; + zIndex?: number; + + // Shadows + /** @platform ios */ + shadowColor?: ColorValue; + /** @platform ios */ + shadowOffset?: TextStyle['shadowOffset']; + /** @platform ios */ + shadowOpacity?: TextStyle['shadowOpacity']; + /** @platform ios */ + shadowRadius?: number; + + // Transforms + transform?: TextStyle['transform']; + transformOrigin?: TextStyle['transformOrigin']; + + // View appearance + /** @platform ios web */ + backfaceVisibility?: TextStyle['backfaceVisibility']; + backgroundColor?: ColorValue; + /** @platform ios web */ + borderBlockColor?: ColorValue; + /** @platform ios web */ + borderBlockEndColor?: ColorValue; + /** @platform ios web */ + borderBlockStartColor?: ColorValue; + /** @platform ios web */ + borderBottomColor?: ColorValue; + /** @platform ios web */ + borderBottomEndRadius?: TextStyle['borderBottomEndRadius']; + /** @platform ios web */ + borderBottomLeftRadius?: TextStyle['borderBottomLeftRadius']; + /** @platform ios web */ + borderBottomRightRadius?: TextStyle['borderBottomRightRadius']; + /** @platform ios web */ + borderBottomStartRadius?: TextStyle['borderBottomStartRadius']; + /** @platform ios web */ + borderColor?: ColorValue; + /** @platform ios web */ + borderEndColor?: ColorValue; + /** @platform ios web */ + borderEndEndRadius?: TextStyle['borderEndEndRadius']; + /** @platform ios web */ + borderEndStartRadius?: TextStyle['borderEndStartRadius']; + /** @platform ios web */ + borderLeftColor?: ColorValue; + /** @platform ios web */ + borderRadius?: TextStyle['borderRadius']; + /** @platform ios web */ + borderRightColor?: ColorValue; + /** @platform ios web */ + borderStartColor?: ColorValue; + /** @platform ios web */ + borderStartEndRadius?: TextStyle['borderStartEndRadius']; + /** @platform ios web */ + borderStartStartRadius?: TextStyle['borderStartStartRadius']; + /** @platform ios web */ + borderStyle?: TextStyle['borderStyle']; + /** @platform ios web */ + borderTopColor?: ColorValue; + /** @platform ios web */ + borderTopEndRadius?: TextStyle['borderTopEndRadius']; + /** @platform ios web */ + borderTopLeftRadius?: TextStyle['borderTopLeftRadius']; + /** @platform ios web */ + borderTopRightRadius?: TextStyle['borderTopRightRadius']; + /** @platform ios web */ + borderTopStartRadius?: TextStyle['borderTopStartRadius']; + boxShadow?: TextStyle['boxShadow']; + /** @platform web */ + cursor?: TextStyle['cursor']; + /** @platform android */ + elevation?: number; + /** @platform android web */ + filter?: TextStyle['filter']; + /** @platform android web */ + mixBlendMode?: TextStyle['mixBlendMode']; + opacity?: TextStyle['opacity']; + /** @platform ios web */ + outlineColor?: ColorValue; + outlineOffset?: TextStyle['outlineOffset']; + /** @platform android web */ + outlineStyle?: TextStyle['outlineStyle']; + outlineWidth?: TextStyle['outlineWidth']; + /** @platform ios web */ + pointerEvents?: TextStyle['pointerEvents']; + + // Typography + color?: ColorValue; + fontFamily?: string; + fontSize?: number; + fontStyle?: TextStyle['fontStyle']; + fontWeight?: TextStyle['fontWeight']; + lineHeight?: number; + /** @platform web */ + letterSpacing?: number; +} +``` + ++ {' + ); +} +``` + +Hello world
'} +++ +## Props + +All props are optional except `children`. + +### `allowFontScaling` {#allowfontscaling} + +If `true`, the text respects the system's accessibility font scaling settings. + +| Type | Default | Platforms | +| --------- | ------- | ------------ | +| `boolean` | `true` | Android, iOS | + +### `children`Type definitions
+ +```ts +interface EnrichedTextProps extends ViewProps { + ref?: RefObject; + children: string; + style?: TextStyle; + htmlStyle?: EnrichedTextHtmlStyle; + useHtmlNormalizer?: boolean; + sanitizationConfig?: SanitizationConfig; + ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip'; + numberOfLines?: number; + selectable?: boolean; + selectionColor?: ColorValue; + allowFontScaling?: boolean; + onLinkPress?: (event: OnLinkPressEvent) => void; + onMentionPress?: (event: OnMentionPressEvent) => void; +} +``` + + {#children} + +The HTML string to render. Accepts the HTML format produced by +[`EnrichedTextInput`](/api-reference/enriched-text-input). See +[Supported tags](/fundamentals/html-format-and-supported-tags) for the full tag set. + +| Type | Default | Platforms | +| -------- | ------- | ----------------- | +| `string` | - | Android, iOS, Web | + +### `style` {#style} + +Standard React Native `TextStyle` applied to the text. + +| Type | Default | Platforms | +| ----------- | ------- | ----------------- | +| `TextStyle` | - | Android, iOS, Web | + +### `htmlStyle` {#htmlstyle} + +Customizes styles of HTML elements, including press colors for interactive +elements. See [`EnrichedTextHtmlStyle`](#enrichedtexthtmlstyle-type). + +| Type | Default | Platforms | +| ----------------------- | ------- | ----------------- | +| `EnrichedTextHtmlStyle` | - | Android, iOS, Web | + +### `useHtmlNormalizer` {#usehtmlnormalizer} + +If `true`, external HTML (for example from Google Docs, Word, or web pages) is +normalized before rendering. This converts arbitrary HTML into the canonical +tag subset that the enriched parser understands. See +[Normalization](/fundamentals/core-concepts#normalization). + +| Type | Default | Platforms | +| --------- | ------- | ----------------- | +| `boolean` | `true` | Android, iOS, Web | + +### `sanitizationConfig` {#sanitizationconfig} + +Web-only configuration for the HTML sanitization step applied to `children` +before rendering. + +```ts +interface SanitizationConfig { + linkRegex?: RegExp; +} +``` + +- `linkRegex` - a regular expression deciding which link URIs survive + sanitization. + +:::caution + +`linkRegex` maps directly to DOMPurify's [`ALLOWED_URI_REGEXP`](https://github.com/cure53/DOMPurify#can-i-configure-dompurify), so it **replaces** the default allow-list rather than extending it. Because this regex affects all URI-containing attributes (e.g. `src` in ` `), remember to keep the standard protocols you still want to permit. + +::: + +```tsx +
+ {''} + +``` + +| Type | Default | Platforms | +| -------------------- | ------- | --------- | +| `SanitizationConfig` | - | Web | + +### `ellipsizeMode` {#ellipsizemode} + +How the text should be truncated when `numberOfLines` is set and the text overflows. + +- `head` - truncates at the beginning, e.g. `...wxyz` +- `middle` - truncates in the middle, e.g. `ab...yz` +- `tail` - truncates at the end, e.g. `abcd...` +- `clip` - clips the text without inserting an ellipsis + +| Type | Default | Platforms | +| ---------------------------------------- | -------- | ------------ | +| `'head' \| 'middle' \| 'tail' \| 'clip'` | `'tail'` | Android, iOS | + +:::note + +On Android, when `numberOfLines` is set to a value higher than `1`, only +`tail` works correctly. + +::: + +### `numberOfLines` {#numberoflines} + +Limits the number of displayed lines. Set to `0` for unlimited lines. + +| Type | Default | Platforms | +| -------- | ------- | ------------ | +| `number` | `0` | Android, iOS | + +### `selectable` {#selectable} + +If `true`, the text can be selected by the user (for example for copy/paste). + +| Type | Default | Platforms | +| --------- | ------- | ----------------- | +| `boolean` | `false` | Android, iOS, Web | + +### `selectionColor` {#selectioncolor} + +The color of the text selection highlight. + +| Type | Default | Platforms | +| ---------------------------------------------- | -------------- | ----------------- | +| [`color`](https://reactnative.dev/docs/colors) | system default | Android, iOS, Web | + +### `onLinkPress` {#onlinkpress} + +Called when the user presses a link element. Receives an `OnLinkPressEvent` +containing the link's URL. See [Links](/rich-text-formatting/links) for more +on how links are created and detected. + +```ts +interface OnLinkPressEvent { + url: string; +} +``` + +| Type | Platforms | +| ----------------------------------- | ----------------- | +| `(event: OnLinkPressEvent) => void` | Android, iOS, Web | + +### `onMentionPress` {#onmentionpress} + +Called when the user presses a mention element. Receives an +`OnMentionPressEvent` with the mention's text, indicator character, and custom +attributes. See [Mentions](/rich-text-formatting/mentions) for more on how +mentions are created. + +```ts +interface OnMentionPressEvent { + text: string; + indicator: string; + attributes: Record; +} +``` + +| Type | Platforms | +| -------------------------------------- | ----------------- | +| `(event: OnMentionPressEvent) => void` | Android, iOS, Web | + +## EnrichedTextHtmlStyle type + +Extends [`HtmlStyle`](/api-reference/enriched-text-input#htmlstyle-type) with +additional press-state styling for interactive elements. All properties from +`HtmlStyle` are supported except `a` and `mention`, which are replaced by the +extended versions below. + +```ts +interface EnrichedTextHtmlStyle extends Omit { + a?: { + color?: ColorValue; + textDecorationLine?: 'underline' | 'none'; + pressColor?: ColorValue; + }; + mention?: + | Record + | EnrichedTextMentionStyleProperties; +} + +interface EnrichedTextMentionStyleProperties { + color?: ColorValue; + backgroundColor?: ColorValue; + textDecorationLine?: 'underline' | 'none'; + pressColor?: ColorValue; + pressBackgroundColor?: ColorValue; +} +``` + +### a (link) + +Inherits all properties from [`HtmlStyle`'s `a`](/api-reference/enriched-text-input#a-link) +and adds: + +- `pressColor` - the color of the link text while it is being pressed. Takes a + [color](https://reactnative.dev/docs/colors) value. + +### mention + +Inherits all properties from +[`HtmlStyle`'s `mention`](/api-reference/enriched-text-input#mention) and adds: + +- `pressColor` - the color of the mention text while it is being pressed. Takes + a [color](https://reactnative.dev/docs/colors) value. +- `pressBackgroundColor` - the background color of the mention while it is being + pressed. Takes a [color](https://reactnative.dev/docs/colors) value. + +Same as in `HtmlStyle`, if only a single config is given the style applies to +all mention types. To style each indicator separately, pass a record with +indicators as keys and configs as values. Use the `'default'` key for a fallback +style applied to any indicator without its own config. + +## Remarks + +- `EnrichedText` is read-only. Use [`EnrichedTextInput`](/api-reference/enriched-text-input) + when the user needs to edit content. +- Sanitizing HTML is your responsibility. See + [Core concepts](/fundamentals/core-concepts#html-is-the-source-of-truth). diff --git a/docs/docs/core-functionalities/_category_.json b/docs/docs/core-functionalities/_category_.json new file mode 100644 index 000000000..602a6243b --- /dev/null +++ b/docs/docs/core-functionalities/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "Core functionalities", + "position": 30, + "link": { + "type": "generated-index" + } +} diff --git a/docs/docs/core-functionalities/handling-input-events.md b/docs/docs/core-functionalities/handling-input-events.md new file mode 100644 index 000000000..e4f23f6e5 --- /dev/null +++ b/docs/docs/core-functionalities/handling-input-events.md @@ -0,0 +1,67 @@ +--- +sidebar_position: 3 +--- + +# Handling input events + +Since the input is [uncontrolled](/fundamentals/core-concepts#the-input-is-uncontrolled), +events are how you observe it. You change content by calling ref methods; you +react to changes by listening to the callbacks below. + +Full payload shapes of the available callbacks can be found in the [`EnrichedTextInput`](/api-reference/enriched-text-input#props) reference. + +:::note + +This page covers `EnrichedTextInput` events. The read-only +[`EnrichedText`](/core-functionalities/rendering-rich-text) component only +exposes `onLinkPress` and `onMentionPress` callbacks. + +::: + +## Content + +- **`onChangeText`** - plain-text content changed. +- **`onChangeHtml`** - the HTML changed. + +:::tip + +The `onChangeHtml` callback has to parse the content into HTML on every keystroke. +This is a heavy computational operation that might slow down your app's performance. Consider using the `getHTML()` ref method instead if it meets your requirements. + +::: + +## Selection and style state + +- **`onChangeSelection`** - the cursor moved or the selection changed. Gives you + `start`, `end`, and the selected `text`. Useful for range-based methods like + [`setLink`](/rich-text-formatting/links). +- **`onChangeState`** - the active styles at the cursor changed. This is the + event that drives a toolbar by using reported `isActive`, `isBlocking`, and + `isConflicting`, plus the current `alignment`. See the + [style state model](/fundamentals/core-concepts#the-style-state-model). + +## Focus + +- **`onFocus`** / **`onBlur`** - the input gained or lost focus. + +## Mentions + +- **`onStartMention`** - a mention started being edited. +- **`onChangeMention`** - the query after the indicator changed. +- **`onEndMention`** - editing a mention stopped. +- **`onMentionDetected`** - the cursor entered or left a mention. + +## Links + +- **`onLinkDetected`** - the cursor entered or left a link. + +## Images + +- **`onPasteImages`** - the user pasted one or more images; hands you each + image's data so you can upload and insert them with + [`setImage`](/rich-text-formatting/inline-images). + +## Keyboard and submission + +- **`onKeyPress`** - a key was pressed. +- **`onSubmitEditing`** - the user pressed return/enter key. Fired when [`submitBehavior`](/api-reference/enriched-text-input#submitbehavior) is set to either `submit` or `blurAndSubmit`. diff --git a/docs/docs/core-functionalities/rendering-rich-text.mdx b/docs/docs/core-functionalities/rendering-rich-text.mdx new file mode 100644 index 000000000..0a26a9dba --- /dev/null +++ b/docs/docs/core-functionalities/rendering-rich-text.mdx @@ -0,0 +1,82 @@ +--- +sidebar_position: 2 +--- + +import InteractiveExample from '@site/src/components/InteractiveExample'; +import RenderingEditor from '@site/src/examples/RenderingEditor'; +import RenderingEditorSrc from '!!raw-loader!@site/src/examples/RenderingEditor'; + +# Rendering rich text + +`EnrichedTextInput` is for editing. To _display_ rich text without an editor - +a chat message, a comment, an article - use its read-only counterpart, +**`EnrichedText`**. + +Both components speak the same [HTML format](/fundamentals/html-format-and-supported-tags), +so the typical flow is: edit in `EnrichedTextInput`, persist the +`getHTML` output, and later feed that +string to `EnrichedText`. + +## Passing content + +`EnrichedText` takes the HTML string as its `children`: + +```tsx +import { EnrichedText } from 'react-native-enriched-html'; + + {' ; +``` + +## Styling + +Styling mirrors the input. `style` controls the container and base typography, +and `htmlStyle` controls per-element appearance. `EnrichedText` extends +`htmlStyle` with **press states** for interactive elements, since links and +mentions are pressable here: + +```tsx +Hello world
'}+ {html} + +``` + +The added `pressColor` / `pressBackgroundColor` fields on `a` and `mention` are +the only shape difference from the input's `htmlStyle`. See the +[`EnrichedText`](/api-reference/enriched-text) reference for the full type. + +## Notable props + +- **`selectable`** - allow the user to select and copy the rendered text. + Defaults to `false`. +- **`onLinkPress` / `onMentionPress`** - fire when a link or mention is pressed. +- **`numberOfLines` / `ellipsizeMode`** - truncate long content to a fixed + number of lines with an ellipsis. +- **`useHtmlNormalizer`** - normalize external or messy HTML into the library's canonical + tag subset before rendering. Defaults to `true`. See + [Normalization](/fundamentals/core-concepts#normalization). +- **`allowFontScaling`** - whether to respect the system's accessibility font scaling settings. + +:::note + +On web, the default behavior of the pressed `` tag is suppressed. To navigate to the link's URL, you need to properly handle the `onLinkPress` event. + +::: + +## Try it out + +Format some text in the editor, then press **Render** - the current HTML is read +with `getHTML()` and handed to an `EnrichedText` below. + ++ +:::caution + +On iOS and Android, `EnrichedText` does not sanitize HTML for you. Sanitize anything you render that +came from users or other untrusted sources. To know more about the web's built-in sanitization, visit [Web support](/core-functionalities/web-support#sanitization). + +::: diff --git a/docs/docs/core-functionalities/styling-the-input.mdx b/docs/docs/core-functionalities/styling-the-input.mdx new file mode 100644 index 000000000..26ba3c24a --- /dev/null +++ b/docs/docs/core-functionalities/styling-the-input.mdx @@ -0,0 +1,110 @@ +--- +sidebar_position: 1 +--- + +# Styling the input + +`EnrichedTextInput` is styled through two separate props. Together they cover +everything from the container's dimensions down to the color of a bullet point. + +- **`style`** - the container's layout behavior and its base typography (`fontSize`, `color`, `fontFamily`, …). It accepts a subset of React Native's `TextStyle`, described by + `EnrichedInputStyle`. +- **`htmlStyle`** - the appearance of individual rich text elements: heading + sizes, blockquote borders, code colors, list markers, mention colors, and so + on. +- **`placeholderTextColor`** - the color of the placeholder text. +- **`selectionColor`** - the color of the text selection highlight. +- **`cursorColor`** - the color of the text cursor. + +:::note + +`cursorColor` is not supported on iOS. For more platform differences, see [Compatibility](/misc/compatibility). + +::: + +Here's an interactive live example - edit the `style` and `htmlStyle` values below and the preview updates live. +You can also check the full API reference for `htmlStyle` and `style`, come back and experiment around here. + +```jsx live +function StylingExample() { + return ( + + ); +} +``` + +## `style` + +`style` accepts a subset of React Native's `TextStyle` - layout, appearance, +and base typography - described by `EnrichedInputStyle`. Most of these map directly +to their React Native `TextStyle` counterparts. Some are platform-limited +(e.g. `shadowColor` is iOS-only, `elevation` is Android-only) - see the +[`EnrichedTextInput`](/api-reference/enriched-text-input#style) reference for the full property list. + +## `htmlStyle` + +`htmlStyle` maps each supported element to a small config object. Anything you +omit falls back to the built-in default. The available keys are: + +| Key | Styles | Notable options | +| ------------ | -------------- | ----------------------------------------------------------- | +| `h1`–`h6` | Headings | `fontSize`, `bold` | +| `blockquote` | Blockquote | `borderColor`, `borderWidth`, `gapWidth`, `color` | +| `codeblock` | Code block | `color`, `backgroundColor`, `borderRadius` | +| `code` | Inline code | `color`, `backgroundColor` | +| `a` | Links | `color`, `textDecorationLine` | +| `mention` | Mentions | `color`, `backgroundColor`, `textDecorationLine` | +| `ol` | Ordered list | `markerColor`, `markerFontWeight`, `marginLeft`, `gapWidth` | +| `ul` | Unordered list | `bulletColor`, `bulletSize`, `marginLeft`, `gapWidth` | +| `ulCheckbox` | Checkbox list | `boxColor`, `boxSize`, `marginLeft`, `gapWidth` | + +The full list of properties, defaults, and platform notes lives in the +[`EnrichedTextInput`](/api-reference/enriched-text-input#htmlstyle) reference. + +### Styling mentions per indicator + +`mention` accepts either a single config applied to every mention, or a record +keyed by [indicator](/rich-text-formatting/mentions) so each mention type gets +its own look: + +```tsx +htmlStyle={{ + mention: { + '@': { color: '#2563eb', backgroundColor: '#dbeafe' }, + '#': { color: '#16a34a', backgroundColor: '#dcfce7' }, + }, +}} +``` + +:::tip + +You can also create a default `mention` style config, by using the `'default'` key. + +```tsx +htmlStyle={{ + mention: { + 'default': { color: '#2563eb', backgroundColor: '#dbeafe' }, + '#': { color: '#16a34a', backgroundColor: '#dcfce7' }, + }, +}} +``` + +This way you can create a style for any mention indicator to fallback if it doesn't have one fully defined. + +::: diff --git a/docs/docs/core-functionalities/web-support.md b/docs/docs/core-functionalities/web-support.md new file mode 100644 index 000000000..dd82cfe3b --- /dev/null +++ b/docs/docs/core-functionalities/web-support.md @@ -0,0 +1,94 @@ +--- +sidebar_position: 4 +--- + +# Web support + +Both `EnrichedTextInput` and `EnrichedText` run on the web. On native the editor +is backed by the platform's text engine; on the web it is built on +[Tiptap](https://tiptap.dev/). That implementation +detail stays behind the same public API. + +## One API across platforms + +The web build exposes the same core API as native - **props, ref methods, and events** - +with one web-only addition - `sanitizationConfig` prop. +Events keep their native shape too - they arrive as +`NativeSyntheticEvent`, read off `e.nativeEvent`, so +[event-handling](/core-functionalities/handling-input-events) code is portable as-is: + +```tsx + setHtml(e.nativeEvent.value)} + onChangeState={e => setState(e.nativeEvent)} +/> +``` + +The interactive examples throughout these docs are the web build running live. + +## Keyboard shortcuts + +The web editor ships desktop-style formatting shortcuts out of the box, along +with native browser **undo/redo**. + +| Action | macOS | Windows / Linux | +| ------------------- | -------------- | --------------------------- | +| Bold | `⌘B` | `Ctrl+B` | +| Italic | `⌘I` | `Ctrl+I` | +| Underline | `⌘U` | `Ctrl+U` | +| Strikethrough | `⌘⇧X` | `Ctrl+Shift+X` | +| Inline code | `⌘⇧C` | `Ctrl+Shift+C` | +| Code block | `⌘⌥⇧C` | `Ctrl+Alt+Shift+C` | +| Normal paragraph | `⌘⌥0` | `Ctrl+Alt+0` | +| Heading 1–6 | `⌘⌥1` – `⌘⌥6` | `Ctrl+Alt+1` – `Ctrl+Alt+6` | +| Numbered list | `⌘⇧7` | `Ctrl+Shift+7` | +| Unordered list | `⌘⇧8` | `Ctrl+Shift+8` | +| Checkbox list | `⌘⇧9` | `Ctrl+Shift+9` | +| Paste as plain text | `⌘⇧V` | `Ctrl+Shift+V` | +| Undo | `⌘Z` | `Ctrl+Z` | +| Redo | `⌘⇧Z` | `Ctrl+Shift+Z` | +| Select all | `⌘A` | `Ctrl+A` | + +## Platform differences + +A few native-only features have no web equivalent and are ignored there: + +- **`contextMenuItems`** - the native editing menu isn't available; use your own + UI instead. +- **`returnKeyLabel`** - can't be set inside a browser. `returnKeyType` maps to + the browser's [`enterkeyhint`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint). +- **RN layout ref methods** - `measure`, `measureInWindow`, `measureLayout`, and + `setNativeProps` are no-ops. + +The [`EnrichedTextInput`](/api-reference/enriched-text-input) and +[`EnrichedText`](/api-reference/enriched-text) references note per-prop platform +support. + +:::note + +On web, `onPasteImages` gives each image a `blob:` URL. If you hold onto those +URIs, call `URL.revokeObjectURL(uri)` once you're done with them (e.g. after an +upload) so the browser can release the memory. + +::: + +## Sanitization + +Unlike the native platforms, the web build sanitizes HTML for you. It runs +[DOMPurify](https://github.com/cure53/DOMPurify) at **every entrypoint** - the +`children` of `EnrichedText`, `defaultValue`, `setValue` and content pasted into `EnrichedTextInput`. Sanitization is also run on the input component's **output** - `getHTML()`. +This ensures untrusted markup can't inject scripts or unsafe attributes into the DOM. + +:::note + +Sanitization can be customized with the `sanitizationConfig` prop. It allows you to set a `linkRegex` that +will define, which URI-containing attributes will actually be preserved by the sanitizer. + +::: + +## Server-side rendering + +The library does **not** support SSR yet. Normalization and sanitization both need a +DOM to work against, which isn't available during server rendering. In an SSR +framework (Next.js, Remix, …), make sure both `EnrichedTextInput` and +`EnrichedText` render **client-side only**. diff --git a/docs/docs/fundamentals/_category_.json b/docs/docs/fundamentals/_category_.json new file mode 100644 index 000000000..61e8f6f9a --- /dev/null +++ b/docs/docs/fundamentals/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "Fundamentals", + "position": 10, + "link": { + "type": "generated-index" + } +} diff --git a/docs/docs/fundamentals/core-concepts.md b/docs/docs/fundamentals/core-concepts.md new file mode 100644 index 000000000..52fd5593b --- /dev/null +++ b/docs/docs/fundamentals/core-concepts.md @@ -0,0 +1,89 @@ +--- +sidebar_position: 3 +--- + +# Core concepts + +You've built an editor. Before going on, it's worth understanding the few +ideas the whole library is built on. They explain why the API looks the way it +does and help you understand further chapters better. + +## The input is uncontrolled + +`EnrichedTextInput` does not take its content from a prop and does not push +every keystroke back into React state. It owns its content and state on the native side +and you talk to it through a `ref`. + +This is deliberate. Rich text changes constantly - every character, selection +move and style toggle - and round-tripping all of that through JavaScript state +would be extremely slow and open to a possible de-synchronization of those states +between the JS and native side. Keeping it in the native makes the editor fast and stable. + +In practice this means: + +- To **change** the content or formatting, call a method on the ref, e.g. `ref.current?.toggleBold()`, `ref.current?.setValue(html)`, or `ref.current?.setLink(...)`. +- To **observe** changes to the content or formatting, listen to events such as `onChangeState`, `onChangeHtml`, or `onChangeSelection`. + +You never set a `value` prop and re-render to make an edit happen. + +:::note + +We have one value-setting prop - `defaultValue`. This provides the editor's initial value upon creation, +so we don't violate the _uncontrolled_ nature of the component here. + +::: + +## HTML is the source of truth + +The editor's content is HTML. `setValue` and `defaultValue` seeds it with an HTML string, +`getHTML` reads the current content back, and `onChangeHtml` streams it as it +changes. What you store and what you render is a string of HTML. + +The library uses a fixed set of standard and custom tags, so the output is +predictable and portable. [Supported tags](/fundamentals/html-format-and-supported-tags) +lists exactly what it produces and accepts. + +:::caution + +Sanitizing HTML is your responsibility. The library doesn't guarantee safe HTML on mobile, so +sanitize anything you persist, render elsewhere, or accept from untrusted +sources. To learn how sanitization is handled on Web, see [Web support](/core-functionalities/web-support). + +::: + +## Normalization + +HTML can be messy. Whether users paste rich text from applications like Google Docs or Microsoft Word, or you inject external HTML via `defaultValue`, `setValue`, or pass it directly as `children` into `EnrichedText`, the markup often contains additional wrapper elements, inline styles, and structural quirks that may not match the HTML structure expected by the library. + +To handle this, both components provide a `useHtmlNormalizer` prop that normalizes incoming HTML. The normalizer cleans and restructures the input into the predictable format the library relies on (e.g., it maps `` to ``, unwraps ` ` containers into `` tags, and strips unsupported tags). The `useHtmlNormalizer` prop defaults to `true`. + +All supported and canonical tags are listed in [Supported tags](/fundamentals/html-format-and-supported-tags). + +## Two components, one HTML format + +The library is split into an editor and a display: + +- **`EnrichedTextInput`** - an interactive editor that emits HTML. +- **`EnrichedText`** - a read-only display component that renders the input's + HTML. + +The HTML format that both components expect is identical, what allows you to integrate them seamlessly. A common setup edits in `EnrichedTextInput`, stores the `getHTML` output, and later displays it with `EnrichedText`. + +## The style state model + +Not every style can be combined with every other. For example, a paragraph can't be both a heading and a list item, code blocks don't support inline formatting such as bold or italic. The editor tracks this and reports it through `onChangeState`, which gives each style three booleans: + +- **`isActive`** - whether the style is applied at the current selection. Use it to + highlight a toolbar button. +- **`isBlocking`** - whether another active style forbids this one entirely, so toggling + it would do nothing. For example, bold is blocked inside a code block. Use it + to disable a toolbar button. +- **`isConflicting`** - whether this style would replace an active one if toggled on. + For example, switching a blockquote paragraph to a heading removes the + blockquote. Use it to hint that the toggle is a swap, not an addition. + +Driving your toolbar from these three flags keeps the UI honest: buttons light +up and grey out according to the editor's state. + +For the comprehensive list of which styles block or conflict with each other, see +[Supported tags](/fundamentals/html-format-and-supported-tags). diff --git a/docs/docs/fundamentals/getting-started.md b/docs/docs/fundamentals/getting-started.md new file mode 100644 index 000000000..563639f5d --- /dev/null +++ b/docs/docs/fundamentals/getting-started.md @@ -0,0 +1,131 @@ +--- +slug: / +sidebar_position: 1 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Getting started + +The goal of the _Fundamentals_ section is to get you from an empty project to a +working rich text editor and to give you the mental model you need to build on +your own. Let's dive in. + +## What is React Native Enriched HTML? + +React Native Enriched HTML is a rich text solution for React Native built by +[Software Mansion](https://swmansion.com/). It supports iOS, Android and Web. + +It ships two fully native components: `EnrichedTextInput`, a rich text editor +that styles text live as you type, and `EnrichedText`, a read-only display component that +renders the input's output. Both speak HTML - the input +produces it, the display consumes it. + +Not only does this library allow you to apply basic rich text styles you know well, +like _bold_ or _italic_, but also provides more powerful tools like _mentions_, something +you will learn more about in the next chapters. + +## Prerequisites + +The library works only with the +[React Native New Architecture (Fabric)](https://reactnative.dev/architecture/landing-page). +It's enabled by default on recent React Native versions; if your app still runs +the old architecture you'll need to switch before installing. + +For the exact React Native versions we support, see +[Compatibility](/misc/compatibility). + +## Installation + +### Bare React Native + +Install the package: + +
+ + +The library contains native code, so rebuild your app after installing. On iOS, +install the pods first: + +```sh +cd ios && pod install +``` + +### Expo + +Install with the Expo CLI so the correct version is picked for your SDK: + +```sh +npx expo install react-native-enriched-html +``` + +Then regenerate the native projects: + +```sh +npx expo prebuild +``` + +:::note + +The library needs native code, so it won't run in Expo Go. Use a +[development build](https://docs.expo.dev/develop/development-builds/introduction/) +instead. + +::: + +### Web + +Install the package: + ++ ```bash + npm install react-native-enriched-html + ``` + + ++ ```bash + yarn add react-native-enriched-html + ``` + ++ + +The web version should work right out of the box. Check the details in the Web Support section. + +## Nightly builds + +To try features before they land in a stable release, install the nightly +build: + ++ ```bash + npm install react-native-enriched-html + ``` + + ++ ```bash + yarn add react-native-enriched-html + ``` + ++ + +Nightlies are published to npm automatically and may contain breaking changes. + +--- + +You're set up. Next, let's [build your first editor](/fundamentals/your-first-editor). diff --git a/docs/docs/fundamentals/html-format-and-supported-tags.md b/docs/docs/fundamentals/html-format-and-supported-tags.md new file mode 100644 index 000000000..962dae581 --- /dev/null +++ b/docs/docs/fundamentals/html-format-and-supported-tags.md @@ -0,0 +1,79 @@ +--- +sidebar_position: 4 +--- + +# HTML format and supported tags + +The provided components work with a fixed set of standard and custom HTML tags - the editor produces them and they can be used as input to both the editor and the display component. This page is the reference for that set. + +Styles fall into two groups: **inline** tags that wrap a range of characters +and **paragraph** tags that apply to whole paragraphs (**a paragraph might span a few lines!**). Not all of them combine +freely, and there are two kinds of restriction: + +- **Conflicting** - toggling a style that conflicts with an active one replaces + it. Toggling `+ ```bash + npm install react-native-enriched-html@nightly + ``` + + ++ ```bash + yarn add react-native-enriched-html@nightly + ``` + +` on a `
` paragraph removes the blockquote and + applies the heading. +- **Blocking** - a blocked style can't be toggled at all while the blocking + style is active. `` is blocked inside ``, so bold can't be + applied there. + +Both show up in the [`onChangeState`](/fundamentals/core-concepts#the-style-state-model) payload as +`isConflicting` and `isBlocking`. + +## Inline tags + +| Style | HTML tag | Conflicts with | Blocked by | +| ------------- | ----------- | ---------------------------- | ---------------------- | +| Bold | `` | -- | ` ` | +| Italic | `` | -- | ` ` | +| Underline | `` | -- | ` ` | +| Strikethrough | ` ` | -- | `` | +| Inline code | ` ` | ``, `` | ` `, ` ` | +| Link | `` | `
`, ``, `` | ` `, ` ` | +| Mention | `
` | ` `, `` | ``, ` ` | +| Image | `
` | ``, `
` | ` ` | + +:::note + +Headings also block bold when `bold: true` is set on the heading in the +`htmlStyle` prop. The heading already renders as bold, so toggling bold on top +of it is redundant and therefore blocked. + +::: + +## Paragraph tags + +Only one paragraph-level style can be active per paragraph - they all conflict +with each other. + +Some paragraph styles are containers that wrap each line inside them with an +**inner content tag**. Each line of a `` is wrapped in `
- `, and each line +of a `
` is wrapped in ` `. + +| Style | HTML tag | Inner content tag | Conflicts with | Blocked by | +| -------------- | --------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| Heading 1 | `
` | -- | `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `
`, `` | -- | +| Heading 2 | ` ` | -- | `
`, `
`, `
`, `
`, `