Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/example-web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import './App.css';
import { EnrichedTextInput } from 'react-native-enriched';

function App() {
return (
<div className="container">
<div>Text input</div>
<input type="text" placeholder="Type something..." />
<EnrichedTextInput />
</div>
);
}
Expand Down
4 changes: 4 additions & 0 deletions apps/example-web/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client"],
"baseUrl": ".",
"paths": {
"react-native-enriched": ["../../src/index.tsx"]
}
},
"include": ["src"]
}
6 changes: 6 additions & 0 deletions apps/example-web/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
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.tsx'),
},
},
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "react-native-enriched",
"version": "0.4.1",
"description": "Rich Text Editor component for React Native",
"source": "./src/index.tsx",
"source": "./src/index.native.tsx",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
"exports": {
Expand Down
20 changes: 20 additions & 0 deletions src/index.native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export { EnrichedTextInput } from './native/EnrichedTextInput';
export type {
OnChangeTextEvent,
OnChangeHtmlEvent,
OnChangeStateEvent,
OnLinkDetected,
OnMentionDetected,
OnChangeSelectionEvent,
OnKeyPressEvent,
OnPasteImagesEvent,
PastedImage,
HtmlStyle,
MentionStyleProperties,
FocusEvent,
BlurEvent,
EnrichedTextInputInstance,
ContextMenuItem,
OnChangeMentionEvent,
EnrichedTextInputProps,
} from './types';
14 changes: 11 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './EnrichedTextInput';
export { EnrichedTextInput } from './web/EnrichedTextInput';
export type {
OnChangeTextEvent,
OnChangeHtmlEvent,
Expand All @@ -8,5 +8,13 @@ export type {
OnChangeSelectionEvent,
OnKeyPressEvent,
OnPasteImagesEvent,
} from './spec/EnrichedTextInputNativeComponent';
export type { HtmlStyle, MentionStyleProperties } from './types';
PastedImage,
HtmlStyle,
MentionStyleProperties,
FocusEvent,
BlurEvent,
EnrichedTextInputInstance,
ContextMenuItem,
OnChangeMentionEvent,
EnrichedTextInputProps,
} from './types';
141 changes: 16 additions & 125 deletions src/EnrichedTextInput.tsx → src/native/EnrichedTextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
type Component,
type RefObject,
useEffect,
useImperativeHandle,
useMemo,
Expand All @@ -10,140 +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 OnMentionEvent,
type OnRequestHtmlResultEvent,
type OnKeyPressEvent,
type OnPasteImagesEvent,
} from './spec/EnrichedTextInputNativeComponent';
} from '../spec/EnrichedTextInputNativeComponent';
import type {
ColorValue,
HostInstance,
MeasureInWindowOnSuccessCallback,
MeasureLayoutOnSuccessCallback,
MeasureOnSuccessCallback,
NativeMethods,
NativeSyntheticEvent,
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<TargetedEvent>;
export type BlurEvent = NativeSyntheticEvent<TargetedEvent>;

export interface EnrichedTextInputInstance extends NativeMethods {
// General commands
focus: () => void;
blur: () => void;
setValue: (value: string) => void;
setSelection: (start: number, end: number) => void;
getHTML: () => Promise<string>;

// 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<string, string>
) => 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<ViewProps, 'children'> {
ref?: RefObject<EnrichedTextInputInstance | null>;
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;
onFocus?: (e: FocusEvent) => void;
onBlur?: (e: BlurEvent) => void;
onChangeText?: (e: NativeSyntheticEvent<OnChangeTextEvent>) => void;
onChangeHtml?: (e: NativeSyntheticEvent<OnChangeHtmlEvent>) => void;
onChangeState?: (e: NativeSyntheticEvent<OnChangeStateEvent>) => void;
onLinkDetected?: (e: OnLinkDetected) => void;
onMentionDetected?: (e: OnMentionDetected) => void;
onStartMention?: (indicator: string) => void;
onChangeMention?: (e: OnChangeMentionEvent) => void;
onEndMention?: (indicator: string) => void;
onChangeSelection?: (e: NativeSyntheticEvent<OnChangeSelectionEvent>) => void;
onKeyPress?: (e: NativeSyntheticEvent<OnKeyPressEvent>) => void;
onPasteImages?: (e: NativeSyntheticEvent<OnPasteImagesEvent>) => 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(
Expand Down Expand Up @@ -402,7 +289,11 @@ export const EnrichedTextInput = ({
) => {
const { text, indicator, payload } = e.nativeEvent;
const attributes = JSON.parse(payload) as Record<string, string>;
onMentionDetected?.({ text, indicator, attributes });
onMentionDetected?.({
text,
indicator,
attributes,
} satisfies OnMentionDetected);
};

const handleRequestHtmlResult = (
Expand Down
Loading
Loading