react-native-richify 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/lib/commonjs/components/OverlayText.d.js +6 -0
- package/lib/commonjs/components/OverlayText.d.js.map +1 -0
- package/lib/commonjs/components/OverlayText.js +45 -0
- package/lib/commonjs/components/OverlayText.js.map +1 -0
- package/lib/commonjs/components/RichTextInput.d.js +6 -0
- package/lib/commonjs/components/RichTextInput.d.js.map +1 -0
- package/lib/commonjs/components/RichTextInput.js +160 -0
- package/lib/commonjs/components/RichTextInput.js.map +1 -0
- package/lib/commonjs/components/Toolbar.d.js +6 -0
- package/lib/commonjs/components/Toolbar.d.js.map +1 -0
- package/lib/commonjs/components/Toolbar.js +99 -0
- package/lib/commonjs/components/Toolbar.js.map +1 -0
- package/lib/commonjs/components/ToolbarButton.d.js +6 -0
- package/lib/commonjs/components/ToolbarButton.d.js.map +1 -0
- package/lib/commonjs/components/ToolbarButton.js +63 -0
- package/lib/commonjs/components/ToolbarButton.js.map +1 -0
- package/lib/commonjs/constants/defaultStyles.d.js +6 -0
- package/lib/commonjs/constants/defaultStyles.d.js.map +1 -0
- package/lib/commonjs/constants/defaultStyles.js +172 -0
- package/lib/commonjs/constants/defaultStyles.js.map +1 -0
- package/lib/commonjs/context/RichTextContext.d.js +6 -0
- package/lib/commonjs/context/RichTextContext.d.js.map +1 -0
- package/lib/commonjs/context/RichTextContext.js +61 -0
- package/lib/commonjs/context/RichTextContext.js.map +1 -0
- package/lib/commonjs/hooks/useFormatting.d.js +6 -0
- package/lib/commonjs/hooks/useFormatting.d.js.map +1 -0
- package/lib/commonjs/hooks/useFormatting.js +82 -0
- package/lib/commonjs/hooks/useFormatting.js.map +1 -0
- package/lib/commonjs/hooks/useRichText.d.js +6 -0
- package/lib/commonjs/hooks/useRichText.d.js.map +1 -0
- package/lib/commonjs/hooks/useRichText.js +136 -0
- package/lib/commonjs/hooks/useRichText.js.map +1 -0
- package/lib/commonjs/hooks/useSelection.d.js +6 -0
- package/lib/commonjs/hooks/useSelection.d.js.map +1 -0
- package/lib/commonjs/hooks/useSelection.js +39 -0
- package/lib/commonjs/hooks/useSelection.js.map +1 -0
- package/lib/commonjs/index.d.js +186 -0
- package/lib/commonjs/index.d.js.map +1 -0
- package/lib/commonjs/index.js +186 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/types/index.d.js +6 -0
- package/lib/commonjs/types/index.d.js.map +1 -0
- package/lib/commonjs/types/index.js +6 -0
- package/lib/commonjs/types/index.js.map +1 -0
- package/lib/commonjs/utils/formatter.d.js +13 -0
- package/lib/commonjs/utils/formatter.d.js.map +1 -0
- package/lib/commonjs/utils/formatter.js +229 -0
- package/lib/commonjs/utils/formatter.js.map +1 -0
- package/lib/commonjs/utils/parser.d.js +6 -0
- package/lib/commonjs/utils/parser.d.js.map +1 -0
- package/lib/commonjs/utils/parser.js +221 -0
- package/lib/commonjs/utils/parser.js.map +1 -0
- package/lib/commonjs/utils/styleMapper.d.js +6 -0
- package/lib/commonjs/utils/styleMapper.d.js.map +1 -0
- package/lib/commonjs/utils/styleMapper.js +87 -0
- package/lib/commonjs/utils/styleMapper.js.map +1 -0
- package/lib/module/components/OverlayText.d.js +4 -0
- package/lib/module/components/OverlayText.d.js.map +1 -0
- package/lib/module/components/OverlayText.js +41 -0
- package/lib/module/components/OverlayText.js.map +1 -0
- package/lib/module/components/RichTextInput.d.js +4 -0
- package/lib/module/components/RichTextInput.d.js.map +1 -0
- package/lib/module/components/RichTextInput.js +155 -0
- package/lib/module/components/RichTextInput.js.map +1 -0
- package/lib/module/components/Toolbar.d.js +4 -0
- package/lib/module/components/Toolbar.d.js.map +1 -0
- package/lib/module/components/Toolbar.js +95 -0
- package/lib/module/components/Toolbar.js.map +1 -0
- package/lib/module/components/ToolbarButton.d.js +4 -0
- package/lib/module/components/ToolbarButton.d.js.map +1 -0
- package/lib/module/components/ToolbarButton.js +59 -0
- package/lib/module/components/ToolbarButton.js.map +1 -0
- package/lib/module/constants/defaultStyles.d.js +4 -0
- package/lib/module/constants/defaultStyles.d.js.map +1 -0
- package/lib/module/constants/defaultStyles.js +168 -0
- package/lib/module/constants/defaultStyles.js.map +1 -0
- package/lib/module/context/RichTextContext.d.js +4 -0
- package/lib/module/context/RichTextContext.d.js.map +1 -0
- package/lib/module/context/RichTextContext.js +55 -0
- package/lib/module/context/RichTextContext.js.map +1 -0
- package/lib/module/hooks/useFormatting.d.js +11 -0
- package/lib/module/hooks/useFormatting.d.js.map +1 -0
- package/lib/module/hooks/useFormatting.js +78 -0
- package/lib/module/hooks/useFormatting.js.map +1 -0
- package/lib/module/hooks/useRichText.d.js +4 -0
- package/lib/module/hooks/useRichText.d.js.map +1 -0
- package/lib/module/hooks/useRichText.js +132 -0
- package/lib/module/hooks/useRichText.js.map +1 -0
- package/lib/module/hooks/useSelection.d.js +4 -0
- package/lib/module/hooks/useSelection.d.js.map +1 -0
- package/lib/module/hooks/useSelection.js +35 -0
- package/lib/module/hooks/useSelection.js.map +1 -0
- package/lib/module/index.d.js +15 -0
- package/lib/module/index.d.js.map +1 -0
- package/lib/module/index.js +25 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/types/index.d.js +4 -0
- package/lib/module/types/index.d.js.map +1 -0
- package/lib/module/types/index.js +4 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/module/utils/formatter.d.js +30 -0
- package/lib/module/utils/formatter.d.js.map +1 -0
- package/lib/module/utils/formatter.js +217 -0
- package/lib/module/utils/formatter.js.map +1 -0
- package/lib/module/utils/parser.d.js +4 -0
- package/lib/module/utils/parser.d.js.map +1 -0
- package/lib/module/utils/parser.js +211 -0
- package/lib/module/utils/parser.js.map +1 -0
- package/lib/module/utils/styleMapper.d.js +4 -0
- package/lib/module/utils/styleMapper.d.js.map +1 -0
- package/lib/module/utils/styleMapper.js +82 -0
- package/lib/module/utils/styleMapper.js.map +1 -0
- package/lib/typescript/src/components/OverlayText.d.ts +11 -0
- package/lib/typescript/src/components/OverlayText.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextInput.d.ts +21 -0
- package/lib/typescript/src/components/RichTextInput.d.ts.map +1 -0
- package/lib/typescript/src/components/Toolbar.d.ts +13 -0
- package/lib/typescript/src/components/Toolbar.d.ts.map +1 -0
- package/lib/typescript/src/components/ToolbarButton.d.ts +8 -0
- package/lib/typescript/src/components/ToolbarButton.d.ts.map +1 -0
- package/lib/typescript/src/constants/defaultStyles.d.ts +46 -0
- package/lib/typescript/src/constants/defaultStyles.d.ts.map +1 -0
- package/lib/typescript/src/context/RichTextContext.d.ts +31 -0
- package/lib/typescript/src/context/RichTextContext.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useFormatting.d.ts +26 -0
- package/lib/typescript/src/hooks/useFormatting.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useRichText.d.ts +17 -0
- package/lib/typescript/src/hooks/useRichText.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSelection.d.ts +14 -0
- package/lib/typescript/src/hooks/useSelection.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +16 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types/index.d.ts +245 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -0
- package/lib/typescript/src/utils/formatter.d.ts +29 -0
- package/lib/typescript/src/utils/formatter.d.ts.map +1 -0
- package/lib/typescript/src/utils/parser.d.ts +46 -0
- package/lib/typescript/src/utils/parser.d.ts.map +1 -0
- package/lib/typescript/src/utils/styleMapper.d.ts +16 -0
- package/lib/typescript/src/utils/styleMapper.d.ts.map +1 -0
- package/package.json +83 -0
- package/src/components/OverlayText.d.ts +10 -0
- package/src/components/OverlayText.tsx +46 -0
- package/src/components/RichTextInput.d.ts +20 -0
- package/src/components/RichTextInput.tsx +174 -0
- package/src/components/Toolbar.d.ts +12 -0
- package/src/components/Toolbar.tsx +100 -0
- package/src/components/ToolbarButton.d.ts +7 -0
- package/src/components/ToolbarButton.tsx +65 -0
- package/src/constants/defaultStyles.d.ts +45 -0
- package/src/constants/defaultStyles.ts +144 -0
- package/src/context/RichTextContext.d.ts +30 -0
- package/src/context/RichTextContext.tsx +63 -0
- package/src/hooks/useFormatting.d.ts +25 -0
- package/src/hooks/useFormatting.ts +135 -0
- package/src/hooks/useRichText.d.ts +16 -0
- package/src/hooks/useRichText.ts +171 -0
- package/src/hooks/useSelection.d.ts +13 -0
- package/src/hooks/useSelection.ts +40 -0
- package/src/index.d.ts +15 -0
- package/src/index.ts +68 -0
- package/src/types/index.d.ts +244 -0
- package/src/types/index.ts +295 -0
- package/src/utils/formatter.d.ts +28 -0
- package/src/utils/formatter.ts +276 -0
- package/src/utils/parser.d.ts +45 -0
- package/src/utils/parser.ts +252 -0
- package/src/utils/styleMapper.d.ts +15 -0
- package/src/utils/styleMapper.ts +92 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
StyledSegment,
|
|
4
|
+
FormatType,
|
|
5
|
+
FormatStyle,
|
|
6
|
+
HeadingLevel,
|
|
7
|
+
SelectionRange,
|
|
8
|
+
} from '@/types';
|
|
9
|
+
import {
|
|
10
|
+
toggleFormatOnSelection,
|
|
11
|
+
setStyleOnSelection,
|
|
12
|
+
setHeadingOnLine,
|
|
13
|
+
isFormatActiveInSelection,
|
|
14
|
+
getSelectionStyle,
|
|
15
|
+
} from '@/utils/formatter';
|
|
16
|
+
|
|
17
|
+
interface UseFormattingOptions {
|
|
18
|
+
segments: StyledSegment[];
|
|
19
|
+
selection: SelectionRange;
|
|
20
|
+
activeStyles: FormatStyle;
|
|
21
|
+
onSegmentsChange: (segments: StyledSegment[]) => void;
|
|
22
|
+
onActiveStylesChange: (styles: FormatStyle) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Hook that provides formatting commands for the rich text editor.
|
|
27
|
+
*
|
|
28
|
+
* Handles both selection-based formatting (when text is selected)
|
|
29
|
+
* and active-style updates (when no text is selected — affects next typed text).
|
|
30
|
+
*/
|
|
31
|
+
export function useFormatting({
|
|
32
|
+
segments,
|
|
33
|
+
selection,
|
|
34
|
+
activeStyles,
|
|
35
|
+
onSegmentsChange,
|
|
36
|
+
onActiveStylesChange,
|
|
37
|
+
}: UseFormattingOptions) {
|
|
38
|
+
const toggleFormat = useCallback(
|
|
39
|
+
(format: FormatType) => {
|
|
40
|
+
if (selection.start === selection.end) {
|
|
41
|
+
// No selection — toggle active style for next typed text
|
|
42
|
+
onActiveStylesChange({
|
|
43
|
+
...activeStyles,
|
|
44
|
+
[format]: !activeStyles[format],
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
// Has selection — toggle format on selected text
|
|
48
|
+
const newSegments = toggleFormatOnSelection(
|
|
49
|
+
segments,
|
|
50
|
+
selection,
|
|
51
|
+
format,
|
|
52
|
+
);
|
|
53
|
+
onSegmentsChange(newSegments);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[segments, selection, activeStyles, onSegmentsChange, onActiveStylesChange],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const setStyleProperty = useCallback(
|
|
60
|
+
<K extends keyof FormatStyle>(key: K, value: FormatStyle[K]) => {
|
|
61
|
+
if (selection.start === selection.end) {
|
|
62
|
+
onActiveStylesChange({
|
|
63
|
+
...activeStyles,
|
|
64
|
+
[key]: value,
|
|
65
|
+
});
|
|
66
|
+
} else {
|
|
67
|
+
const newSegments = setStyleOnSelection(
|
|
68
|
+
segments,
|
|
69
|
+
selection,
|
|
70
|
+
key,
|
|
71
|
+
value,
|
|
72
|
+
);
|
|
73
|
+
onSegmentsChange(newSegments);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[segments, selection, activeStyles, onSegmentsChange, onActiveStylesChange],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const setHeading = useCallback(
|
|
80
|
+
(level: HeadingLevel) => {
|
|
81
|
+
const newSegments = setHeadingOnLine(segments, selection, level);
|
|
82
|
+
onSegmentsChange(newSegments);
|
|
83
|
+
},
|
|
84
|
+
[segments, selection, onSegmentsChange],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const setColor = useCallback(
|
|
88
|
+
(color: string) => {
|
|
89
|
+
setStyleProperty('color', color);
|
|
90
|
+
},
|
|
91
|
+
[setStyleProperty],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const setBackgroundColor = useCallback(
|
|
95
|
+
(color: string) => {
|
|
96
|
+
setStyleProperty('backgroundColor', color);
|
|
97
|
+
},
|
|
98
|
+
[setStyleProperty],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const setFontSize = useCallback(
|
|
102
|
+
(size: number) => {
|
|
103
|
+
setStyleProperty('fontSize', size);
|
|
104
|
+
},
|
|
105
|
+
[setStyleProperty],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const isFormatActive = useCallback(
|
|
109
|
+
(format: FormatType): boolean => {
|
|
110
|
+
if (selection.start === selection.end) {
|
|
111
|
+
return !!activeStyles[format];
|
|
112
|
+
}
|
|
113
|
+
return isFormatActiveInSelection(segments, selection, format);
|
|
114
|
+
},
|
|
115
|
+
[segments, selection, activeStyles],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const currentSelectionStyle = useCallback((): FormatStyle => {
|
|
119
|
+
if (selection.start === selection.end) {
|
|
120
|
+
return activeStyles;
|
|
121
|
+
}
|
|
122
|
+
return getSelectionStyle(segments, selection);
|
|
123
|
+
}, [segments, selection, activeStyles]);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
toggleFormat,
|
|
127
|
+
setStyleProperty,
|
|
128
|
+
setHeading,
|
|
129
|
+
setColor,
|
|
130
|
+
setBackgroundColor,
|
|
131
|
+
setFontSize,
|
|
132
|
+
isFormatActive,
|
|
133
|
+
currentSelectionStyle,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StyledSegment, UseRichTextReturn } from '@/types';
|
|
2
|
+
export interface UseRichTextOptions {
|
|
3
|
+
/** Initial segments to populate the editor with. */
|
|
4
|
+
initialSegments?: StyledSegment[];
|
|
5
|
+
/** Callback when segments change. */
|
|
6
|
+
onChangeSegments?: (segments: StyledSegment[]) => void;
|
|
7
|
+
/** Callback when plain text changes. */
|
|
8
|
+
onChangeText?: (text: string) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Main hook for the rich text editor.
|
|
12
|
+
*
|
|
13
|
+
* Manages the complete editor state (segments, selection, active styles)
|
|
14
|
+
* and exposes all actions needed to build a rich text UI.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useRichText(options?: UseRichTextOptions): UseRichTextReturn;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
StyledSegment,
|
|
4
|
+
FormatType,
|
|
5
|
+
FormatStyle,
|
|
6
|
+
HeadingLevel,
|
|
7
|
+
SelectionRange,
|
|
8
|
+
RichTextState,
|
|
9
|
+
RichTextActions,
|
|
10
|
+
UseRichTextReturn,
|
|
11
|
+
} from '@/types';
|
|
12
|
+
import { EMPTY_FORMAT_STYLE } from '@/constants/defaultStyles';
|
|
13
|
+
import {
|
|
14
|
+
createSegment,
|
|
15
|
+
segmentsToPlainText,
|
|
16
|
+
reconcileTextChange,
|
|
17
|
+
findPositionInSegments,
|
|
18
|
+
} from '@/utils/parser';
|
|
19
|
+
import { useSelection } from '@/hooks/useSelection';
|
|
20
|
+
import { useFormatting } from '@/hooks/useFormatting';
|
|
21
|
+
|
|
22
|
+
export interface UseRichTextOptions {
|
|
23
|
+
/** Initial segments to populate the editor with. */
|
|
24
|
+
initialSegments?: StyledSegment[];
|
|
25
|
+
/** Callback when segments change. */
|
|
26
|
+
onChangeSegments?: (segments: StyledSegment[]) => void;
|
|
27
|
+
/** Callback when plain text changes. */
|
|
28
|
+
onChangeText?: (text: string) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Main hook for the rich text editor.
|
|
33
|
+
*
|
|
34
|
+
* Manages the complete editor state (segments, selection, active styles)
|
|
35
|
+
* and exposes all actions needed to build a rich text UI.
|
|
36
|
+
*/
|
|
37
|
+
export function useRichText(
|
|
38
|
+
options: UseRichTextOptions = {},
|
|
39
|
+
): UseRichTextReturn {
|
|
40
|
+
const { initialSegments, onChangeSegments, onChangeText } = options;
|
|
41
|
+
|
|
42
|
+
// ─── State ───────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
const [segments, setSegments] = useState<StyledSegment[]>(() => {
|
|
45
|
+
if (initialSegments && initialSegments.length > 0) {
|
|
46
|
+
return initialSegments;
|
|
47
|
+
}
|
|
48
|
+
return [createSegment('')];
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const [activeStyles, setActiveStyles] = useState<FormatStyle>({
|
|
52
|
+
...EMPTY_FORMAT_STYLE,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const { selection, handleSelectionChange } = useSelection();
|
|
56
|
+
|
|
57
|
+
// Refs for stable access in callbacks
|
|
58
|
+
const segmentsRef = useRef(segments);
|
|
59
|
+
segmentsRef.current = segments;
|
|
60
|
+
const activeStylesRef = useRef(activeStyles);
|
|
61
|
+
activeStylesRef.current = activeStyles;
|
|
62
|
+
|
|
63
|
+
// ─── Segment Change Handler ──────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
const updateSegments = useCallback(
|
|
66
|
+
(newSegments: StyledSegment[]) => {
|
|
67
|
+
setSegments(newSegments);
|
|
68
|
+
onChangeSegments?.(newSegments);
|
|
69
|
+
onChangeText?.(segmentsToPlainText(newSegments));
|
|
70
|
+
},
|
|
71
|
+
[onChangeSegments, onChangeText],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// ─── Formatting ──────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
const formatting = useFormatting({
|
|
77
|
+
segments,
|
|
78
|
+
selection,
|
|
79
|
+
activeStyles,
|
|
80
|
+
onSegmentsChange: updateSegments,
|
|
81
|
+
onActiveStylesChange: setActiveStyles,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ─── Text Change Handler ─────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
const handleTextChange = useCallback(
|
|
87
|
+
(newText: string) => {
|
|
88
|
+
const currentSegments = segmentsRef.current;
|
|
89
|
+
const currentActiveStyles = activeStylesRef.current;
|
|
90
|
+
|
|
91
|
+
const newSegments = reconcileTextChange(
|
|
92
|
+
currentSegments,
|
|
93
|
+
newText,
|
|
94
|
+
currentActiveStyles,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
updateSegments(newSegments);
|
|
98
|
+
},
|
|
99
|
+
[updateSegments],
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// ─── Selection Change Handler ────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
const onSelectionChange = useCallback(
|
|
105
|
+
(newSelection: SelectionRange) => {
|
|
106
|
+
handleSelectionChange(newSelection);
|
|
107
|
+
|
|
108
|
+
// Update active styles based on cursor position
|
|
109
|
+
if (newSelection.start === newSelection.end) {
|
|
110
|
+
const pos = findPositionInSegments(
|
|
111
|
+
segmentsRef.current,
|
|
112
|
+
newSelection.start,
|
|
113
|
+
);
|
|
114
|
+
if (segmentsRef.current.length > 0) {
|
|
115
|
+
const seg = segmentsRef.current[pos.segmentIndex];
|
|
116
|
+
setActiveStyles({ ...seg.styles });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[handleSelectionChange],
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// ─── Export / Import ─────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
const getPlainText = useCallback((): string => {
|
|
126
|
+
return segmentsToPlainText(segmentsRef.current);
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
const exportJSON = useCallback((): StyledSegment[] => {
|
|
130
|
+
return JSON.parse(JSON.stringify(segmentsRef.current));
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
const importJSON = useCallback(
|
|
134
|
+
(newSegments: StyledSegment[]) => {
|
|
135
|
+
const safeSegments =
|
|
136
|
+
newSegments.length > 0 ? newSegments : [createSegment('')];
|
|
137
|
+
updateSegments(safeSegments);
|
|
138
|
+
},
|
|
139
|
+
[updateSegments],
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const clear = useCallback(() => {
|
|
143
|
+
updateSegments([createSegment('')]);
|
|
144
|
+
setActiveStyles({ ...EMPTY_FORMAT_STYLE });
|
|
145
|
+
}, [updateSegments]);
|
|
146
|
+
|
|
147
|
+
// ─── Build Return Value ──────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
const state: RichTextState = {
|
|
150
|
+
segments,
|
|
151
|
+
selection,
|
|
152
|
+
activeStyles,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const actions: RichTextActions = {
|
|
156
|
+
toggleFormat: formatting.toggleFormat,
|
|
157
|
+
setStyleProperty: formatting.setStyleProperty,
|
|
158
|
+
setHeading: formatting.setHeading,
|
|
159
|
+
setColor: formatting.setColor,
|
|
160
|
+
setBackgroundColor: formatting.setBackgroundColor,
|
|
161
|
+
setFontSize: formatting.setFontSize,
|
|
162
|
+
handleTextChange,
|
|
163
|
+
handleSelectionChange: onSelectionChange,
|
|
164
|
+
getPlainText,
|
|
165
|
+
exportJSON,
|
|
166
|
+
importJSON,
|
|
167
|
+
clear,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return { state, actions };
|
|
171
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SelectionRange } from '@/types';
|
|
2
|
+
/**
|
|
3
|
+
* Hook for tracking TextInput selection state.
|
|
4
|
+
*
|
|
5
|
+
* Returns the current selection and a handler to update it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useSelection(initialSelection?: SelectionRange): {
|
|
8
|
+
selection: SelectionRange;
|
|
9
|
+
setSelection: import("react").Dispatch<import("react").SetStateAction<SelectionRange>>;
|
|
10
|
+
handleSelectionChange: (newSelection: SelectionRange) => void;
|
|
11
|
+
getSelection: () => SelectionRange;
|
|
12
|
+
hasSelection: () => boolean;
|
|
13
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useState, useCallback, useRef } from 'react';
|
|
2
|
+
import type { SelectionRange } from '@/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook for tracking TextInput selection state.
|
|
6
|
+
*
|
|
7
|
+
* Returns the current selection and a handler to update it.
|
|
8
|
+
*/
|
|
9
|
+
export function useSelection(initialSelection?: SelectionRange) {
|
|
10
|
+
const [selection, setSelection] = useState<SelectionRange>(
|
|
11
|
+
initialSelection ?? { start: 0, end: 0 },
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
// Use a ref to avoid stale closures in callbacks
|
|
15
|
+
const selectionRef = useRef(selection);
|
|
16
|
+
selectionRef.current = selection;
|
|
17
|
+
|
|
18
|
+
const handleSelectionChange = useCallback(
|
|
19
|
+
(newSelection: SelectionRange) => {
|
|
20
|
+
setSelection(newSelection);
|
|
21
|
+
},
|
|
22
|
+
[],
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const getSelection = useCallback((): SelectionRange => {
|
|
26
|
+
return selectionRef.current;
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const hasSelection = useCallback((): boolean => {
|
|
30
|
+
return selectionRef.current.start !== selectionRef.current.end;
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
selection,
|
|
35
|
+
setSelection,
|
|
36
|
+
handleSelectionChange,
|
|
37
|
+
getSelection,
|
|
38
|
+
hasSelection,
|
|
39
|
+
};
|
|
40
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { RichTextInput } from '@/components/RichTextInput';
|
|
2
|
+
export { OverlayText } from '@/components/OverlayText';
|
|
3
|
+
export { Toolbar } from '@/components/Toolbar';
|
|
4
|
+
export { ToolbarButton } from '@/components/ToolbarButton';
|
|
5
|
+
export { useRichText } from '@/hooks/useRichText';
|
|
6
|
+
export type { UseRichTextOptions } from '@/hooks/useRichText';
|
|
7
|
+
export { useSelection } from '@/hooks/useSelection';
|
|
8
|
+
export { useFormatting } from '@/hooks/useFormatting';
|
|
9
|
+
export { RichTextProvider, useRichTextContext, } from '@/context/RichTextContext';
|
|
10
|
+
export type { RichTextProviderProps } from '@/context/RichTextContext';
|
|
11
|
+
export { createSegment, segmentsToPlainText, getTotalLength, mergeAdjacentSegments, reconcileTextChange, } from '@/utils/parser';
|
|
12
|
+
export { toggleFormatOnSelection, setStyleOnSelection, setHeadingOnLine, isFormatActiveInSelection, getSelectionStyle, } from '@/utils/formatter';
|
|
13
|
+
export { formatStyleToTextStyle, segmentToTextStyle, segmentsToTextStyles, } from '@/utils/styleMapper';
|
|
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';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// ─── Components ──────────────────────────────────────────────────────────────
|
|
2
|
+
export { RichTextInput } from '@/components/RichTextInput';
|
|
3
|
+
export { OverlayText } from '@/components/OverlayText';
|
|
4
|
+
export { Toolbar } from '@/components/Toolbar';
|
|
5
|
+
export { ToolbarButton } from '@/components/ToolbarButton';
|
|
6
|
+
|
|
7
|
+
// ─── Hooks ───────────────────────────────────────────────────────────────────
|
|
8
|
+
export { useRichText } from '@/hooks/useRichText';
|
|
9
|
+
export type { UseRichTextOptions } from '@/hooks/useRichText';
|
|
10
|
+
export { useSelection } from '@/hooks/useSelection';
|
|
11
|
+
export { useFormatting } from '@/hooks/useFormatting';
|
|
12
|
+
|
|
13
|
+
// ─── Context ─────────────────────────────────────────────────────────────────
|
|
14
|
+
export {
|
|
15
|
+
RichTextProvider,
|
|
16
|
+
useRichTextContext,
|
|
17
|
+
} from '@/context/RichTextContext';
|
|
18
|
+
export type { RichTextProviderProps } from '@/context/RichTextContext';
|
|
19
|
+
|
|
20
|
+
// ─── Utilities ───────────────────────────────────────────────────────────────
|
|
21
|
+
export {
|
|
22
|
+
createSegment,
|
|
23
|
+
segmentsToPlainText,
|
|
24
|
+
getTotalLength,
|
|
25
|
+
mergeAdjacentSegments,
|
|
26
|
+
reconcileTextChange,
|
|
27
|
+
} from '@/utils/parser';
|
|
28
|
+
export {
|
|
29
|
+
toggleFormatOnSelection,
|
|
30
|
+
setStyleOnSelection,
|
|
31
|
+
setHeadingOnLine,
|
|
32
|
+
isFormatActiveInSelection,
|
|
33
|
+
getSelectionStyle,
|
|
34
|
+
} from '@/utils/formatter';
|
|
35
|
+
export {
|
|
36
|
+
formatStyleToTextStyle,
|
|
37
|
+
segmentToTextStyle,
|
|
38
|
+
segmentsToTextStyles,
|
|
39
|
+
} from '@/utils/styleMapper';
|
|
40
|
+
|
|
41
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
42
|
+
export {
|
|
43
|
+
DEFAULT_COLORS,
|
|
44
|
+
DEFAULT_THEME,
|
|
45
|
+
DEFAULT_TOOLBAR_ITEMS,
|
|
46
|
+
DEFAULT_BASE_TEXT_STYLE,
|
|
47
|
+
HEADING_FONT_SIZES,
|
|
48
|
+
EMPTY_FORMAT_STYLE,
|
|
49
|
+
} from '@/constants/defaultStyles';
|
|
50
|
+
|
|
51
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
52
|
+
export type {
|
|
53
|
+
FormatType,
|
|
54
|
+
HeadingLevel,
|
|
55
|
+
ListType,
|
|
56
|
+
FormatStyle,
|
|
57
|
+
StyledSegment,
|
|
58
|
+
SelectionRange,
|
|
59
|
+
RichTextState,
|
|
60
|
+
RichTextActions,
|
|
61
|
+
UseRichTextReturn,
|
|
62
|
+
RichTextTheme,
|
|
63
|
+
ToolbarItem,
|
|
64
|
+
OverlayTextProps,
|
|
65
|
+
ToolbarButtonProps,
|
|
66
|
+
ToolbarProps,
|
|
67
|
+
RichTextInputProps,
|
|
68
|
+
} from '@/types';
|