From 4a6450ad066e1eec757b78c85ceaa768c16b7a51 Mon Sep 17 00:00:00 2001 From: Maksymilian Rojek Date: Fri, 9 Jan 2026 10:59:00 +0100 Subject: [PATCH 1/4] feat: add types to web implementation, extract common types --- apps/example-web/src/App.tsx | 2 +- src/common/types.ts | 67 ++++++ src/index.native.tsx | 9 +- src/index.tsx | 7 + src/native/EnrichedTextInput.tsx | 54 ++--- .../EnrichedTextInputNativeComponent.ts | 37 +--- src/web/EnrichedTextInput.tsx | 190 +++++++++++++++++- 7 files changed, 286 insertions(+), 80 deletions(-) create mode 100644 src/common/types.ts diff --git a/apps/example-web/src/App.tsx b/apps/example-web/src/App.tsx index d050def55..9e52b8955 100644 --- a/apps/example-web/src/App.tsx +++ b/apps/example-web/src/App.tsx @@ -5,7 +5,7 @@ function App() { return (
Text input
- +
); } diff --git a/src/common/types.ts b/src/common/types.ts new file mode 100644 index 000000000..2c746d95b --- /dev/null +++ b/src/common/types.ts @@ -0,0 +1,67 @@ +export interface OnChangeTextEvent { + value: string; +} + +export interface OnChangeHtmlEvent { + value: string; +} + +export interface OnChangeStateEvent { + isBold: boolean; + isItalic: boolean; + isUnderline: boolean; + isStrikeThrough: boolean; + isInlineCode: boolean; + isH1: boolean; + isH2: boolean; + isH3: boolean; + isCodeBlock: boolean; + isBlockQuote: boolean; + isOrderedList: boolean; + isUnorderedList: boolean; + isLink: boolean; + isImage: boolean; + isMention: boolean; +} + +export interface OnMentionDetected { + text: string; + indicator: string; + attributes: Record; +} + +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; + toggleCodeBlock: () => void; + toggleBlockQuote: () => void; + toggleOrderedList: () => void; + toggleUnorderedList: () => 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 index 92aa3862e..969870077 100644 --- a/src/index.native.tsx +++ b/src/index.native.tsx @@ -1,9 +1,12 @@ export * from './native/EnrichedTextInput'; +export type { + OnLinkDetected, + OnChangeSelectionEvent, +} from './native/EnrichedTextInputNativeComponent'; export type { OnChangeTextEvent, OnChangeHtmlEvent, + OnChangeMentionEvent, OnChangeStateEvent, - OnLinkDetected, OnMentionDetected, - OnChangeSelectionEvent, -} from './native/EnrichedTextInputNativeComponent'; +} from './common/types'; diff --git a/src/index.tsx b/src/index.tsx index 85f527f6a..6c3a48821 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1 +1,8 @@ export * from './web/EnrichedTextInput'; +export type { + OnChangeTextEvent, + OnChangeHtmlEvent, + OnChangeMentionEvent, + OnChangeStateEvent, + OnMentionDetected, +} from './common/types'; diff --git a/src/native/EnrichedTextInput.tsx b/src/native/EnrichedTextInput.tsx index 826b8e6d9..ffc855795 100644 --- a/src/native/EnrichedTextInput.tsx +++ b/src/native/EnrichedTextInput.tsx @@ -9,13 +9,9 @@ import { import EnrichedTextInputNativeComponent, { Commands, type NativeProps, - type OnChangeHtmlEvent, type OnChangeSelectionEvent, - type OnChangeStateEvent, - type OnChangeTextEvent, type OnLinkDetected, type OnMentionEvent, - type OnMentionDetected, type OnMentionDetectedInternal, type OnRequestHtmlResultEvent, type MentionStyleProperties, @@ -33,42 +29,18 @@ import type { ViewStyle, } from 'react-native'; import { normalizeHtmlStyle } from './normalizeHtmlStyle'; +import type { + OnChangeTextEvent, + OnChangeHtmlEvent, + OnChangeStateEvent, + OnMentionDetected, + OnChangeMentionEvent, + EnrichedTextInputInstanceBase, +} from '../common/types'; -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; - toggleCodeBlock: () => void; - toggleBlockQuote: () => void; - toggleOrderedList: () => void; - toggleUnorderedList: () => 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 EnrichedTextInputInstance + extends EnrichedTextInputInstanceBase, + NativeMethods {} export interface HtmlStyle { h1?: { @@ -160,7 +132,7 @@ const nullthrows = (value: T | null | undefined): T => { return value; }; -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` ); @@ -317,7 +289,7 @@ export const EnrichedTextInput = ({ }, startMention: (indicator: string) => { if (!mentionIndicators?.includes(indicator)) { - warnAboutMissconfiguredMentions(indicator); + warnAboutMisconfiguredMentions(indicator); } Commands.startMention(nullthrows(nativeRef.current), indicator); diff --git a/src/native/EnrichedTextInputNativeComponent.ts b/src/native/EnrichedTextInputNativeComponent.ts index 7d1a8e932..4359e1013 100644 --- a/src/native/EnrichedTextInputNativeComponent.ts +++ b/src/native/EnrichedTextInputNativeComponent.ts @@ -8,31 +8,11 @@ import type { import type { ColorValue, HostComponent, ViewProps } from 'react-native'; import React from 'react'; -export interface OnChangeTextEvent { - value: string; -} - -export interface OnChangeHtmlEvent { - value: string; -} - -export interface OnChangeStateEvent { - isBold: boolean; - isItalic: boolean; - isUnderline: boolean; - isStrikeThrough: boolean; - isInlineCode: boolean; - isH1: boolean; - isH2: boolean; - isH3: boolean; - isCodeBlock: boolean; - isBlockQuote: boolean; - isOrderedList: boolean; - isUnorderedList: boolean; - isLink: boolean; - isImage: boolean; - isMention: boolean; -} +import type { + OnChangeTextEvent, + OnChangeHtmlEvent, + OnChangeStateEvent, +} from '../common/types'; export interface OnLinkDetected { text: string; @@ -46,13 +26,6 @@ export interface OnMentionDetectedInternal { indicator: string; payload: string; } - -export interface OnMentionDetected { - text: string; - indicator: string; - attributes: Record; -} - export interface OnMentionEvent { indicator: string; text: UnsafeMixed; diff --git a/src/web/EnrichedTextInput.tsx b/src/web/EnrichedTextInput.tsx index 5c06b3e7a..833f7dd63 100644 --- a/src/web/EnrichedTextInput.tsx +++ b/src/web/EnrichedTextInput.tsx @@ -1,3 +1,187 @@ -export const EnrichedTextInput = () => { - return ; -}; +import { + forwardRef, + useImperativeHandle, + useRef, + type SyntheticEvent, + type CSSProperties, + type RefObject, + type ForwardedRef, +} from 'react'; + +import type { + EnrichedTextInputInstanceBase, + OnChangeHtmlEvent, + OnChangeMentionEvent, + OnChangeStateEvent, + OnChangeTextEvent, + OnMentionDetected, +} from '../common/types'; + +export type EnrichedTextInputInstance = EnrichedTextInputInstanceBase; + +export interface OnLinkDetected { + text: string; + url: string; + start: number; + end: number; +} + +export interface OnChangeSelectionEvent { + start: number; + end: number; + text: string; +} + +export interface MentionStyleProperties { + color?: string; + backgroundColor?: string; + textDecorationLine?: 'underline' | 'none'; +} + +export interface HtmlStyle { + h1?: { + fontSize?: number; + bold?: boolean; + }; + h2?: { + fontSize?: number; + bold?: boolean; + }; + h3?: { + fontSize?: number; + bold?: boolean; + }; + 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; + }; +} + +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; + onFocus?: () => void; + onBlur?: () => void; + onChangeText?: (e: SyntheticEvent) => void; + onChangeHtml?: (e: SyntheticEvent) => void; + onChangeState?: (e: SyntheticEvent) => void; + onLinkDetected?: (e: OnLinkDetected) => void; + onMentionDetected?: (e: OnMentionDetected) => void; + onStartMention?: (indicator: string) => void; + onChangeMention?: (e: OnChangeMentionEvent) => void; + onEndMention?: (indicator: string) => void; + onChangeSelection?: ( + e: SyntheticEvent + ) => void; + /** + * Unused for web, but kept for parity with native + */ + androidExperimentalSynchronousEvents?: boolean; +} + +export const EnrichedTextInput = forwardRef( + ( + props: EnrichedTextInputProps, + ref: ForwardedRef + ) => { + const { + autoFocus, + editable = true, + defaultValue, + placeholder, + style, + } = props; + + 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: () => {}, + toggleCodeBlock: () => {}, + toggleBlockQuote: () => {}, + toggleOrderedList: () => {}, + toggleUnorderedList: () => {}, + setLink: () => {}, + setImage: () => {}, + startMention: () => {}, + setMention: () => {}, + })); + + return ( + + ); + } +); From 2b70d39d099fbfc377acbe3005a3ffce95ceee20 Mon Sep 17 00:00:00 2001 From: Maksymilian Rojek Date: Tue, 13 Jan 2026 11:21:24 +0100 Subject: [PATCH 2/4] feat: extract to /common default props for EnrichedTextInput, remove forwardRef --- src/common/defaultProps.ts | 8 ++ src/native/EnrichedTextInput.tsx | 13 +-- src/web/EnrichedTextInput.tsx | 139 ++++++++++++++++--------------- 3 files changed, 85 insertions(+), 75 deletions(-) create mode 100644 src/common/defaultProps.ts 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/native/EnrichedTextInput.tsx b/src/native/EnrichedTextInput.tsx index 7129bb533..52c5eb1fa 100644 --- a/src/native/EnrichedTextInput.tsx +++ b/src/native/EnrichedTextInput.tsx @@ -39,6 +39,7 @@ import type { OnChangeMentionEvent, EnrichedTextInputInstanceBase, } from '../common/types'; +import { ENRICHED_TEXT_INPUT_DEFAULTS } from '../common/defaultProps'; export interface EnrichedTextInputInstance extends EnrichedTextInputInstanceBase, @@ -156,16 +157,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, @@ -179,8 +180,8 @@ export const EnrichedTextInput = ({ onChangeMention, onEndMention, onChangeSelection, - androidExperimentalSynchronousEvents = false, - scrollEnabled = true, + androidExperimentalSynchronousEvents = ENRICHED_TEXT_INPUT_DEFAULTS.androidExperimentalSynchronousEvents, + scrollEnabled = ENRICHED_TEXT_INPUT_DEFAULTS.scrollEnabled, ...rest }: EnrichedTextInputProps) => { const nativeRef = useRef(null); diff --git a/src/web/EnrichedTextInput.tsx b/src/web/EnrichedTextInput.tsx index ed690f7bf..ade9ef7c5 100644 --- a/src/web/EnrichedTextInput.tsx +++ b/src/web/EnrichedTextInput.tsx @@ -1,21 +1,21 @@ import { - forwardRef, useImperativeHandle, useRef, type SyntheticEvent, type CSSProperties, type RefObject, - type ForwardedRef, } from 'react'; import type { EnrichedTextInputInstanceBase, OnChangeHtmlEvent, OnChangeMentionEvent, + OnChangeStateDeprecatedEvent, OnChangeStateEvent, OnChangeTextEvent, OnMentionDetected, } from '../common/types'; +import { ENRICHED_TEXT_INPUT_DEFAULTS } from '../common/defaultProps'; export type EnrichedTextInputInstance = EnrichedTextInputInstanceBase; @@ -98,11 +98,18 @@ export interface EnrichedTextInputProps { htmlStyle?: HtmlStyle; style?: CSSProperties; scrollEnabled?: boolean; + linkRegex?: RegExp | null; onFocus?: () => void; onBlur?: () => void; onChangeText?: (e: SyntheticEvent) => void; onChangeHtml?: (e: SyntheticEvent) => void; onChangeState?: (e: SyntheticEvent) => void; + /** + * @deprecated Use onChangeState prop instead. + */ + onChangeStateDeprecated?: ( + e: SyntheticEvent + ) => void; onLinkDetected?: (e: OnLinkDetected) => void; onMentionDetected?: (e: OnMentionDetected) => void; onStartMention?: (indicator: string) => void; @@ -117,73 +124,67 @@ export interface EnrichedTextInputProps { androidExperimentalSynchronousEvents?: boolean; } -export const EnrichedTextInput = forwardRef( - ( - props: EnrichedTextInputProps, - ref: ForwardedRef - ) => { - const { - autoFocus, - editable = true, - defaultValue, - placeholder, - style, - } = props; +export const EnrichedTextInput = ({ + ref, + autoFocus, + editable = ENRICHED_TEXT_INPUT_DEFAULTS.editable, + defaultValue, + placeholder, + style, +}: EnrichedTextInputProps) => { + const inputRef = useRef(null); - 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(''); + }, - 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: () => {}, + setLink: () => {}, + setImage: () => {}, + startMention: () => {}, + setMention: () => {}, + })); - // Text formatting commands - toggleBold: () => {}, - toggleItalic: () => {}, - toggleUnderline: () => {}, - toggleStrikeThrough: () => {}, - toggleInlineCode: () => {}, - toggleH1: () => {}, - toggleH2: () => {}, - toggleH3: () => {}, - toggleH4: () => {}, - toggleH5: () => {}, - toggleH6: () => {}, - toggleCodeBlock: () => {}, - toggleBlockQuote: () => {}, - toggleOrderedList: () => {}, - toggleUnorderedList: () => {}, - setLink: () => {}, - setImage: () => {}, - startMention: () => {}, - setMention: () => {}, - })); - - return ( - - ); - } -); + return ( + + ); +}; From c30730407b9818a9ad6d32652d2e8b771ca28a33 Mon Sep 17 00:00:00 2001 From: Maksymilian Rojek Date: Thu, 15 Jan 2026 17:48:09 +0100 Subject: [PATCH 3/4] feat: change event types of event handlers --- apps/example/src/App.tsx | 10 +-- src/common/types.ts | 17 +++++ src/index.native.tsx | 8 +-- src/index.tsx | 3 + src/native/EnrichedTextInput.tsx | 66 ++++++++++++++----- .../EnrichedTextInputNativeComponent.ts | 13 ++-- src/web/EnrichedTextInput.tsx | 32 +++------ 7 files changed, 90 insertions(+), 59 deletions(-) diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index 6edefc174..94f3c441d 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -317,9 +317,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} @@ -327,8 +327,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/src/common/types.ts b/src/common/types.ts index 1c1d79465..247030d23 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -126,11 +126,28 @@ export interface OnMentionDetected { 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 OnKeyPressEvent { + key: string; +} + export interface EnrichedTextInputInstanceBase { // General commands focus: () => void; diff --git a/src/index.native.tsx b/src/index.native.tsx index 59ea5f9ed..ce23e8162 100644 --- a/src/index.native.tsx +++ b/src/index.native.tsx @@ -1,14 +1,12 @@ export * from './native/EnrichedTextInput'; -export type { - OnLinkDetected, - OnChangeSelectionEvent, - OnKeyPressEvent, -} from './native/EnrichedTextInputNativeComponent'; export type { OnChangeTextEvent, OnChangeHtmlEvent, OnChangeMentionEvent, + OnChangeSelectionEvent, OnChangeStateEvent, OnChangeStateDeprecatedEvent, + OnKeyPressEvent, + OnLinkDetected, OnMentionDetected, } from './common/types'; diff --git a/src/index.tsx b/src/index.tsx index 265dc5787..8aed07c53 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,7 +3,10 @@ export type { OnChangeTextEvent, OnChangeHtmlEvent, OnChangeMentionEvent, + OnChangeSelectionEvent, OnChangeStateEvent, OnChangeStateDeprecatedEvent, + OnKeyPressEvent, + OnLinkDetected, OnMentionDetected, } from './common/types'; diff --git a/src/native/EnrichedTextInput.tsx b/src/native/EnrichedTextInput.tsx index 6cb41eeae..69e1a6bff 100644 --- a/src/native/EnrichedTextInput.tsx +++ b/src/native/EnrichedTextInput.tsx @@ -9,13 +9,12 @@ import { import EnrichedTextInputNativeComponent, { Commands, type NativeProps, - type OnChangeSelectionEvent, - type OnLinkDetected, + type OnChangeSelectionNativeEvent, + type OnLinkDetectedNativeEvent, type OnMentionEvent, type OnMentionDetectedInternal, type OnRequestHtmlResultEvent, type MentionStyleProperties, - type OnKeyPressEvent, } from './EnrichedTextInputNativeComponent'; import type { ColorValue, @@ -37,7 +36,10 @@ import type { OnChangeStateEvent, OnChangeStateDeprecatedEvent, OnMentionDetected, + OnLinkDetected, + OnChangeSelectionEvent, OnChangeMentionEvent, + OnKeyPressEvent, EnrichedTextInputInstanceBase, } from '../common/types'; import { ENRICHED_TEXT_INPUT_DEFAULTS } from '../common/defaultProps'; @@ -109,22 +111,20 @@ export interface EnrichedTextInputProps extends Omit { linkRegex?: RegExp | null; onFocus?: () => void; onBlur?: () => 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. @@ -342,11 +342,41 @@ 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 handleChangeState = (e: NativeSyntheticEvent) => { + onChangeState?.(e.nativeEvent); + }; + + const handleChangeStateDeprecated = ( + e: NativeSyntheticEvent + ) => { + onChangeStateDeprecated?.(e.nativeEvent); + }; + + const handleKeyPress = (e: NativeSyntheticEvent) => { + onKeyPress?.(e.nativeEvent); + }; + + const handleChangeSelection = ( + e: NativeSyntheticEvent + ) => { + onChangeSelection?.(e.nativeEvent); + }; + const handleMentionDetected = ( e: NativeSyntheticEvent ) => { @@ -388,18 +418,18 @@ export const EnrichedTextInput = ({ linkRegex={linkRegex} onInputFocus={onFocus} onInputBlur={onBlur} - onChangeText={onChangeText} - onChangeHtml={onChangeHtml} + onChangeText={handleChangeText} + onChangeHtml={handleChangeHtml} isOnChangeHtmlSet={onChangeHtml !== undefined} isOnChangeTextSet={onChangeText !== undefined} - onChangeState={onChangeState} - onChangeStateDeprecated={onChangeStateDeprecated} + onChangeState={handleChangeState} + onChangeStateDeprecated={handleChangeStateDeprecated} onLinkDetected={handleLinkDetected} onMentionDetected={handleMentionDetected} onMention={handleMentionEvent} - onChangeSelection={onChangeSelection} + onChangeSelection={handleChangeSelection} onRequestHtmlResult={handleRequestHtmlResult} - onInputKeyPress={onKeyPress} + onInputKeyPress={handleKeyPress} androidExperimentalSynchronousEvents={ androidExperimentalSynchronousEvents } diff --git a/src/native/EnrichedTextInputNativeComponent.ts b/src/native/EnrichedTextInputNativeComponent.ts index 07615bb71..c41816990 100644 --- a/src/native/EnrichedTextInputNativeComponent.ts +++ b/src/native/EnrichedTextInputNativeComponent.ts @@ -23,9 +23,10 @@ import type { OnChangeHtmlEvent, OnChangeStateEvent, OnChangeStateDeprecatedEvent, + OnKeyPressEvent, } from '../common/types'; -export interface OnLinkDetected { +export interface OnLinkDetectedNativeEvent { text: string; url: string; start: Int32; @@ -42,7 +43,7 @@ export interface OnMentionEvent { text: UnsafeMixed; } -export interface OnChangeSelectionEvent { +export interface OnChangeSelectionNativeEvent { start: Int32; end: Int32; text: string; @@ -59,10 +60,6 @@ export interface MentionStyleProperties { textDecorationLine?: 'underline' | 'none'; } -export interface OnKeyPressEvent { - key: string; -} - type Heading = { fontSize?: Float; bold?: boolean; @@ -133,10 +130,10 @@ export interface NativeProps extends ViewProps { onChangeHtml?: DirectEventHandler; onChangeState?: DirectEventHandler; onChangeStateDeprecated?: DirectEventHandler; - onLinkDetected?: DirectEventHandler; + onLinkDetected?: DirectEventHandler; onMentionDetected?: DirectEventHandler; onMention?: DirectEventHandler; - onChangeSelection?: DirectEventHandler; + onChangeSelection?: DirectEventHandler; onRequestHtmlResult?: DirectEventHandler; onInputKeyPress?: DirectEventHandler; diff --git a/src/web/EnrichedTextInput.tsx b/src/web/EnrichedTextInput.tsx index ade9ef7c5..3bbfcaeeb 100644 --- a/src/web/EnrichedTextInput.tsx +++ b/src/web/EnrichedTextInput.tsx @@ -1,7 +1,6 @@ import { useImperativeHandle, useRef, - type SyntheticEvent, type CSSProperties, type RefObject, } from 'react'; @@ -13,25 +12,15 @@ import type { OnChangeStateDeprecatedEvent, OnChangeStateEvent, OnChangeTextEvent, + OnLinkDetected, OnMentionDetected, + OnChangeSelectionEvent, + OnKeyPressEvent, } from '../common/types'; import { ENRICHED_TEXT_INPUT_DEFAULTS } from '../common/defaultProps'; export type EnrichedTextInputInstance = EnrichedTextInputInstanceBase; -export interface OnLinkDetected { - text: string; - url: string; - start: number; - end: number; -} - -export interface OnChangeSelectionEvent { - start: number; - end: number; - text: string; -} - export interface MentionStyleProperties { color?: string; backgroundColor?: string; @@ -101,23 +90,20 @@ export interface EnrichedTextInputProps { linkRegex?: RegExp | null; onFocus?: () => void; onBlur?: () => void; - onChangeText?: (e: SyntheticEvent) => void; - onChangeHtml?: (e: SyntheticEvent) => void; - onChangeState?: (e: SyntheticEvent) => void; + onChangeText?: (e: OnChangeTextEvent) => void; + onChangeHtml?: (e: OnChangeHtmlEvent) => void; + onChangeState?: (e: OnChangeStateEvent) => void; /** * @deprecated Use onChangeState prop instead. */ - onChangeStateDeprecated?: ( - e: SyntheticEvent - ) => 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: SyntheticEvent - ) => void; + onChangeSelection?: (e: OnChangeSelectionEvent) => void; + onKeyPress?: (e: OnKeyPressEvent) => void; /** * Unused for web, but kept for parity with native */ From d5d05288aafc013eb60ca0443fa9cd18dfc95640 Mon Sep 17 00:00:00 2001 From: Maksymilian Rojek Date: Fri, 16 Jan 2026 11:13:11 +0100 Subject: [PATCH 4/4] fix: codegen failing to find imported types --- src/common/types.ts | 133 ++---------------- .../EnrichedTextInputNativeComponent.ts | 132 ++++++++++++++++- 2 files changed, 133 insertions(+), 132 deletions(-) diff --git a/src/common/types.ts b/src/common/types.ts index 247030d23..1a8ccd729 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,124 +1,11 @@ -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; - }; -} - -export interface OnChangeStateDeprecatedEvent { - isBold: boolean; - isItalic: boolean; - isUnderline: boolean; - isStrikeThrough: boolean; - isInlineCode: boolean; - isH1: boolean; - isH2: boolean; - isH3: boolean; - isH4: boolean; - isH5: boolean; - isH6: boolean; - isCodeBlock: boolean; - isBlockQuote: boolean; - isOrderedList: boolean; - isUnorderedList: boolean; - isLink: boolean; - isImage: boolean; - isMention: boolean; -} +// Re-export event types from the NativeComponent spec file (source of truth for Codegen) +export type { + OnChangeTextEvent, + OnChangeHtmlEvent, + OnChangeStateEvent, + OnChangeStateDeprecatedEvent, + OnKeyPressEvent, +} from '../native/EnrichedTextInputNativeComponent'; export interface OnMentionDetected { text: string; @@ -144,10 +31,6 @@ export interface OnChangeMentionEvent { text: string; } -export interface OnKeyPressEvent { - key: string; -} - export interface EnrichedTextInputInstanceBase { // General commands focus: () => void; diff --git a/src/native/EnrichedTextInputNativeComponent.ts b/src/native/EnrichedTextInputNativeComponent.ts index c41816990..1485cc355 100644 --- a/src/native/EnrichedTextInputNativeComponent.ts +++ b/src/native/EnrichedTextInputNativeComponent.ts @@ -18,13 +18,131 @@ export interface LinkNativeRegex { isDefault: boolean; } -import type { - OnChangeTextEvent, - OnChangeHtmlEvent, - OnChangeStateEvent, - OnChangeStateDeprecatedEvent, - OnKeyPressEvent, -} from '../common/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; + }; +} + +export interface OnChangeStateDeprecatedEvent { + isBold: boolean; + isItalic: boolean; + isUnderline: boolean; + isStrikeThrough: boolean; + isInlineCode: boolean; + isH1: boolean; + isH2: boolean; + isH3: boolean; + isH4: boolean; + isH5: boolean; + isH6: boolean; + isCodeBlock: boolean; + isBlockQuote: boolean; + isOrderedList: boolean; + isUnorderedList: boolean; + isLink: boolean; + isImage: boolean; + isMention: boolean; +} + +export interface OnKeyPressEvent { + key: string; +} export interface OnLinkDetectedNativeEvent { text: string;