diff --git a/apps/example-web/src/App.tsx b/apps/example-web/src/App.tsx
index b6fa757c8..9e52b8955 100644
--- a/apps/example-web/src/App.tsx
+++ b/apps/example-web/src/App.tsx
@@ -1,10 +1,11 @@
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..1108d2773 100644
--- a/apps/example-web/tsconfig.app.json
+++ b/apps/example-web/tsconfig.app.json
@@ -1,8 +1,11 @@
{
- "extends": "../../tsconfig.base.json",
+ "extends": "../../tsconfig.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client"],
+ "paths": {
+ "react-native-enriched": ["../../src/index"]
+ }
},
"include": ["src"]
}
diff --git a/apps/example-web/tsconfig.node.json b/apps/example-web/tsconfig.node.json
index c31ba89ef..89fbc7609 100644
--- a/apps/example-web/tsconfig.node.json
+++ b/apps/example-web/tsconfig.node.json
@@ -1,9 +1,12 @@
{
- "extends": "../../tsconfig.base.json",
+ "extends": "../../tsconfig.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"lib": ["ES2023"],
"types": ["node"],
+ "paths": {
+ "react-native-enriched": ["../../src/index"]
+ }
},
"include": ["vite.config.ts"]
}
diff --git a/apps/example-web/vite.config.ts b/apps/example-web/vite.config.ts
index 4a5def4c3..b5d8bc646 100644
--- a/apps/example-web/vite.config.ts
+++ b/apps/example-web/vite.config.ts
@@ -1,7 +1,13 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
+import path from 'path';
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
+ resolve: {
+ alias: {
+ 'react-native-enriched': path.resolve(__dirname, '../../src/index.tsx'),
+ },
+ },
});
diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx
index 2d8a92db6..c9115030e 100644
--- a/apps/example/src/App.tsx
+++ b/apps/example/src/App.tsx
@@ -318,9 +318,9 @@ export default function App() {
cursorColor="dodgerblue"
autoCapitalize="sentences"
linkRegex={LINK_REGEX}
- onChangeText={(e) => handleChangeText(e.nativeEvent)}
- onChangeHtml={(e) => handleChangeHtml(e.nativeEvent)}
- onChangeState={(e) => handleChangeState(e.nativeEvent)}
+ onChangeText={handleChangeText}
+ onChangeHtml={handleChangeHtml}
+ onChangeState={handleChangeState}
onLinkDetected={handleLinkDetected}
onMentionDetected={console.log}
onStartMention={handleStartMention}
@@ -328,8 +328,8 @@ export default function App() {
onEndMention={handleEndMention}
onFocus={handleFocusEvent}
onBlur={handleBlurEvent}
- onChangeSelection={(e) => handleSelectionChangeEvent(e.nativeEvent)}
- onKeyPress={(e) => handleKeyPress(e.nativeEvent)}
+ onChangeSelection={handleSelectionChangeEvent}
+ onKeyPress={handleKeyPress}
androidExperimentalSynchronousEvents={
ANDROID_EXPERIMENTAL_SYNCHRONOUS_EVENTS
}
diff --git a/apps/example/tsconfig.json b/apps/example/tsconfig.json
new file mode 100644
index 000000000..8a06dd54a
--- /dev/null
+++ b/apps/example/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "paths": {
+ "react-native-enriched": ["../../src/index.native"]
+ }
+ }
+}
diff --git a/lefthook.yml b/lefthook.yml
index 6aa1bd1bb..8e8214dc3 100644
--- a/lefthook.yml
+++ b/lefthook.yml
@@ -7,7 +7,7 @@ pre-commit:
types:
glob: "*.{js,ts, jsx, tsx}"
- run: npx tsc
+ run: yarn typecheck:all
lint-android:
glob: "**/*.{kt,kts,java,gradle}"
diff --git a/package.json b/package.json
index 549344116..b1345454d 100644
--- a/package.json
+++ b/package.json
@@ -2,11 +2,17 @@
"name": "react-native-enriched",
"version": "0.3.0",
"description": "Rich Text Editor component for React Native",
- "source": "./src/index.tsx",
"main": "./lib/module/index.js",
+ "module": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
+ "react-native": "./lib/module/index.native.js",
+ "sideEffects": false,
"exports": {
".": {
+ "react-native": {
+ "types": "./lib/typescript/src/index.native.d.ts",
+ "default": "./lib/module/index.native.js"
+ },
"types": "./lib/typescript/src/index.d.ts",
"default": "./lib/module/index.js"
},
@@ -35,7 +41,10 @@
"example": "yarn workspace react-native-enriched-example",
"example-web": "yarn workspace react-native-enriched-web-example",
"test": "jest --passWithNoTests",
- "typecheck": "tsc",
+ "typecheck": "tsc -p tsconfig.build.json",
+ "typecheck:example": "tsc -p apps/example/tsconfig.json",
+ "typecheck:example-web": "tsc -p apps/example-web/tsconfig.json",
+ "typecheck:all": "yarn typecheck && yarn typecheck:example && yarn typecheck:example-web",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"lint:android": "cd apps/example/android && ./gradlew lintVerify",
"lint:android:fix": "cd apps/example/android && ./gradlew lintFormat",
diff --git a/src/common/defaultProps.ts b/src/common/defaultProps.ts
new file mode 100644
index 000000000..ec0f473ca
--- /dev/null
+++ b/src/common/defaultProps.ts
@@ -0,0 +1,8 @@
+export const ENRICHED_TEXT_INPUT_DEFAULTS = {
+ editable: true,
+ mentionIndicators: ['@'],
+ autoCapitalize: 'sentences' as const,
+ htmlStyle: {},
+ androidExperimentalSynchronousEvents: false,
+ scrollEnabled: true,
+};
diff --git a/src/common/types.ts b/src/common/types.ts
new file mode 100644
index 000000000..31c775bc2
--- /dev/null
+++ b/src/common/types.ts
@@ -0,0 +1,67 @@
+// Re-export event types from the NativeComponent spec file (source of truth for Codegen)
+export type {
+ OnChangeTextEvent,
+ OnChangeHtmlEvent,
+ OnChangeStateEvent,
+ OnChangeStateDeprecatedEvent,
+ OnKeyPressEvent,
+} from '../spec/EnrichedTextInputNativeComponent';
+
+export interface OnMentionDetected {
+ text: string;
+ indicator: string;
+ attributes: Record;
+}
+
+export interface OnLinkDetected {
+ text: string;
+ url: string;
+ start: number;
+ end: number;
+}
+
+export interface OnChangeSelectionEvent {
+ start: number;
+ end: number;
+ text: string;
+}
+
+export interface OnChangeMentionEvent {
+ indicator: string;
+ text: string;
+}
+
+export interface EnrichedTextInputInstanceBase {
+ // 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;
+ setImage: (src: string, width: number, height: number) => void;
+ startMention: (indicator: string) => void;
+ setMention: (
+ indicator: string,
+ text: string,
+ attributes?: Record
+ ) => void;
+}
diff --git a/src/index.native.tsx b/src/index.native.tsx
new file mode 100644
index 000000000..2fcdef1b1
--- /dev/null
+++ b/src/index.native.tsx
@@ -0,0 +1,13 @@
+export * from './native/EnrichedTextInput';
+export type {
+ OnChangeTextEvent,
+ OnChangeHtmlEvent,
+ OnChangeMentionEvent,
+ OnChangeSelectionEvent,
+ OnChangeStateEvent,
+ OnChangeStateDeprecatedEvent,
+ OnKeyPressEvent,
+ OnLinkDetected,
+ OnMentionDetected,
+} from './common/types';
+export type { HtmlStyle, MentionStyleProperties } from './native/types';
diff --git a/src/index.tsx b/src/index.tsx
index 809ac9587..8aed07c53 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,12 +1,12 @@
-export * from './EnrichedTextInput';
+export * from './web/EnrichedTextInput';
export type {
OnChangeTextEvent,
OnChangeHtmlEvent,
+ OnChangeMentionEvent,
+ OnChangeSelectionEvent,
OnChangeStateEvent,
OnChangeStateDeprecatedEvent,
+ OnKeyPressEvent,
OnLinkDetected,
OnMentionDetected,
- OnChangeSelectionEvent,
- OnKeyPressEvent,
-} from './spec/EnrichedTextInputNativeComponent';
-export type { HtmlStyle, MentionStyleProperties } from './types';
+} from './common/types';
diff --git a/src/EnrichedTextInput.tsx b/src/native/EnrichedTextInput.tsx
similarity index 73%
rename from src/EnrichedTextInput.tsx
rename to src/native/EnrichedTextInput.tsx
index 32d849d75..7c2ae264b 100644
--- a/src/EnrichedTextInput.tsx
+++ b/src/native/EnrichedTextInput.tsx
@@ -9,18 +9,14 @@ import {
import EnrichedTextInputNativeComponent, {
Commands,
type NativeProps,
- type OnChangeHtmlEvent,
- type OnChangeSelectionEvent,
- type OnChangeStateEvent,
- type OnChangeTextEvent,
- type OnLinkDetected,
+ type OnChangeSelectionNativeEvent,
+ type OnLinkDetectedNativeEvent,
type OnMentionEvent,
- type OnMentionDetected,
type OnMentionDetectedInternal,
type OnRequestHtmlResultEvent,
type OnChangeStateDeprecatedEvent,
type OnKeyPressEvent,
-} from './spec/EnrichedTextInputNativeComponent';
+} from '../spec/EnrichedTextInputNativeComponent';
import type {
ColorValue,
HostInstance,
@@ -34,54 +30,29 @@ import type {
ViewProps,
ViewStyle,
} from 'react-native';
-import { normalizeHtmlStyle } from './utils/normalizeHtmlStyle';
-import { toNativeRegexConfig } from './utils/regexParser';
-import { nullthrows } from './utils/nullthrows';
+import { normalizeHtmlStyle } from '../utils/normalizeHtmlStyle';
+import { toNativeRegexConfig } from '../utils/regexParser';
+import type {
+ OnChangeTextEvent,
+ OnChangeHtmlEvent,
+ OnChangeStateEvent,
+ OnMentionDetected,
+ OnLinkDetected,
+ OnChangeSelectionEvent,
+ OnChangeMentionEvent,
+ EnrichedTextInputInstanceBase,
+} from '../common/types';
+import { ENRICHED_TEXT_INPUT_DEFAULTS } from '../common/defaultProps';
+import { nullthrows } from '../utils/nullthrows';
import type { HtmlStyle } from './types';
+export interface EnrichedTextInputInstance
+ extends EnrichedTextInputInstanceBase,
+ NativeMethods {}
+
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;
- setImage: (src: string, width: number, height: number) => void;
- startMention: (indicator: string) => void;
- setMention: (
- indicator: string,
- text: string,
- attributes?: Record
- ) => void;
-}
-
-export interface OnChangeMentionEvent {
- indicator: string;
- text: string;
-}
-
export interface EnrichedTextInputProps extends Omit {
ref?: RefObject;
autoFocus?: boolean;
@@ -99,22 +70,20 @@ export interface EnrichedTextInputProps extends Omit {
linkRegex?: RegExp | null;
onFocus?: (e: FocusEvent) => void;
onBlur?: (e: BlurEvent) => void;
- onChangeText?: (e: NativeSyntheticEvent) => void;
- onChangeHtml?: (e: NativeSyntheticEvent) => void;
- onChangeState?: (e: NativeSyntheticEvent) => void;
+ onChangeText?: (e: OnChangeTextEvent) => void;
+ onChangeHtml?: (e: OnChangeHtmlEvent) => void;
+ onChangeState?: (e: OnChangeStateEvent) => void;
/**
* @deprecated Use onChangeState prop instead.
*/
- onChangeStateDeprecated?: (
- e: NativeSyntheticEvent
- ) => void;
+ onChangeStateDeprecated?: (e: OnChangeStateDeprecatedEvent) => 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;
+ onChangeSelection?: (e: OnChangeSelectionEvent) => void;
+ onKeyPress?: (e: OnKeyPressEvent) => void;
/**
* If true, Android will use experimental synchronous events.
* This will prevent from input flickering when updating component size.
@@ -125,7 +94,7 @@ export interface EnrichedTextInputProps extends Omit {
androidExperimentalSynchronousEvents?: boolean;
}
-const warnAboutMissconfiguredMentions = (indicator: string) => {
+const warnAboutMisconfiguredMentions = (indicator: string) => {
console.warn(
`Looks like you are trying to set a "${indicator}" but it's not in the mentionIndicators prop`
);
@@ -141,16 +110,16 @@ type HtmlRequest = {
export const EnrichedTextInput = ({
ref,
autoFocus,
- editable = true,
- mentionIndicators = ['@'],
+ editable = ENRICHED_TEXT_INPUT_DEFAULTS.editable,
+ mentionIndicators = ENRICHED_TEXT_INPUT_DEFAULTS.mentionIndicators,
defaultValue,
placeholder,
placeholderTextColor,
cursorColor,
selectionColor,
style,
- autoCapitalize = 'sentences',
- htmlStyle = {},
+ autoCapitalize = ENRICHED_TEXT_INPUT_DEFAULTS.autoCapitalize,
+ htmlStyle = ENRICHED_TEXT_INPUT_DEFAULTS.htmlStyle,
linkRegex: _linkRegex,
onFocus,
onBlur,
@@ -165,8 +134,8 @@ export const EnrichedTextInput = ({
onEndMention,
onChangeSelection,
onKeyPress,
- androidExperimentalSynchronousEvents = false,
- scrollEnabled = true,
+ androidExperimentalSynchronousEvents = ENRICHED_TEXT_INPUT_DEFAULTS.androidExperimentalSynchronousEvents,
+ scrollEnabled = ENRICHED_TEXT_INPUT_DEFAULTS.scrollEnabled,
...rest
}: EnrichedTextInputProps) => {
const nativeRef = useRef(null);
@@ -302,7 +271,7 @@ export const EnrichedTextInput = ({
},
startMention: (indicator: string) => {
if (!mentionIndicators?.includes(indicator)) {
- warnAboutMissconfiguredMentions(indicator);
+ warnAboutMisconfiguredMentions(indicator);
}
Commands.startMention(nullthrows(nativeRef.current), indicator);
@@ -327,11 +296,31 @@ export const EnrichedTextInput = ({
}
};
- const handleLinkDetected = (e: NativeSyntheticEvent) => {
+ const handleLinkDetected = (
+ e: NativeSyntheticEvent
+ ) => {
const { text, url, start, end } = e.nativeEvent;
onLinkDetected?.({ text, url, start, end });
};
+ const handleChangeText = (e: NativeSyntheticEvent) => {
+ onChangeText?.(e.nativeEvent);
+ };
+
+ const handleChangeHtml = (e: NativeSyntheticEvent) => {
+ onChangeHtml?.(e.nativeEvent);
+ };
+
+ const handleKeyPress = (e: NativeSyntheticEvent) => {
+ onKeyPress?.(e.nativeEvent);
+ };
+
+ const handleChangeSelection = (
+ e: NativeSyntheticEvent
+ ) => {
+ onChangeSelection?.(e.nativeEvent);
+ };
+
const handleMentionDetected = (
e: NativeSyntheticEvent
) => {
@@ -359,31 +348,28 @@ export const EnrichedTextInput = ({
const onChangeStateWithDeprecated = (
e: NativeSyntheticEvent
) => {
- onChangeState?.(e);
+ onChangeState?.(e.nativeEvent);
// TODO: remove in 0.5.0 release
onChangeStateDeprecated?.({
- ...e,
- nativeEvent: {
- isBold: e.nativeEvent.bold.isActive,
- isItalic: e.nativeEvent.italic.isActive,
- isUnderline: e.nativeEvent.underline.isActive,
- isStrikeThrough: e.nativeEvent.strikeThrough.isActive,
- isInlineCode: e.nativeEvent.inlineCode.isActive,
- isH1: e.nativeEvent.h1.isActive,
- isH2: e.nativeEvent.h2.isActive,
- isH3: e.nativeEvent.h3.isActive,
- isH4: e.nativeEvent.h4.isActive,
- isH5: e.nativeEvent.h5.isActive,
- isH6: e.nativeEvent.h6.isActive,
- isCodeBlock: e.nativeEvent.codeBlock.isActive,
- isBlockQuote: e.nativeEvent.blockQuote.isActive,
- isOrderedList: e.nativeEvent.orderedList.isActive,
- isUnorderedList: e.nativeEvent.unorderedList.isActive,
- isCheckboxList: e.nativeEvent.checkboxList.isActive,
- isLink: e.nativeEvent.link.isActive,
- isImage: e.nativeEvent.image.isActive,
- isMention: e.nativeEvent.mention.isActive,
- },
+ isBold: e.nativeEvent.bold.isActive,
+ isItalic: e.nativeEvent.italic.isActive,
+ isUnderline: e.nativeEvent.underline.isActive,
+ isStrikeThrough: e.nativeEvent.strikeThrough.isActive,
+ isInlineCode: e.nativeEvent.inlineCode.isActive,
+ isH1: e.nativeEvent.h1.isActive,
+ isH2: e.nativeEvent.h2.isActive,
+ isH3: e.nativeEvent.h3.isActive,
+ isH4: e.nativeEvent.h4.isActive,
+ isH5: e.nativeEvent.h5.isActive,
+ isH6: e.nativeEvent.h6.isActive,
+ isCodeBlock: e.nativeEvent.codeBlock.isActive,
+ isBlockQuote: e.nativeEvent.blockQuote.isActive,
+ isOrderedList: e.nativeEvent.orderedList.isActive,
+ isUnorderedList: e.nativeEvent.unorderedList.isActive,
+ isCheckboxList: e.nativeEvent.checkboxList.isActive,
+ isLink: e.nativeEvent.link.isActive,
+ isImage: e.nativeEvent.image.isActive,
+ isMention: e.nativeEvent.mention.isActive,
});
};
@@ -404,17 +390,17 @@ export const EnrichedTextInput = ({
linkRegex={linkRegex}
onInputFocus={onFocus}
onInputBlur={onBlur}
- onChangeText={onChangeText}
- onChangeHtml={onChangeHtml}
+ onChangeText={handleChangeText}
+ onChangeHtml={handleChangeHtml}
isOnChangeHtmlSet={onChangeHtml !== undefined}
isOnChangeTextSet={onChangeText !== undefined}
onChangeState={onChangeStateWithDeprecated}
onLinkDetected={handleLinkDetected}
onMentionDetected={handleMentionDetected}
onMention={handleMentionEvent}
- onChangeSelection={onChangeSelection}
+ onChangeSelection={handleChangeSelection}
onRequestHtmlResult={handleRequestHtmlResult}
- onInputKeyPress={onKeyPress}
+ onInputKeyPress={handleKeyPress}
androidExperimentalSynchronousEvents={
androidExperimentalSynchronousEvents
}
diff --git a/src/types.ts b/src/native/types.ts
similarity index 100%
rename from src/types.ts
rename to src/native/types.ts
diff --git a/src/spec/EnrichedTextInputNativeComponent.ts b/src/spec/EnrichedTextInputNativeComponent.ts
index f7ce7b833..6228fc02c 100644
--- a/src/spec/EnrichedTextInputNativeComponent.ts
+++ b/src/spec/EnrichedTextInputNativeComponent.ts
@@ -146,7 +146,11 @@ export interface OnChangeStateDeprecatedEvent {
isMention: boolean;
}
-export interface OnLinkDetected {
+export interface OnKeyPressEvent {
+ key: string;
+}
+
+export interface OnLinkDetectedNativeEvent {
text: string;
url: string;
start: Int32;
@@ -158,19 +162,12 @@ export interface OnMentionDetectedInternal {
indicator: string;
payload: string;
}
-
-export interface OnMentionDetected {
- text: string;
- indicator: string;
- attributes: Record;
-}
-
export interface OnMentionEvent {
indicator: string;
text: UnsafeMixed;
}
-export interface OnChangeSelectionEvent {
+export interface OnChangeSelectionNativeEvent {
start: Int32;
end: Int32;
text: string;
@@ -181,10 +178,6 @@ export interface OnRequestHtmlResultEvent {
html: UnsafeMixed;
}
-export interface OnKeyPressEvent {
- key: string;
-}
-
interface TargetedEvent {
target: Int32;
}
@@ -264,10 +257,10 @@ export interface NativeProps extends ViewProps {
onChangeText?: DirectEventHandler;
onChangeHtml?: DirectEventHandler;
onChangeState?: DirectEventHandler;
- onLinkDetected?: DirectEventHandler;
+ onLinkDetected?: DirectEventHandler;
onMentionDetected?: DirectEventHandler;
onMention?: DirectEventHandler;
- onChangeSelection?: DirectEventHandler;
+ onChangeSelection?: DirectEventHandler;
onRequestHtmlResult?: DirectEventHandler;
onInputKeyPress?: DirectEventHandler;
diff --git a/src/utils/normalizeHtmlStyle.ts b/src/utils/normalizeHtmlStyle.ts
index 3392d080e..629815980 100644
--- a/src/utils/normalizeHtmlStyle.ts
+++ b/src/utils/normalizeHtmlStyle.ts
@@ -1,6 +1,6 @@
import { type ColorValue, processColor } from 'react-native';
import type { HtmlStyleInternal } from '../spec/EnrichedTextInputNativeComponent';
-import type { HtmlStyle, MentionStyleProperties } from '../types';
+import type { HtmlStyle, MentionStyleProperties } from '../native/types';
const defaultStyle: Required = {
h1: {
diff --git a/src/web/EnrichedTextInput.tsx b/src/web/EnrichedTextInput.tsx
new file mode 100644
index 000000000..f3a13b766
--- /dev/null
+++ b/src/web/EnrichedTextInput.tsx
@@ -0,0 +1,183 @@
+import {
+ useImperativeHandle,
+ useRef,
+ type CSSProperties,
+ type RefObject,
+} from 'react';
+
+import type {
+ EnrichedTextInputInstanceBase,
+ OnChangeHtmlEvent,
+ OnChangeMentionEvent,
+ OnChangeStateDeprecatedEvent,
+ OnChangeStateEvent,
+ OnChangeTextEvent,
+ OnLinkDetected,
+ OnMentionDetected,
+ OnChangeSelectionEvent,
+ OnKeyPressEvent,
+} from '../common/types';
+import { ENRICHED_TEXT_INPUT_DEFAULTS } from '../common/defaultProps';
+
+export type EnrichedTextInputInstance = EnrichedTextInputInstanceBase;
+
+export interface MentionStyleProperties {
+ color?: string;
+ backgroundColor?: string;
+ textDecorationLine?: 'underline' | 'none';
+}
+
+type HeadingStyle = {
+ fontSize?: number;
+ bold?: boolean;
+};
+
+export interface HtmlStyle {
+ h1?: HeadingStyle;
+ h2?: HeadingStyle;
+ h3?: HeadingStyle;
+ h4?: HeadingStyle;
+ h5?: HeadingStyle;
+ h6?: HeadingStyle;
+ blockquote?: {
+ borderColor?: string;
+ borderWidth?: number;
+ gapWidth?: number;
+ color?: string;
+ };
+ codeblock?: {
+ color?: string;
+ borderRadius?: number;
+ backgroundColor?: string;
+ };
+ code?: {
+ color?: string;
+ backgroundColor?: string;
+ };
+ a?: {
+ color?: string;
+ textDecorationLine?: 'underline' | 'none';
+ };
+ mention?: Record | MentionStyleProperties;
+ ol?: {
+ gapWidth?: number;
+ marginLeft?: number;
+ markerFontWeight?: string | number;
+ markerColor?: string;
+ };
+ ul?: {
+ bulletColor?: string;
+ bulletSize?: number;
+ marginLeft?: number;
+ gapWidth?: number;
+ };
+ ulCheckbox?: {
+ boxSize?: number;
+ gapWidth?: number;
+ marginLeft?: number;
+ boxColor?: string;
+ };
+}
+
+export interface EnrichedTextInputProps {
+ ref?: RefObject;
+ autoFocus?: boolean;
+ editable?: boolean;
+ mentionIndicators?: string[];
+ defaultValue?: string;
+ placeholder?: string;
+ placeholderTextColor?: string;
+ cursorColor?: string;
+ selectionColor?: string;
+ autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
+ htmlStyle?: HtmlStyle;
+ style?: CSSProperties;
+ scrollEnabled?: boolean;
+ linkRegex?: RegExp | null;
+ onFocus?: () => void;
+ onBlur?: () => void;
+ onChangeText?: (e: OnChangeTextEvent) => void;
+ onChangeHtml?: (e: OnChangeHtmlEvent) => void;
+ onChangeState?: (e: OnChangeStateEvent) => void;
+ /**
+ * @deprecated Use onChangeState prop instead.
+ */
+ onChangeStateDeprecated?: (e: OnChangeStateDeprecatedEvent) => void;
+ onLinkDetected?: (e: OnLinkDetected) => void;
+ onMentionDetected?: (e: OnMentionDetected) => void;
+ onStartMention?: (indicator: string) => void;
+ onChangeMention?: (e: OnChangeMentionEvent) => void;
+ onEndMention?: (indicator: string) => void;
+ onChangeSelection?: (e: OnChangeSelectionEvent) => void;
+ onKeyPress?: (e: OnKeyPressEvent) => void;
+ /**
+ * Unused for web, but kept for parity with native
+ */
+ androidExperimentalSynchronousEvents?: boolean;
+}
+
+export const EnrichedTextInput = ({
+ ref,
+ autoFocus,
+ editable = ENRICHED_TEXT_INPUT_DEFAULTS.editable,
+ defaultValue,
+ placeholder,
+ style,
+}: EnrichedTextInputProps) => {
+ const inputRef = useRef(null);
+
+ useImperativeHandle(ref, () => ({
+ // General commands
+ focus: () => {
+ inputRef.current?.focus();
+ },
+ blur: () => {
+ inputRef.current?.blur();
+ },
+ setValue: (value: string) => {
+ if (inputRef.current) {
+ inputRef.current.value = value;
+ }
+ },
+ setSelection: (start: number, end: number) => {
+ inputRef.current?.setSelectionRange(start, end);
+ },
+ getHTML: () => {
+ return Promise.resolve('');
+ },
+
+ // Text formatting commands
+ toggleBold: () => {},
+ toggleItalic: () => {},
+ toggleUnderline: () => {},
+ toggleStrikeThrough: () => {},
+ toggleInlineCode: () => {},
+ toggleH1: () => {},
+ toggleH2: () => {},
+ toggleH3: () => {},
+ toggleH4: () => {},
+ toggleH5: () => {},
+ toggleH6: () => {},
+ toggleCodeBlock: () => {},
+ toggleBlockQuote: () => {},
+ toggleOrderedList: () => {},
+ toggleUnorderedList: () => {},
+ toggleCheckboxList: () => {},
+ setLink: () => {},
+ setImage: () => {},
+ startMention: () => {},
+ setMention: () => {},
+ }));
+
+ return (
+
+ );
+};
diff --git a/tsconfig.json b/tsconfig.json
index 37038f647..824b524f8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,8 +2,26 @@
"extends": "./tsconfig.base.json",
"compilerOptions": {
"rootDir": ".",
- "paths": {
- "react-native-enriched": ["./src/index"]
- }
+ "allowUnreachableCode": false,
+ "allowUnusedLabels": false,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "jsx": "react-jsx",
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "noEmit": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitReturns": true,
+ "noImplicitUseStrict": false,
+ "noStrictGenericChecks": false,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "target": "ESNext",
+ "verbatimModuleSyntax": true
}
}