diff --git a/apps/example-web/src/App.tsx b/apps/example-web/src/App.tsx
index b6fa757c8..005cd1804 100644
--- a/apps/example-web/src/App.tsx
+++ b/apps/example-web/src/App.tsx
@@ -1,10 +1,10 @@
import './App.css';
+import { EnrichedTextInput } from 'react-native-enriched';
function App() {
return (
);
}
diff --git a/apps/example-web/tsconfig.app.json b/apps/example-web/tsconfig.app.json
index 690114886..469857255 100644
--- a/apps/example-web/tsconfig.app.json
+++ b/apps/example-web/tsconfig.app.json
@@ -3,6 +3,10 @@
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client"],
+ "baseUrl": ".",
+ "paths": {
+ "react-native-enriched": ["../../src/index.web.tsx"]
+ }
},
"include": ["src"]
}
diff --git a/apps/example-web/vite.config.ts b/apps/example-web/vite.config.ts
index 4a5def4c3..9dc8e4dac 100644
--- a/apps/example-web/vite.config.ts
+++ b/apps/example-web/vite.config.ts
@@ -1,7 +1,16 @@
+import path from 'node:path';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
+ resolve: {
+ alias: {
+ 'react-native-enriched': path.resolve(
+ __dirname,
+ '../../src/index.web.tsx'
+ ),
+ },
+ },
});
diff --git a/package.json b/package.json
index 25bd3844f..37a47630d 100644
--- a/package.json
+++ b/package.json
@@ -5,8 +5,13 @@
"source": "./src/index.tsx",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
+ "sideEffects": false,
"exports": {
".": {
+ "react-native": {
+ "types": "./lib/typescript/src/index.d.ts",
+ "default": "./lib/module/index.js"
+ },
"types": "./lib/typescript/src/index.d.ts",
"default": "./lib/module/index.js"
},
@@ -20,6 +25,14 @@
"cpp",
"*.podspec",
"react-native.config.js",
+ "!src/web",
+ "!src/index.web.tsx",
+ "!lib/module/web",
+ "!lib/module/index.web.js",
+ "!lib/module/index.web.js.map",
+ "!lib/typescript/src/web",
+ "!lib/typescript/src/index.web.d.ts",
+ "!lib/typescript/src/index.web.d.ts.map",
"!ios/build",
"!android/build",
"!android/gradle",
diff --git a/src/index.tsx b/src/index.tsx
index 3cfed7e2f..e5c3fc14e 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,5 +1,6 @@
-export * from './EnrichedTextInput';
+export { EnrichedTextInput } from './native/EnrichedTextInput';
export type {
+ EnrichedTextInputProps,
OnChangeTextEvent,
OnChangeHtmlEvent,
OnChangeStateEvent,
@@ -9,5 +10,11 @@ export type {
OnKeyPressEvent,
OnPasteImagesEvent,
OnSubmitEditing,
-} from './spec/EnrichedTextInputNativeComponent';
-export type { HtmlStyle, MentionStyleProperties } from './types';
+ HtmlStyle,
+ MentionStyleProperties,
+ FocusEvent,
+ BlurEvent,
+ EnrichedTextInputInstance,
+ ContextMenuItem,
+ OnChangeMentionEvent,
+} from './types';
diff --git a/src/index.web.tsx b/src/index.web.tsx
new file mode 100644
index 000000000..4dd115ed8
--- /dev/null
+++ b/src/index.web.tsx
@@ -0,0 +1,20 @@
+export { EnrichedTextInput } from './web/EnrichedTextInput';
+export type {
+ EnrichedTextInputProps,
+ OnChangeTextEvent,
+ OnChangeHtmlEvent,
+ OnChangeStateEvent,
+ OnLinkDetected,
+ OnMentionDetected,
+ OnChangeSelectionEvent,
+ OnKeyPressEvent,
+ OnPasteImagesEvent,
+ OnSubmitEditing,
+ HtmlStyle,
+ MentionStyleProperties,
+ FocusEvent,
+ BlurEvent,
+ EnrichedTextInputInstance,
+ ContextMenuItem,
+ OnChangeMentionEvent,
+} from './types';
diff --git a/src/EnrichedTextInput.tsx b/src/native/EnrichedTextInput.tsx
similarity index 69%
rename from src/EnrichedTextInput.tsx
rename to src/native/EnrichedTextInput.tsx
index 3e8fc9f67..7ff8120b4 100644
--- a/src/EnrichedTextInput.tsx
+++ b/src/native/EnrichedTextInput.tsx
@@ -1,6 +1,5 @@
import {
type Component,
- type RefObject,
useEffect,
useImperativeHandle,
useMemo,
@@ -10,147 +9,28 @@ import { useCallback } from 'react';
import EnrichedTextInputNativeComponent, {
Commands,
type NativeProps,
- type OnChangeHtmlEvent,
- type OnChangeSelectionEvent,
- type OnChangeStateEvent,
- type OnChangeTextEvent,
type OnContextMenuItemPressEvent,
- type OnLinkDetected,
type OnMentionEvent,
- type OnMentionDetected,
type OnMentionDetectedInternal,
type OnRequestHtmlResultEvent,
- type OnKeyPressEvent,
- type OnPasteImagesEvent,
- type OnSubmitEditing,
-} from './spec/EnrichedTextInputNativeComponent';
+} from '../spec/EnrichedTextInputNativeComponent';
import type {
- ColorValue,
HostInstance,
MeasureInWindowOnSuccessCallback,
MeasureLayoutOnSuccessCallback,
MeasureOnSuccessCallback,
NativeMethods,
NativeSyntheticEvent,
- ReturnKeyTypeOptions,
- TargetedEvent,
- TextStyle,
- ViewProps,
- ViewStyle,
} from 'react-native';
-import { normalizeHtmlStyle } from './utils/normalizeHtmlStyle';
-import { toNativeRegexConfig } from './utils/regexParser';
-import { nullthrows } from './utils/nullthrows';
-import type { HtmlStyle } from './types';
-
-export type FocusEvent = NativeSyntheticEvent;
-export type BlurEvent = NativeSyntheticEvent;
-
-export interface EnrichedTextInputInstance extends NativeMethods {
- // General commands
- focus: () => void;
- blur: () => void;
- setValue: (value: string) => void;
- setSelection: (start: number, end: number) => void;
- getHTML: () => Promise;
-
- // Text formatting commands
- toggleBold: () => void;
- toggleItalic: () => void;
- toggleUnderline: () => void;
- toggleStrikeThrough: () => void;
- toggleInlineCode: () => void;
- toggleH1: () => void;
- toggleH2: () => void;
- toggleH3: () => void;
- toggleH4: () => void;
- toggleH5: () => void;
- toggleH6: () => void;
- toggleCodeBlock: () => void;
- toggleBlockQuote: () => void;
- toggleOrderedList: () => void;
- toggleUnorderedList: () => void;
- toggleCheckboxList: (checked: boolean) => void;
- setLink: (start: number, end: number, text: string, url: string) => void;
- removeLink: (start: number, end: number) => void;
- setImage: (src: string, width: number, height: number) => void;
- startMention: (indicator: string) => void;
- setMention: (
- indicator: string,
- text: string,
- attributes?: Record
- ) => void;
-}
-
-export interface ContextMenuItem {
- text: string;
- onPress: ({
- text,
- selection,
- styleState,
- }: {
- text: string;
- selection: { start: number; end: number };
- styleState: OnChangeStateEvent;
- }) => void;
- visible?: boolean;
-}
-
-export interface OnChangeMentionEvent {
- indicator: string;
- text: string;
-}
-
-export 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?: ViewStyle | TextStyle;
- scrollEnabled?: boolean;
- linkRegex?: RegExp | null;
- 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[];
- /**
- * 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.
- * Disabled by default.
- */
- androidExperimentalSynchronousEvents?: boolean;
- /**
- * If true, external HTML (e.g. from Google Docs, Word, web pages) will be
- * normalized through the HTML normalizer before being applied.
- * This converts arbitrary HTML into the canonical tag subset that the enriched
- * parser understands.
- * Disabled by default.
- */
- useHtmlNormalizer?: boolean;
-}
+import { normalizeHtmlStyle } from '../utils/normalizeHtmlStyle';
+import { toNativeRegexConfig } from '../utils/regexParser';
+import { nullthrows } from '../utils/nullthrows';
+import type {
+ ContextMenuItem,
+ EnrichedTextInputProps,
+ OnLinkDetected,
+ OnMentionDetected,
+} from '../types';
const warnMentionIndicators = (indicator: string) => {
console.warn(
@@ -416,7 +296,11 @@ export const EnrichedTextInput = ({
) => {
const { text, indicator, payload } = e.nativeEvent;
const attributes = JSON.parse(payload) as Record;
- onMentionDetected?.({ text, indicator, attributes });
+ onMentionDetected?.({
+ text,
+ indicator,
+ attributes,
+ } satisfies OnMentionDetected);
};
const handleRequestHtmlResult = (
diff --git a/src/types.ts b/src/types.ts
index b7f9591bb..aeee6e931 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,4 +1,14 @@
-import type { ColorValue, TextStyle } from 'react-native';
+import type { RefObject } from 'react';
+import type {
+ ColorValue,
+ NativeMethods,
+ NativeSyntheticEvent,
+ ReturnKeyTypeOptions,
+ TargetedEvent,
+ TextStyle,
+ ViewProps,
+ ViewStyle,
+} from 'react-native';
interface HeadingStyle {
fontSize?: number;
@@ -57,3 +67,258 @@ export interface HtmlStyle {
boxColor?: ColorValue;
};
}
+
+// Event types
+
+export interface OnChangeTextEvent {
+ value: string;
+}
+
+export interface OnChangeHtmlEvent {
+ value: string;
+}
+
+export 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;
+ };
+}
+
+export interface OnLinkDetected {
+ text: string;
+ url: string;
+ start: number;
+ end: number;
+}
+
+export interface OnMentionDetected {
+ text: string;
+ indicator: string;
+ attributes: Record;
+}
+
+export interface OnChangeSelectionEvent {
+ start: number;
+ end: number;
+ text: string;
+}
+
+export interface OnKeyPressEvent {
+ key: string;
+}
+
+export interface OnPasteImagesEvent {
+ images: {
+ uri: string;
+ type: string;
+ width: number;
+ height: number;
+ }[];
+}
+
+export interface OnSubmitEditing {
+ text: string;
+}
+
+// Component types
+
+export type FocusEvent = NativeSyntheticEvent;
+export type BlurEvent = NativeSyntheticEvent;
+
+export interface EnrichedTextInputInstance extends NativeMethods {
+ // General commands
+ focus: () => void;
+ blur: () => void;
+ setValue: (value: string) => void;
+ setSelection: (start: number, end: number) => void;
+ getHTML: () => Promise;
+
+ // Text formatting commands
+ toggleBold: () => void;
+ toggleItalic: () => void;
+ toggleUnderline: () => void;
+ toggleStrikeThrough: () => void;
+ toggleInlineCode: () => void;
+ toggleH1: () => void;
+ toggleH2: () => void;
+ toggleH3: () => void;
+ toggleH4: () => void;
+ toggleH5: () => void;
+ toggleH6: () => void;
+ toggleCodeBlock: () => void;
+ toggleBlockQuote: () => void;
+ toggleOrderedList: () => void;
+ toggleUnorderedList: () => void;
+ toggleCheckboxList: (checked: boolean) => void;
+ setLink: (start: number, end: number, text: string, url: string) => void;
+ removeLink: (start: number, end: number) => void;
+ setImage: (src: string, width: number, height: number) => void;
+ startMention: (indicator: string) => void;
+ setMention: (
+ indicator: string,
+ text: string,
+ attributes?: Record
+ ) => void;
+}
+
+export interface ContextMenuItem {
+ text: string;
+ onPress: ({
+ text,
+ selection,
+ styleState,
+ }: {
+ text: string;
+ selection: { start: number; end: number };
+ styleState: OnChangeStateEvent;
+ }) => void;
+ visible?: boolean;
+}
+
+export interface OnChangeMentionEvent {
+ indicator: string;
+ text: string;
+}
+
+export 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?: ViewStyle | TextStyle;
+ scrollEnabled?: boolean;
+ linkRegex?: RegExp | null;
+ 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[];
+ /**
+ * 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.
+ * Disabled by default.
+ */
+ androidExperimentalSynchronousEvents?: boolean;
+ /**
+ * If true, external HTML (e.g. from Google Docs, Word, web pages) will be
+ * normalized through the HTML normalizer before being applied.
+ * This converts arbitrary HTML into the canonical tag subset that the enriched
+ * parser understands.
+ * Disabled by default.
+ */
+ useHtmlNormalizer?: boolean;
+}
diff --git a/src/web/EnrichedTextInput.tsx b/src/web/EnrichedTextInput.tsx
new file mode 100644
index 000000000..668ab7e4f
--- /dev/null
+++ b/src/web/EnrichedTextInput.tsx
@@ -0,0 +1,48 @@
+import { useImperativeHandle } from 'react';
+import type {
+ EnrichedTextInputInstance,
+ EnrichedTextInputProps,
+} from '../types';
+
+export const EnrichedTextInput = ({
+ ref,
+ defaultValue,
+}: EnrichedTextInputProps) => {
+ useImperativeHandle(
+ ref,
+ (): EnrichedTextInputInstance => ({
+ focus: () => {},
+ blur: () => {},
+ setValue: () => {},
+ setSelection: () => {},
+ getHTML: () => Promise.resolve(''),
+ toggleBold: () => {},
+ toggleItalic: () => {},
+ toggleUnderline: () => {},
+ toggleStrikeThrough: () => {},
+ toggleInlineCode: () => {},
+ toggleH1: () => {},
+ toggleH2: () => {},
+ toggleH3: () => {},
+ toggleH4: () => {},
+ toggleH5: () => {},
+ toggleH6: () => {},
+ toggleCodeBlock: () => {},
+ toggleBlockQuote: () => {},
+ toggleOrderedList: () => {},
+ toggleUnorderedList: () => {},
+ toggleCheckboxList: () => {},
+ setLink: () => {},
+ removeLink: () => {},
+ setImage: () => {},
+ startMention: () => {},
+ setMention: () => {},
+ measure: () => {},
+ measureInWindow: () => {},
+ measureLayout: () => {},
+ setNativeProps: () => {},
+ })
+ );
+
+ return ;
+};