react-native-richify 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/lib/commonjs/components/OverlayText.d.js.map +1 -1
  2. package/lib/commonjs/components/OverlayText.js +8 -2
  3. package/lib/commonjs/components/OverlayText.js.map +1 -1
  4. package/lib/commonjs/components/RichTextInput.d.js.map +1 -1
  5. package/lib/commonjs/components/RichTextInput.js +9 -6
  6. package/lib/commonjs/components/RichTextInput.js.map +1 -1
  7. package/lib/commonjs/components/Toolbar.d.js.map +1 -1
  8. package/lib/commonjs/components/Toolbar.js +4 -7
  9. package/lib/commonjs/components/Toolbar.js.map +1 -1
  10. package/lib/commonjs/components/ToolbarButton.d.js.map +1 -1
  11. package/lib/commonjs/components/ToolbarButton.js.map +1 -1
  12. package/lib/commonjs/constants/defaultStyles.d.js.map +1 -1
  13. package/lib/commonjs/constants/defaultStyles.js.map +1 -1
  14. package/lib/commonjs/context/RichTextContext.d.js.map +1 -1
  15. package/lib/commonjs/context/RichTextContext.js.map +1 -1
  16. package/lib/commonjs/hooks/useFormatting.d.js.map +1 -1
  17. package/lib/commonjs/hooks/useFormatting.js.map +1 -1
  18. package/lib/commonjs/hooks/useRichText.d.js.map +1 -1
  19. package/lib/commonjs/hooks/useRichText.js +7 -1
  20. package/lib/commonjs/hooks/useRichText.js.map +1 -1
  21. package/lib/commonjs/hooks/useSelection.d.js.map +1 -1
  22. package/lib/commonjs/hooks/useSelection.js.map +1 -1
  23. package/lib/commonjs/index.d.js.map +1 -1
  24. package/lib/commonjs/index.js.map +1 -1
  25. package/lib/commonjs/types/index.d.js.map +1 -1
  26. package/lib/commonjs/types/index.js.map +1 -1
  27. package/lib/commonjs/utils/formatter.d.js.map +1 -1
  28. package/lib/commonjs/utils/formatter.js.map +1 -1
  29. package/lib/commonjs/utils/parser.d.js.map +1 -1
  30. package/lib/commonjs/utils/parser.js.map +1 -1
  31. package/lib/commonjs/utils/styleMapper.d.js.map +1 -1
  32. package/lib/commonjs/utils/styleMapper.js.map +1 -1
  33. package/lib/module/components/OverlayText.d.js.map +1 -1
  34. package/lib/module/components/OverlayText.js +8 -2
  35. package/lib/module/components/OverlayText.js.map +1 -1
  36. package/lib/module/components/RichTextInput.d.js.map +1 -1
  37. package/lib/module/components/RichTextInput.js +9 -6
  38. package/lib/module/components/RichTextInput.js.map +1 -1
  39. package/lib/module/components/Toolbar.d.js.map +1 -1
  40. package/lib/module/components/Toolbar.js +4 -7
  41. package/lib/module/components/Toolbar.js.map +1 -1
  42. package/lib/module/components/ToolbarButton.d.js.map +1 -1
  43. package/lib/module/components/ToolbarButton.js.map +1 -1
  44. package/lib/module/constants/defaultStyles.d.js.map +1 -1
  45. package/lib/module/constants/defaultStyles.js.map +1 -1
  46. package/lib/module/context/RichTextContext.d.js.map +1 -1
  47. package/lib/module/context/RichTextContext.js.map +1 -1
  48. package/lib/module/hooks/useFormatting.d.js.map +1 -1
  49. package/lib/module/hooks/useFormatting.js.map +1 -1
  50. package/lib/module/hooks/useRichText.d.js.map +1 -1
  51. package/lib/module/hooks/useRichText.js +7 -1
  52. package/lib/module/hooks/useRichText.js.map +1 -1
  53. package/lib/module/hooks/useSelection.d.js.map +1 -1
  54. package/lib/module/hooks/useSelection.js.map +1 -1
  55. package/lib/module/index.d.js.map +1 -1
  56. package/lib/module/index.js.map +1 -1
  57. package/lib/module/types/index.d.js.map +1 -1
  58. package/lib/module/types/index.js.map +1 -1
  59. package/lib/module/utils/formatter.d.js.map +1 -1
  60. package/lib/module/utils/formatter.js.map +1 -1
  61. package/lib/module/utils/parser.d.js.map +1 -1
  62. package/lib/module/utils/parser.js.map +1 -1
  63. package/lib/module/utils/styleMapper.d.js.map +1 -1
  64. package/lib/module/utils/styleMapper.js.map +1 -1
  65. package/lib/typescript/src/components/OverlayText.d.ts.map +1 -1
  66. package/lib/typescript/src/components/RichTextInput.d.ts.map +1 -1
  67. package/lib/typescript/src/hooks/useRichText.d.ts.map +1 -1
  68. package/lib/typescript/src/index.d.ts +1 -1
  69. package/lib/typescript/src/index.d.ts.map +1 -1
  70. package/lib/typescript/src/types/index.d.ts +22 -10
  71. package/lib/typescript/src/types/index.d.ts.map +1 -1
  72. package/package.json +1 -1
  73. package/src/components/OverlayText.tsx +11 -3
  74. package/src/components/RichTextInput.tsx +11 -5
  75. package/src/components/Toolbar.d.ts +1 -1
  76. package/src/components/Toolbar.tsx +5 -5
  77. package/src/components/ToolbarButton.d.ts +1 -1
  78. package/src/constants/defaultStyles.d.ts +1 -1
  79. package/src/hooks/useRichText.ts +11 -4
  80. package/src/index.d.ts +1 -1
  81. package/src/index.ts +2 -0
  82. package/src/types/index.d.ts +22 -10
  83. package/src/types/index.ts +24 -10
