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 (
Text input
- +
); } 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 } }