diff --git a/docs/docs/misc/comparison-with-other-libraries.md b/docs/docs/misc/comparison-with-other-libraries.md index 939389b68..1e08befa5 100644 --- a/docs/docs/misc/comparison-with-other-libraries.md +++ b/docs/docs/misc/comparison-with-other-libraries.md @@ -4,4 +4,62 @@ sidebar_position: 2 # Comparison with other libraries - +React Native doesn't ship a built-in rich text component, so a number of +community libraries exist. They fall into two broad categories: **native +implementations** that render directly through the platform's text system, and +**WebView wrappers** that embed a browser-based editor. This page compares +React Native Enriched HTML with the most popular alternatives in each +category. + +## Native solutions + +### React Native Aztec + +[React Native Aztec](https://github.com/wordpress-mobile/react-native-aztec) +was WordPress's native rich text component for Gutenberg Mobile. The repository +was **archived in March 2025** and the code folded into the +[Gutenberg monorepo](https://github.com/WordPress/gutenberg), where activity +is focused on the WordPress editor itself rather than the standalone React +Native wrapper. Aztec **does not support the New Architecture (Fabric)** and is +dual-licensed under **GPL-2.0 / MPL-2.0** - both are copyleft licenses that +may require you to distribute your own source code under the same terms, +which can be a deal-breaker for proprietary apps. + +### React Native Enriched Markdown + +[React Native Enriched Markdown](https://github.com/software-mansion/react-native-enriched-markdown) +is another fully native library from [Software Mansion](https://swmansion.com) (MIT-licensed, New +Architecture required). It focuses on **Markdown** rather than HTML: + +- **Text component** - renders a wide range of Markdown features natively, + including GFM tables, task lists, LaTeX math and spoiler text. +- **Text input** - provides a rich editing experience with Markdown output. + +The key difference is the **content format**. Enriched Markdown targets apps +that work with Markdown end-to-end, while Enriched HTML is built for apps that +need an **HTML-based editor with advanced, customisable formatting +capabilities** and a matching display component. The two libraries can coexist in one app if you need both formats. + +## WebView-based solutions + +### 10tap Editor + +[10tap Editor](https://github.com/10play/10tap-editor) wraps +[TipTap](https://tiptap.dev) (ProseMirror) inside a React Native WebView. + +### React Native Pell Rich Editor + +[React Native Pell Rich Editor](https://github.com/wxik/react-native-rich-editor) +is a lightweight WebView-based editor. + +### Why native matters + +All WebView-based editors share a set of inherent trade-offs compared to a +fully native approach: + +| | Native (Enriched HTML) | WebView editors | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Input latency** | Text goes through the platform's native text system - every keystroke, selection change and style toggle is processed without leaving the native layer. | Every interaction crosses the React Native ↔ WebView bridge, adding measurable latency, especially on lower-end devices. | +| **Platform integration** | Full access to native APIs: system context menus, autocorrect / predictive text, VoiceOver / TalkBack, drag-and-drop, hardware keyboards. | Limited by what the WebView exposes; features like custom context-menu items or native accessibility trees require extra workarounds or are simply unavailable. | +| **Memory & startup** | No WebView process to spin up - the component is just another native view in the hierarchy. | Each editor instance spawns a WebView, increasing memory use and adding a visible loading delay on first render. | +| **Debugging** | Standard React Native / native debugging tools. | Requires separate WebView / browser dev-tools; bridge-related bugs can be hard to trace. | diff --git a/docs/docs/misc/compatibility.md b/docs/docs/misc/compatibility.md deleted file mode 100644 index 326f8ab42..000000000 --- a/docs/docs/misc/compatibility.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Compatibility - - diff --git a/docs/docs/misc/compatibility.mdx b/docs/docs/misc/compatibility.mdx new file mode 100644 index 000000000..29e71da48 --- /dev/null +++ b/docs/docs/misc/compatibility.mdx @@ -0,0 +1,77 @@ +--- +sidebar_position: 1 +--- + +# Compatibility + +This page is the single source of truth for the React Native versions the +library supports and for the small set of behaviours that differ between +platforms. + +:::info + +React Native Enriched HTML works **only** with the +[React Native New Architecture (Fabric)](https://reactnative.dev/architecture/landing-page). +The New Architecture is enabled by default in recent React Native versions. If +your app still uses the old architecture, you'll need to migrate to the New +Architecture before installing the library. + +::: + +## Supported React Native versions + +The table assumes you're using the latest patch of each library minor version. + + + +## Platform differences + +Android, iOS and Web are built on different text systems, so a few APIs +behave differently depending on the platform. These are platform +characteristics, not library bugs, and they're collected here. For limitations that affect +every platform, see [Known limitations](/misc/known-limitations) instead. + +### Justified text alignment (Android) + +On Android, `'justify'` is accepted in the type signature but has no justified +layout effect - the paragraph is shown with its natural alignment, the same as +`'auto'`. This applies both to `setTextAlignment('justify')` and to the `justify` value reported by +`onChangeState`. + +### `ellipsizeMode` with multiple lines (Android) + +On Android, when `numberOfLines` is set to a value higher than `1`, only the +`'tail'` value of `ellipsizeMode` works correctly. + +### Custom context menu (iOS 16+, ordering) + +Custom context-menu items (`contextMenuItems`) require **iOS 16 or newer** on +iOS. 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 shown in a submenu depending on the device manufacturer. On the Web the +native editing menu isn't available at all, so the prop is ignored - build your +own UI instead. + +### Return key behaviour + +- **`returnKeyType`** on Android it's accepted but ignored, because + `returnKeyType` doesn't work with multiline inputs. On the Web it maps to the + browser's [`enterkeyhint`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint). +- **`returnKeyLabel`** is supported on Android but not on iOS, and can't be set + inside a browser, so it's ignored on the Web. + +### Cursor / selection color + +The `cursorColor` prop is applied on Android and Web. On iOS the caret and +selection color follow the system tint. + +### HTML is not sanitized (Mobile) + +On iOS and Android, the library does not sanitize HTML. It makes no guarantees that the markup it accepts or produces is safe. You are fully responsible for sanitizing any HTML you persist, render elsewhere, or accept from untrusted sources. + +Sanitization _is_ enforced on the web, as injecting unsafe HTML directly into the DOM poses severe security risks (like XSS). + +### React Native layout ref methods + +On Web those methods are no-ops. These include: `measure`, +`measureInWindow`, `measureLayout`, and `setNativeProps`. diff --git a/docs/docs/misc/contributing.md b/docs/docs/misc/contributing.md index cd2d319b8..23bf7e80a 100644 --- a/docs/docs/misc/contributing.md +++ b/docs/docs/misc/contributing.md @@ -4,4 +4,309 @@ sidebar_position: 5 # Contributing - +Contributions are always welcome, no matter how large or small. This page will walk you through the development workflow. Before contributing, please read the +[code of conduct](https://github.com/software-mansion/react-native-enriched-html/blob/main/CODE_OF_CONDUCT.md). + +## Development workflow + +The project is a monorepo managed with +[Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains four +packages: + +- the library package, in the root directory; +- a native example app, in `apps/example/`; +- a web example app, in `apps/example-web/`; +- this documentation site, in `docs/` (a standalone project with its own + dependencies - see [Documentation](#documentation)). + +Install dependencies for the monorepo workspaces by running `yarn` in the root directory: + +```sh +yarn +``` + +:::note + +Because the project relies on Yarn workspaces, you can't use `npm` for +development. + +::: + +The [native example app](https://github.com/software-mansion/react-native-enriched-html/tree/main/apps/example) +demonstrates the library on iOS and Android and is how you test any changes you +make there. The [web example app](https://github.com/software-mansion/react-native-enriched-html/tree/main/apps/example-web) +does the same for the web. Both are configured to use the local version of the +library, so your source changes are reflected there: + +- **JavaScript** changes show up without a rebuild. +- **Native** changes (Objective-C, Swift, Java, Kotlin) require rebuilding the + native example app. + +The web example is a [Vite](https://vite.dev/) + [React](https://react.dev/) +app; changes to the library's web source are hot-reloaded by its dev server. + +### Editing native code + +To edit the native code in an IDE: + +- **iOS** - open `apps/example/ios/EnrichedTextInputExample.xcworkspace` in + Xcode. Find the sources under **Pods > Development Pods > + ReactNativeEnrichedHtml**. +- **Android** - open `apps/example/android` in Android Studio. Find the sources + under **react-native-enriched-html** in the **Android** view. + +### Running the native example app + +Start the Metro bundler: + +```sh +yarn example start +``` + +Run the app: + +```sh +# Android +yarn example android + +# iOS +yarn example ios +``` + +### Running the web example app + +Start the Vite dev server: + +```sh +yarn example-web dev +``` + +The app is then served at `http://localhost:5173`. + +## Linting, types, and tests + +Make sure your code passes TypeScript and ESLint: + +```sh +yarn typecheck +yarn lint +``` + +Fix formatting errors automatically: + +```sh +yarn lint --fix +``` + +Add tests for your change where possible, and run the unit tests: + +```sh +yarn test +``` + +The project uses [TypeScript](https://www.typescriptlang.org/) for type +checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) +for linting and formatting, and [Jest](https://jestjs.io/) for testing. + +:::note + +Pre-commit hooks verify that the linter and typecheck pass when you commit. + +::: + +## End-to-end tests + +### Mobile (Maestro) + +We use [Maestro](https://maestro.mobile.dev/) for mobile end-to-end testing. +Flows live in `.maestro/enrichedInput/flows/` and +`.maestro/enrichedText/flows/`. Shared subflows live in `.maestro/subflows/`, +with component-specific subflows in `.maestro/enrichedInput/subflows/` and +`.maestro/enrichedText/subflows/`. + +**Prerequisites:** + +- **Maestro CLI** (v2.3.0+) - follow the + [Getting Started guide](https://github.com/mobile-dev-inc/maestro?tab=readme-ov-file#getting-started), + then ensure `~/.maestro/bin` is in your `PATH`. +- **iOS** - Xcode, with `xcrun` available (it ships with the Xcode Command Line + Tools). +- **Android** - the Android SDK with SDK Command-line Tools, Platform-Tools, and + Emulator. Set `ANDROID_HOME` (typically `$HOME/Library/Android/sdk` on macOS) + and add these to your `PATH`: + - `$ANDROID_HOME/cmdline-tools/latest/bin` + - `$ANDROID_HOME/platform-tools` + - `$ANDROID_HOME/emulator` + +The target devices are: + +| Platform | Device | OS | +| -------- | --------- | ----------------------------- | +| iOS | iPhone 17 | iOS 26.2 | +| Android | Pixel 9 | API 36 "Baklava" (Android 16) | + +**Running the tests** - start the Metro bundler first, then run a suite. Each +command sets up the device and runs all Maestro flows, building only when +necessary: + +```sh +yarn example start + +# Both platforms sequentially +yarn test:e2e:mobile + +# Single platform +yarn test:e2e:ios +yarn test:e2e:android +``` + +Target a specific flow or force a rebuild: + +```sh +# Run a single flow +yarn test:e2e:ios .maestro/enrichedInput/flows/core_controls_smoke.yaml + +# Force a fresh build even if the app is already installed +yarn test:e2e:android --rebuild +``` + +**Visual regression tests** - some flows compare a screenshot of the component +against a saved baseline in `.maestro/enrichedInput/screenshots/` or +`.maestro/enrichedText/screenshots/`. By default the baseline is asserted; pass +`--update-screenshots` to capture new baselines instead: + +```sh +# Update baselines on both platforms +yarn test:e2e:mobile --update-screenshots + +# Single platform +yarn test:e2e:ios --update-screenshots +yarn test:e2e:android --update-screenshots .maestro/enrichedInput/flows/inline_styles_visual.yaml +``` + +Always review newly saved screenshots before committing them. + +:::note Flaky Android tests on macOS + +macOS may throttle the Android emulator via **App Nap** when its window isn't +visible, which can cause test timeouts. Either keep the emulator window visible +while tests run, or disable App Nap for the emulator: + +```sh +defaults write com.google.android.emulator NSAppSleepDisabled -bool YES +``` + +This requires an emulator restart and may drain your battery, so you may want +to re-enable it afterwards with `-bool NO`. + +::: + +### Web (Playwright) + +We use [Playwright](https://playwright.dev/) for end-to-end testing of the web +example. Tests live in `.playwright/tests/`. Install the browser binaries once: + +```sh +yarn playwright install +``` + +Run the suite from the root directory: + +```sh +yarn test:e2e:web +``` + +**Visual regression tests** - some tests compare a screenshot of the component +against a saved baseline in `.playwright/screenshots/`. By default the baseline is asserted; pass +`--update-screenshots` to capture new baselines instead: + +```sh +yarn test:e2e:web --update-screenshots +``` + +Append `--ui` for Playwright's +[UI mode](https://playwright.dev/docs/test-ui-mode) (pick tests, watch runs, +inspect traces). The Playwright config starts the Vite dev server +automatically, so you don't need to run `yarn example-web dev` separately. + +## Documentation + +The site you're reading is built with [Docusaurus](https://docusaurus.io/) and +lives in the `docs/` directory. It's a standalone project with its own +`yarn.lock`, separate from the monorepo workspace, so install its dependencies +from inside the folder: + +```sh +cd docs +yarn +``` + +Then start the local dev server: + +```sh +yarn start +``` + +A few things worth knowing: + +- Pages are Markdown / MDX files in `docs/docs/`; the navigation is generated + from the folder structure and `_category_.json` files. Filenames are + kebab-case. +- If you make any changes, run `yarn build` before opening a pull request, to make sure the project successfully builds. + +:::info + +When you change the public API or behaviour, please update the documentation in +the same pull request whenever possible. Keeping the docs in sync with the code +is one of the most valuable ways to contribute. + +::: + +## Commit message convention + +We follow the +[conventional commits specification](https://www.conventionalcommits.org/en). +Pre-commit hooks verify the format when you commit: + +- `fix`: bug fixes, e.g. fix crash due to deprecated method. +- `feat`: new features, e.g. add a new method to the module. +- `refactor`: code refactor, e.g. migrate from class components to hooks. +- `docs`: documentation changes, e.g. add a usage example for the module. +- `test`: adding or updating tests, e.g. add integration tests. +- `chore`: tooling changes, e.g. change CI config. + +## Scripts + +The `package.json` file contains scripts for common tasks: + +- `yarn` - set up the project by installing dependencies. +- `yarn typecheck` - type-check files with TypeScript. +- `yarn lint` - lint files with ESLint. +- `yarn test` - run unit tests with Jest. +- `yarn example start` - start the Metro server for the native example app. +- `yarn example android` - run the native example app on Android. +- `yarn example ios` - run the native example app on iOS. +- `yarn example-web dev` - start the Vite dev server for the web example app. +- `yarn test:e2e` - run all E2E tests (mobile + web) sequentially. +- `yarn test:e2e:mobile` - run mobile E2E tests on iOS and Android sequentially. +- `yarn test:e2e:android` - run E2E tests on Android. +- `yarn test:e2e:ios` - run E2E tests on iOS. +- `yarn test:e2e:web` - run E2E tests on the web example with Playwright. + +## Sending a pull request + +:::tip + +Working on your first pull request? Learn how from this free series: +[How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github). + +::: + +When you send a pull request: + +- Prefer small pull requests focused on one change. +- Verify that linters and tests are passing. +- Review the documentation to make sure it looks good. +- Follow the pull request template when opening a pull request. +- For pull requests that change the API or implementation, discuss with the + maintainers first by opening an issue. diff --git a/docs/docs/misc/known-limitations.md b/docs/docs/misc/known-limitations.md index af18c99cd..de53e854f 100644 --- a/docs/docs/misc/known-limitations.md +++ b/docs/docs/misc/known-limitations.md @@ -4,4 +4,31 @@ sidebar_position: 3 # Known limitations - +This page lists limitations that apply to **every** platform. Behaviours that +differ only between iOS, Android, and Web are documented separately under +[Compatibility](/misc/compatibility). + +Some of these are on our [Roadmap](/misc/roadmap); others are deliberate design +choices that keep the library fast and its output predictable. + +## Single-level lists only + +The editor supports a single level of ordered and unordered lists. **Nested +lists are not supported** - you can't indent a list item to create a sublist. +Multi-level list support is planned; see the [Roadmap](/misc/roadmap). + +## Fixed set of HTML tags + +The library intentionally works with a fixed, curated set of standard and +custom HTML tags rather than accepting arbitrary markup. Tags outside that set +are stripped or normalized away when they enter the editor. This is a +deliberate design decision: it keeps the produced HTML portable and guarantees +that the `EnrichedTextInput` and the `EnrichedText` display render identically. See +[Supported tags](/fundamentals/html-format-and-supported-tags) for the full +list. + +## New Architecture required + +The components are built exclusively for the React Native New Architecture +(Fabric). There is no Old Architecture (Paper) fallback. See +[Compatibility](/misc/compatibility) for supported React Native versions. diff --git a/docs/docs/misc/roadmap.md b/docs/docs/misc/roadmap.md index 28978894c..f4011ba78 100644 --- a/docs/docs/misc/roadmap.md +++ b/docs/docs/misc/roadmap.md @@ -4,4 +4,20 @@ sidebar_position: 4 # Roadmap - +Here's what we're planning to bring to React Native Enriched HTML in the future. + +## Nested lists + +Support for multi-level ordered and unordered lists, so users can organize +complex content hierarchically. This lifts the current +[single-level list limitation](/misc/known-limitations). + +## RTL support + +Natively integrated right-to-left text direction for RTL languages. + +## Accessibility reader + +Support for screen readers and accessibility APIs, +delivering a rich, inclusive text editing experience while aligning with WCAG +and ADA standards. diff --git a/docs/src/components/Compatibility/EnrichedCompatibility.tsx b/docs/src/components/Compatibility/EnrichedCompatibility.tsx new file mode 100644 index 000000000..476287235 --- /dev/null +++ b/docs/src/components/Compatibility/EnrichedCompatibility.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { No, Version, Yes } from './index'; + +const REACT_NATIVE_VERSIONS = [ + '0.80', + '0.81', + '0.82', + '0.83', + '0.84', + '0.85', + '0.86', +]; + +const LIBRARY_VERSIONS = [ + { version: 'nightly', supportedFrom: '0.81', supportedTo: '0.86' }, + { version: '1.1.x', supportedFrom: '0.81', supportedTo: '0.86' }, + { version: '1.0.x', supportedFrom: '0.81', supportedTo: '0.85' }, +] as const; + +function isSupported( + reactNativeVersion: string, + supportedFrom: string, + supportedTo: string, +) { + return ( + reactNativeVersion.localeCompare(supportedFrom, undefined, { + numeric: true, + }) >= 0 && + reactNativeVersion.localeCompare(supportedTo, undefined, { + numeric: true, + }) <= 0 + ); +} + +export default function EnrichedCompatibility() { + return ( +
+ + + + + {REACT_NATIVE_VERSIONS.map(version => ( + + ))} + + + + {LIBRARY_VERSIONS.map(({ version, supportedFrom, supportedTo }) => ( + + + {REACT_NATIVE_VERSIONS.map(reactNativeVersion => ( + + ))} + + ))} + +
+ {version} +
+ + + {isSupported( + reactNativeVersion, + supportedFrom, + supportedTo, + ) ? ( + + ) : ( + + )} +
+
+ ); +} diff --git a/docs/src/theme/MDXComponents.js b/docs/src/theme/MDXComponents.js index 2b2796f2a..fab0f703d 100644 --- a/docs/src/theme/MDXComponents.js +++ b/docs/src/theme/MDXComponents.js @@ -9,6 +9,7 @@ import Indent from '@site/src/components/Indent'; import Row from '@site/src/components/Row'; import Grid from '@site/src/components/Grid'; import { Yes, No, Version, Spacer } from '@site/src/components/Compatibility'; +import EnrichedCompatibility from '@site/src/components/Compatibility/EnrichedCompatibility'; import { Badges } from '@swmansion/t-rex-ui'; export default { @@ -26,5 +27,6 @@ export default { No, Version, Spacer, + EnrichedCompatibility, Badges, };