@@ -17,21 +17,29 @@ export const OverlayText: React.FC<OverlayTextProps> = React.memo(
17
17
  const overlayStyle =
18
18
  resolvedTheme.overlayContainerStyle ??
19
19
  DEFAULT_THEME.overlayContainerStyle;
20
+ const resolvedBaseTextStyle =
21
+ baseTextStyle ??
22
+ resolvedTheme.baseTextStyle ??
23
+ DEFAULT_THEME.baseTextStyle;
24
+ const overlayTheme = {
25
+ ...resolvedTheme,
26
+ baseTextStyle: resolvedBaseTextStyle,
27
+ };
20
28
 
21
29
  return (
22
30
  <View style={overlayStyle} pointerEvents="none">
23
- <Text>
31
+ <Text style={resolvedBaseTextStyle}>
24
32
  {segments.map((segment, index) => {
25
33
  if (segment.text.length === 0 && segments.length > 1) {
26
34
  return null;
27
35
  }
28
36
 
29
- const textStyle = segmentToTextStyle(segment, resolvedTheme);
37
+ const textStyle = segmentToTextStyle(segment, overlayTheme);
30
38
 
31
39
  return (
32
40
  <Text
33
41
  key={`${index}-${segment.text.slice(0, 8)}`}
34
- style={[baseTextStyle, textStyle]}
42
+ style={textStyle}
35
43
  >
36
44
  {segment.text}
37
45
  </Text>
@@ -89,7 +89,10 @@ export const RichTextInput: React.FC<RichTextInputProps> = ({
89
89
  // Input style
90
90
  const inputStyle = [
91
91
  styles.textInput,
92
+ resolvedTheme.baseTextStyle ?? DEFAULT_THEME.baseTextStyle,
92
93
  resolvedTheme.inputStyle ?? DEFAULT_THEME.inputStyle,
94
+ textInputProps?.style,
95
+ styles.hiddenInputText,
93
96
  ];
94
97
 
95
98
  // Toolbar component
@@ -141,11 +144,12 @@ export const RichTextInput: React.FC<RichTextInputProps> = ({
141
144
  editable={editable}
142
145
  maxLength={maxLength}
143
146
  autoFocus={autoFocus}
147
+ underlineColorAndroid="transparent"
144
148
  selectionColor={
145
149
  resolvedTheme.colors?.cursor ?? DEFAULT_THEME.colors?.cursor
146
150
  }
147
151
  textAlignVertical="top"
148
- scrollEnabled={true}
152
+ scrollEnabled={typeof maxHeight === 'number'}
149
153
  />
150
154
  </View>
151
155
 
@@ -164,11 +168,13 @@ const styles = StyleSheet.create({
164
168
  position: 'relative',
165
169
  },
166
170
  textInput: {
167
- // The TextInput must be transparent so the overlay text shows through.
168
- // Only the caret/cursor and selection highlight are visible.
169
- color: 'transparent',
170
- // Ensure it matches the overlay text positioning exactly.
171
171
  position: 'relative',
172
172
  zIndex: 1,
173
173
  },
174
+ hiddenInputText: {
175
+ // Keep the editable layer invisible while preserving the caret.
176
+ color: 'transparent',
177
+ backgroundColor: 'transparent',
178
+ textShadowColor: 'transparent',
179
+ },
174
180
  });
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { ToolbarProps } from '@/types';
2
+ import type { ToolbarProps } from '../types';
3
3
  /**
4
4
  * Formatting toolbar for the rich text editor.
5
5
  *
@@ -20,17 +20,17 @@ export const Toolbar: React.FC<ToolbarProps> = React.memo(
20
20
 
21
21
  // Compute active state for each item
22
22
  const enrichedItems: ToolbarItem[] = useMemo(() => {
23
+ const selectionStyle = actions.getSelectionStyle();
24
+
23
25
  return toolbarItems.map((item) => {
24
26
  let isActive = false;
25
27
 
26
28
  if (item.format) {
27
- // Check if the format is currently active
28
- const { activeStyles } = state;
29
- isActive = !!activeStyles[item.format];
29
+ isActive = actions.isFormatActive(item.format);
30
30
  }
31
31
 
32
32
  if (item.heading) {
33
- isActive = state.activeStyles.heading === item.heading;
33
+ isActive = selectionStyle.heading === item.heading;
34
34
  }
35
35
 
36
36
  return {
@@ -38,7 +38,7 @@ export const Toolbar: React.FC<ToolbarProps> = React.memo(
38
38
  active: item.active ?? isActive,
39
39
  };
40
40
  });
41
- }, [toolbarItems, state]);
41
+ }, [actions, toolbarItems]);
42
42
 
43
43
  // Custom render
44
44
  if (renderToolbar) {
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { ToolbarButtonProps } from '@/types';
2
+ import type { ToolbarButtonProps } from '../types';
3
3
  /**
4
4
  * A single toolbar button that toggles a formatting option.
5
5
  * Supports custom rendering via the `renderButton` prop.
@@ -1,4 +1,4 @@
1
- import type { RichTextTheme, FormatStyle, ToolbarItem } from '@/types';
1
+ import type { RichTextTheme, FormatStyle, ToolbarItem } from '../types';
2
2
  /**
3
3
  * Default color palette used throughout the editor.
4
4
  */
@@ -1,9 +1,7 @@
1
- import { useState, useCallback, useRef, useEffect } from 'react';
1
+ import { useState, useCallback, useRef } from 'react';
2
2
  import type {
3
3
  StyledSegment,
4
- FormatType,
5
4
  FormatStyle,
6
- HeadingLevel,
7
5
  SelectionRange,
8
6
  RichTextState,
9
7
  RichTextActions,
@@ -16,6 +14,7 @@ import {
16
14
  reconcileTextChange,
17
15
  findPositionInSegments,
18
16
  } from '../utils/parser';
17
+ import { getSelectionStyle } from '../utils/formatter';
19
18
  import { useSelection } from '../hooks/useSelection';
20
19
  import { useFormatting } from '../hooks/useFormatting';
21
20
 
@@ -57,6 +56,8 @@ export function useRichText(
57
56
  // Refs for stable access in callbacks
58
57
  const segmentsRef = useRef(segments);
59
58
  segmentsRef.current = segments;
59
+ const selectionRef = useRef(selection);
60
+ selectionRef.current = selection;
60
61
  const activeStylesRef = useRef(activeStyles);
61
62
  activeStylesRef.current = activeStyles;
62
63
 
@@ -86,7 +87,11 @@ export function useRichText(
86
87
  const handleTextChange = useCallback(
87
88
  (newText: string) => {
88
89
  const currentSegments = segmentsRef.current;
89
- const currentActiveStyles = activeStylesRef.current;
90
+ const currentSelection = selectionRef.current;
91
+ const currentActiveStyles =
92
+ currentSelection.start === currentSelection.end
93
+ ? activeStylesRef.current
94
+ : getSelectionStyle(currentSegments, currentSelection);
90
95
 
91
96
  const newSegments = reconcileTextChange(
92
97
  currentSegments,
@@ -161,6 +166,8 @@ export function useRichText(
161
166
  setFontSize: formatting.setFontSize,
162
167
  handleTextChange,
163
168
  handleSelectionChange: onSelectionChange,
169
+ isFormatActive: formatting.isFormatActive,
170
+ getSelectionStyle: formatting.currentSelectionStyle,
164
171
  getPlainText,
165
172
  exportJSON,
166
173
  importJSON,
package/src/index.d.ts CHANGED
@@ -12,4 +12,4 @@ export { createSegment, segmentsToPlainText, getTotalLength, mergeAdjacentSegmen
12
12
  export { toggleFormatOnSelection, setStyleOnSelection, setHeadingOnLine, isFormatActiveInSelection, getSelectionStyle, } from './utils/formatter';
13
13
  export { formatStyleToTextStyle, segmentToTextStyle, segmentsToTextStyles, } from './utils/styleMapper';
14
14
  export { DEFAULT_COLORS, DEFAULT_THEME, DEFAULT_TOOLBAR_ITEMS, DEFAULT_BASE_TEXT_STYLE, HEADING_FONT_SIZES, EMPTY_FORMAT_STYLE, } from './constants/defaultStyles';
15
- export type { FormatType, HeadingLevel, ListType, FormatStyle, StyledSegment, SelectionRange, RichTextState, RichTextActions, UseRichTextReturn, RichTextTheme, ToolbarItem, OverlayTextProps, ToolbarButtonProps, ToolbarProps, RichTextInputProps, } from './types';
15
+ export type { FormatType, HeadingLevel, ListType, FormatStyle, StyledSegment, SelectionRange, RichTextState, RichTextActions, UseRichTextReturn, RichTextTheme, ToolbarItem, ToolbarButtonRenderProps, ToolbarRenderProps, OverlayTextProps, ToolbarButtonProps, ToolbarProps, RichTextInputProps, } from './types';
package/src/index.ts CHANGED
@@ -61,6 +61,8 @@ export type {
61
61
  UseRichTextReturn,
62
62
  RichTextTheme,
63
63
  ToolbarItem,
64
+ ToolbarButtonRenderProps,
65
+ ToolbarRenderProps,
64
66
  OverlayTextProps,
65
67
  ToolbarButtonProps,
66
68
  ToolbarProps,
@@ -75,6 +75,10 @@ export interface RichTextActions {
75
75
  handleTextChange: (text: string) => void;
76
76
  /** Handle selection change from TextInput. */
77
77
  handleSelectionChange: (selection: SelectionRange) => void;
78
+ /** Check whether a format is active at the current cursor/selection. */
79
+ isFormatActive: (format: FormatType) => boolean;
80
+ /** Get the effective shared style at the current cursor/selection. */
81
+ getSelectionStyle: () => FormatStyle;
78
82
  /** Get the full plain text content. */
79
83
  getPlainText: () => string;
80
84
  /** Export the segments as a serializable JSON array. */
@@ -150,11 +154,23 @@ export interface ToolbarItem {
150
154
  /** Whether this item is currently active. */
151
155
  active?: boolean;
152
156
  /** Custom render function for the button. */
153
- renderButton?: (props: {
154
- active: boolean;
155
- onPress: () => void;
156
- label: string;
157
- }) => React.ReactElement;
157
+ renderButton?: (props: ToolbarButtonRenderProps) => React.ReactElement | null;
158
+ }
159
+ /**
160
+ * Props passed to a custom toolbar button renderer.
161
+ */
162
+ export interface ToolbarButtonRenderProps {
163
+ active: boolean;
164
+ onPress: () => void;
165
+ label: string;
166
+ }
167
+ /**
168
+ * Props passed to a custom toolbar renderer.
169
+ */
170
+ export interface ToolbarRenderProps {
171
+ items: ToolbarItem[];
172
+ state: RichTextState;
173
+ actions: RichTextActions;
158
174
  }
159
175
  /**
160
176
  * Props for the OverlayText component.
@@ -197,11 +213,7 @@ export interface ToolbarProps {
197
213
  /** Whether to show the toolbar. */
198
214
  visible?: boolean;
199
215
  /** Custom render function for the entire toolbar. */
200
- renderToolbar?: (props: {
201
- items: ToolbarItem[];
202
- state: RichTextState;
203
- actions: RichTextActions;
204
- }) => React.ReactElement;
216
+ renderToolbar?: (props: ToolbarRenderProps) => React.ReactElement | null;
205
217
  }
206
218
  /**
207
219
  * Props for the main RichTextInput component.
@@ -101,6 +101,10 @@ export interface RichTextActions {
101
101
  handleTextChange: (text: string) => void;
102
102
  /** Handle selection change from TextInput. */
103
103
  handleSelectionChange: (selection: SelectionRange) => void;
104
+ /** Check whether a format is active at the current cursor/selection. */
105
+ isFormatActive: (format: FormatType) => boolean;
106
+ /** Get the effective shared style at the current cursor/selection. */
107
+ getSelectionStyle: () => FormatStyle;
104
108
  /** Get the full plain text content. */
105
109
  getPlainText: () => string;
106
110
  /** Export the segments as a serializable JSON array. */
@@ -185,11 +189,25 @@ export interface ToolbarItem {
185
189
  /** Whether this item is currently active. */
186
190
  active?: boolean;
187
191
  /** Custom render function for the button. */
188
- renderButton?: (props: {
189
- active: boolean;
190
- onPress: () => void;
191
- label: string;
192
- }) => React.ReactElement;
192
+ renderButton?: (props: ToolbarButtonRenderProps) => React.ReactElement | null;
193
+ }
194
+
195
+ /**
196
+ * Props passed to a custom toolbar button renderer.
197
+ */
198
+ export interface ToolbarButtonRenderProps {
199
+ active: boolean;
200
+ onPress: () => void;
201
+ label: string;
202
+ }
203
+
204
+ /**
205
+ * Props passed to a custom toolbar renderer.
206
+ */
207
+ export interface ToolbarRenderProps {
208
+ items: ToolbarItem[];
209
+ state: RichTextState;
210
+ actions: RichTextActions;
193
211
  }
194
212
 
195
213
  // ─── Component Props ─────────────────────────────────────────────────────────
@@ -237,11 +255,7 @@ export interface ToolbarProps {
237
255
  /** Whether to show the toolbar. */
238
256
  visible?: boolean;
239
257
  /** Custom render function for the entire toolbar. */
240
- renderToolbar?: (props: {
241
- items: ToolbarItem[];
242
- state: RichTextState;
243
- actions: RichTextActions;
244
- }) => React.ReactElement;
258
+ renderToolbar?: (props: ToolbarRenderProps) => React.ReactElement | null;
245
259
  }
246
260
 
247
261
  /**