@copilotkit/react-textarea 0.5.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/.turbo/turbo-build.log +133 -0
- package/CHANGELOG.md +50 -0
- package/dist/chunk-4LJAS777.mjs +10 -0
- package/dist/chunk-4LJAS777.mjs.map +1 -0
- package/dist/chunk-5SJM4R4K.mjs +10 -0
- package/dist/chunk-5SJM4R4K.mjs.map +1 -0
- package/dist/chunk-ETGKLURC.mjs +27 -0
- package/dist/chunk-ETGKLURC.mjs.map +1 -0
- package/dist/chunk-GOAPCQCR.mjs +124 -0
- package/dist/chunk-GOAPCQCR.mjs.map +1 -0
- package/dist/chunk-H4VKQGVU.mjs +3 -0
- package/dist/chunk-H4VKQGVU.mjs.map +1 -0
- package/dist/chunk-IU3WTXLQ.mjs +3 -0
- package/dist/chunk-IU3WTXLQ.mjs.map +1 -0
- package/dist/chunk-JD7BAH7U.mjs +3 -0
- package/dist/chunk-JD7BAH7U.mjs.map +1 -0
- package/dist/chunk-JJASB23A.mjs +88 -0
- package/dist/chunk-JJASB23A.mjs.map +1 -0
- package/dist/chunk-KCHYD3EB.mjs +107 -0
- package/dist/chunk-KCHYD3EB.mjs.map +1 -0
- package/dist/chunk-KGKLUWKW.mjs +47 -0
- package/dist/chunk-KGKLUWKW.mjs.map +1 -0
- package/dist/chunk-KKG2RK2T.mjs +16 -0
- package/dist/chunk-KKG2RK2T.mjs.map +1 -0
- package/dist/chunk-L24ZN4LL.mjs +65 -0
- package/dist/chunk-L24ZN4LL.mjs.map +1 -0
- package/dist/chunk-MRXNTQOX.mjs +55 -0
- package/dist/chunk-MRXNTQOX.mjs.map +1 -0
- package/dist/chunk-NKW5OU2S.mjs +33 -0
- package/dist/chunk-NKW5OU2S.mjs.map +1 -0
- package/dist/chunk-RVJAOO4S.mjs +18 -0
- package/dist/chunk-RVJAOO4S.mjs.map +1 -0
- package/dist/chunk-TSF4AJIK.mjs +30 -0
- package/dist/chunk-TSF4AJIK.mjs.map +1 -0
- package/dist/chunk-X4FJ6WVZ.mjs +66 -0
- package/dist/chunk-X4FJ6WVZ.mjs.map +1 -0
- package/dist/chunk-Y5BUWZOI.mjs +37 -0
- package/dist/chunk-Y5BUWZOI.mjs.map +1 -0
- package/dist/chunk-YPBKY4KY.mjs +3 -0
- package/dist/chunk-YPBKY4KY.mjs.map +1 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.d.ts +18 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.mjs +17 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.mjs.map +1 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-element.d.ts +6 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-element.mjs +4 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-element.mjs.map +1 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-placeholder.d.ts +6 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-placeholder.mjs +4 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-placeholder.mjs.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.mjs +18 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.mjs +3 -0
- package/dist/context/index.mjs.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/hooks/use-autosuggestions.d.ts +13 -0
- package/dist/hooks/use-autosuggestions.mjs +7 -0
- package/dist/hooks/use-autosuggestions.mjs.map +1 -0
- package/dist/hooks/use-copilot-textarea-editor.d.ts +8 -0
- package/dist/hooks/use-copilot-textarea-editor.mjs +5 -0
- package/dist/hooks/use-copilot-textarea-editor.mjs.map +1 -0
- package/dist/index.css +340 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +21 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/debouncer.d.ts +11 -0
- package/dist/lib/debouncer.mjs +4 -0
- package/dist/lib/debouncer.mjs.map +1 -0
- package/dist/lib/editor-to-text.d.ts +7 -0
- package/dist/lib/editor-to-text.mjs +45 -0
- package/dist/lib/editor-to-text.mjs.map +1 -0
- package/dist/lib/get-text-around-cursor.d.ts +8 -0
- package/dist/lib/get-text-around-cursor.mjs +4 -0
- package/dist/lib/get-text-around-cursor.mjs.map +1 -0
- package/dist/lib/slatejs-edits/add-autocompletions.d.ts +8 -0
- package/dist/lib/slatejs-edits/add-autocompletions.mjs +4 -0
- package/dist/lib/slatejs-edits/add-autocompletions.mjs.map +1 -0
- package/dist/lib/slatejs-edits/clear-autocompletions.d.ts +8 -0
- package/dist/lib/slatejs-edits/clear-autocompletions.mjs +4 -0
- package/dist/lib/slatejs-edits/clear-autocompletions.mjs.map +1 -0
- package/dist/lib/slatejs-edits/replace-text.d.ts +5 -0
- package/dist/lib/slatejs-edits/replace-text.mjs +4 -0
- package/dist/lib/slatejs-edits/replace-text.mjs.map +1 -0
- package/dist/lib/slatejs-edits/with-partial-history.d.ts +10 -0
- package/dist/lib/slatejs-edits/with-partial-history.mjs +4 -0
- package/dist/lib/slatejs-edits/with-partial-history.mjs.map +1 -0
- package/dist/lib/utils.d.ts +10 -0
- package/dist/lib/utils.mjs +4 -0
- package/dist/lib/utils.mjs.map +1 -0
- package/dist/types/autosuggestion-state.d.ts +8 -0
- package/dist/types/autosuggestion-state.mjs +3 -0
- package/dist/types/autosuggestion-state.mjs.map +1 -0
- package/dist/types/autosuggestions-bare-function.d.ts +3 -0
- package/dist/types/autosuggestions-bare-function.mjs +3 -0
- package/dist/types/autosuggestions-bare-function.mjs.map +1 -0
- package/dist/types/base-autosuggestions-config.d.ts +9 -0
- package/dist/types/base-autosuggestions-config.mjs +4 -0
- package/dist/types/base-autosuggestions-config.mjs.map +1 -0
- package/dist/types/custom-editor.d.ts +29 -0
- package/dist/types/custom-editor.mjs +3 -0
- package/dist/types/custom-editor.mjs.map +1 -0
- package/dist/types/editor-autocomplete-state.d.ts +10 -0
- package/dist/types/editor-autocomplete-state.mjs +5 -0
- package/dist/types/editor-autocomplete-state.mjs.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.mjs +5 -0
- package/dist/types/index.mjs.map +1 -0
- package/package.json +53 -0
- package/postcss.config.js +9 -0
- package/src/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.tsx +175 -0
- package/src/components/copilot-textarea/base-copilot-textarea/render-element.tsx +42 -0
- package/src/components/copilot-textarea/base-copilot-textarea/render-placeholder.tsx +25 -0
- package/src/components/index.ts +2 -0
- package/src/context/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-autosuggestions.ts +129 -0
- package/src/hooks/use-copilot-textarea-editor.tsx +104 -0
- package/src/index.tsx +7 -0
- package/src/lib/debouncer.ts +38 -0
- package/src/lib/editor-to-text.ts +64 -0
- package/src/lib/get-text-around-cursor.ts +82 -0
- package/src/lib/slatejs-edits/add-autocompletions.ts +30 -0
- package/src/lib/slatejs-edits/clear-autocompletions.ts +15 -0
- package/src/lib/slatejs-edits/replace-text.ts +32 -0
- package/src/lib/slatejs-edits/with-partial-history.ts +156 -0
- package/src/lib/utils.ts +59 -0
- package/src/styles.css +3 -0
- package/src/types/autosuggestion-state.ts +6 -0
- package/src/types/autosuggestions-bare-function.ts +5 -0
- package/src/types/base-autosuggestions-config.tsx +15 -0
- package/src/types/custom-editor.tsx +29 -0
- package/src/types/editor-autocomplete-state.ts +19 -0
- package/src/types/index.ts +4 -0
- package/tailwind.config.js +7 -0
- package/tsconfig.json +5 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BaseEditor } from 'slate';
|
|
2
|
+
import { ReactEditor } from 'slate-react';
|
|
3
|
+
import { HistoryEditor } from 'slate-history';
|
|
4
|
+
|
|
5
|
+
type CustomEditor = BaseEditor & ReactEditor & HistoryEditor;
|
|
6
|
+
type ParagraphElement = {
|
|
7
|
+
type: "paragraph";
|
|
8
|
+
children: CustomText[];
|
|
9
|
+
};
|
|
10
|
+
type SuggestionElement = {
|
|
11
|
+
type: "suggestion";
|
|
12
|
+
inline: boolean;
|
|
13
|
+
content: string;
|
|
14
|
+
children: CustomText[];
|
|
15
|
+
};
|
|
16
|
+
type CustomElement = ParagraphElement | SuggestionElement;
|
|
17
|
+
type SuggestionAwareText = {
|
|
18
|
+
text: string;
|
|
19
|
+
};
|
|
20
|
+
type CustomText = SuggestionAwareText;
|
|
21
|
+
declare module "slate" {
|
|
22
|
+
interface CustomTypes {
|
|
23
|
+
Editor: CustomEditor;
|
|
24
|
+
Element: CustomElement;
|
|
25
|
+
Text: CustomText;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { CustomEditor, CustomElement, CustomText, ParagraphElement, SuggestionAwareText, SuggestionElement };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BasePoint } from 'slate';
|
|
2
|
+
|
|
3
|
+
interface EditorAutocompleteState {
|
|
4
|
+
cursorPoint: BasePoint;
|
|
5
|
+
textBeforeCursor: string;
|
|
6
|
+
textAfterCursor: string;
|
|
7
|
+
}
|
|
8
|
+
declare function areEqual_autocompleteState(prev: EditorAutocompleteState, next: EditorAutocompleteState): boolean;
|
|
9
|
+
|
|
10
|
+
export { EditorAutocompleteState, areEqual_autocompleteState };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@copilotkit/react-textarea",
|
|
3
|
+
"private": false,
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.5.0",
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"**/*.css"
|
|
10
|
+
],
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"main": "./dist/index.mjs",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./dist/index.mjs",
|
|
15
|
+
"./styles.css": "./dist/index.css"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"react": "^18.2.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/react": "^18.2.5",
|
|
23
|
+
"@types/react-syntax-highlighter": "^15.5.7",
|
|
24
|
+
"eslint": "^7.32.0",
|
|
25
|
+
"postcss": "^8.4.20",
|
|
26
|
+
"react": "^18.2.0",
|
|
27
|
+
"tailwindcss": "^3.3.0",
|
|
28
|
+
"tsup": "^6.1.3",
|
|
29
|
+
"typescript": "^4.9.4",
|
|
30
|
+
"eslint-config-custom": "0.2.0",
|
|
31
|
+
"tailwind-config": "0.1.0",
|
|
32
|
+
"tsconfig": "0.3.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"ai": "^2.1.22",
|
|
36
|
+
"class-variance-authority": "^0.6.1",
|
|
37
|
+
"clsx": "^1.2.1",
|
|
38
|
+
"nanoid": "^4.0.2",
|
|
39
|
+
"next": "^13.4.1",
|
|
40
|
+
"next-themes": "^0.2.1",
|
|
41
|
+
"react-dom": "^18.2.0",
|
|
42
|
+
"slate": "^0.94.1",
|
|
43
|
+
"slate-history": "^0.93.0",
|
|
44
|
+
"slate-react": "^0.98.1",
|
|
45
|
+
"tailwind-merge": "^1.13.2"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup --treeshake",
|
|
49
|
+
"dev": "tsup --watch --no-splitting",
|
|
50
|
+
"check-types": "tsc --noEmit",
|
|
51
|
+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist && rm -rf .next"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// This example is for an Editor with `ReactEditor` and `HistoryEditor`
|
|
2
|
+
import {
|
|
3
|
+
TextareaHTMLAttributes,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { Descendant, Editor } from "slate";
|
|
10
|
+
import { Editable, Slate } from "slate-react";
|
|
11
|
+
import { twMerge } from "tailwind-merge";
|
|
12
|
+
import { useAutosuggestions } from "../../../hooks/use-autosuggestions";
|
|
13
|
+
import { AutosuggestionsBareFunction } from "../../../types/autosuggestions-bare-function";
|
|
14
|
+
import { useCopilotTextareaEditor } from "../../../hooks/use-copilot-textarea-editor";
|
|
15
|
+
import {
|
|
16
|
+
getFullEditorTextWithNewlines,
|
|
17
|
+
getTextAroundCursor,
|
|
18
|
+
} from "../../../lib/get-text-around-cursor";
|
|
19
|
+
import { addAutocompletionsToEditor } from "../../../lib/slatejs-edits/add-autocompletions";
|
|
20
|
+
import { clearAutocompletionsFromEditor } from "../../../lib/slatejs-edits/clear-autocompletions";
|
|
21
|
+
import { replaceEditorText } from "../../../lib/slatejs-edits/replace-text";
|
|
22
|
+
import { AutosuggestionState } from "../../../types/autosuggestion-state";
|
|
23
|
+
import {
|
|
24
|
+
BaseAutosuggestionsConfig,
|
|
25
|
+
defaultBaseAutosuggestionsConfig,
|
|
26
|
+
} from "../../../types/base-autosuggestions-config";
|
|
27
|
+
import { makeRenderElementFunction } from "./render-element";
|
|
28
|
+
import { makeRenderPlaceholderFunction } from "./render-placeholder";
|
|
29
|
+
|
|
30
|
+
export interface BaseCopilotTextareaProps
|
|
31
|
+
extends TextareaHTMLAttributes<HTMLDivElement> {
|
|
32
|
+
placeholderStyle?: React.CSSProperties;
|
|
33
|
+
suggestionsStyle?: React.CSSProperties;
|
|
34
|
+
value?: string;
|
|
35
|
+
onValueChange?: (value: string) => void;
|
|
36
|
+
autosuggestionsConfig: Partial<BaseAutosuggestionsConfig> & {
|
|
37
|
+
purposePrompt: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function BaseCopilotTextarea(
|
|
42
|
+
props: BaseCopilotTextareaProps & {
|
|
43
|
+
autosuggestionsFunction: AutosuggestionsBareFunction;
|
|
44
|
+
}
|
|
45
|
+
): JSX.Element {
|
|
46
|
+
const autosuggestionsConfig: BaseAutosuggestionsConfig = {
|
|
47
|
+
...defaultBaseAutosuggestionsConfig,
|
|
48
|
+
...props.autosuggestionsConfig,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const valueOnInitialRender = useMemo(() => props.value ?? "", []);
|
|
52
|
+
const [lastKnownFullEditorText, setLastKnownFullEditorText] =
|
|
53
|
+
useState(valueOnInitialRender);
|
|
54
|
+
|
|
55
|
+
const initialValue: Descendant[] = useMemo(() => {
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
type: "paragraph",
|
|
59
|
+
children: [{ text: valueOnInitialRender }],
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
}, [valueOnInitialRender]);
|
|
63
|
+
|
|
64
|
+
const editor = useCopilotTextareaEditor();
|
|
65
|
+
|
|
66
|
+
const insertText = useCallback(
|
|
67
|
+
(autosuggestion: AutosuggestionState) => {
|
|
68
|
+
Editor.insertText(editor, autosuggestion.text, {
|
|
69
|
+
at: autosuggestion.point,
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
[editor]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const {
|
|
76
|
+
currentAutocompleteSuggestion,
|
|
77
|
+
onChangeHandler: onChangeHandlerForAutocomplete,
|
|
78
|
+
onKeyDownHandler: onKeyDownHandlerForAutocomplete,
|
|
79
|
+
} = useAutosuggestions(
|
|
80
|
+
autosuggestionsConfig.debounceTime,
|
|
81
|
+
autosuggestionsConfig.acceptAutosuggestionKey,
|
|
82
|
+
props.autosuggestionsFunction,
|
|
83
|
+
insertText,
|
|
84
|
+
autosuggestionsConfig.disableWhenEmpty
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// sync autosuggestions state with the editor
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
clearAutocompletionsFromEditor(editor);
|
|
90
|
+
if (currentAutocompleteSuggestion) {
|
|
91
|
+
addAutocompletionsToEditor(
|
|
92
|
+
editor,
|
|
93
|
+
currentAutocompleteSuggestion.text,
|
|
94
|
+
currentAutocompleteSuggestion.point
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}, [currentAutocompleteSuggestion]);
|
|
98
|
+
|
|
99
|
+
const renderElementMemoized = useMemo(() => {
|
|
100
|
+
const suggestionStyleAugmented: React.CSSProperties = {
|
|
101
|
+
...props.suggestionsStyle,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return makeRenderElementFunction(suggestionStyleAugmented);
|
|
105
|
+
}, [props.suggestionsStyle]);
|
|
106
|
+
|
|
107
|
+
const renderPlaceholderMemoized = useMemo(() => {
|
|
108
|
+
// For some reason slateJS specifies a top value of 0, which makes for strange styling. We override this here.
|
|
109
|
+
const placeholderStyleSlatejsOverrides: React.CSSProperties = {
|
|
110
|
+
top: undefined,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const placeholderStyleAugmented: React.CSSProperties = {
|
|
114
|
+
...placeholderStyleSlatejsOverrides,
|
|
115
|
+
...props.placeholderStyle,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return makeRenderPlaceholderFunction(placeholderStyleAugmented);
|
|
119
|
+
}, [props.placeholderStyle]);
|
|
120
|
+
|
|
121
|
+
// update the editor text, but only when the value changes from outside the component
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (props.value === lastKnownFullEditorText) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setLastKnownFullEditorText(props.value ?? "");
|
|
128
|
+
replaceEditorText(editor, props.value ?? "");
|
|
129
|
+
}, [props.value]);
|
|
130
|
+
|
|
131
|
+
// separate into TextareaHTMLAttributes<HTMLDivElement> and CopilotTextareaProps
|
|
132
|
+
const {
|
|
133
|
+
placeholderStyle,
|
|
134
|
+
value,
|
|
135
|
+
onValueChange,
|
|
136
|
+
autosuggestionsConfig: autosuggestionsConfigFromProps,
|
|
137
|
+
autosuggestionsFunction,
|
|
138
|
+
className,
|
|
139
|
+
...propsToForward
|
|
140
|
+
} = props;
|
|
141
|
+
|
|
142
|
+
const moddedClassName = (() => {
|
|
143
|
+
const baseClassName = "copilot-textarea";
|
|
144
|
+
const defaultTailwindClassName = "bg-white overflow-y-auto resize-y";
|
|
145
|
+
const mergedClassName = twMerge(defaultTailwindClassName, className ?? "");
|
|
146
|
+
return `${baseClassName} ${mergedClassName}`;
|
|
147
|
+
})();
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
// Add the editable component inside the context.
|
|
151
|
+
<Slate
|
|
152
|
+
editor={editor}
|
|
153
|
+
initialValue={initialValue}
|
|
154
|
+
onChange={(value) => {
|
|
155
|
+
const newEditorState = getTextAroundCursor(editor);
|
|
156
|
+
|
|
157
|
+
const fullEditorText = newEditorState
|
|
158
|
+
? newEditorState.textBeforeCursor + newEditorState.textAfterCursor
|
|
159
|
+
: getFullEditorTextWithNewlines(editor); // we don't double-parse the editor. When `newEditorState` is null, we didn't parse the editor yet.
|
|
160
|
+
|
|
161
|
+
setLastKnownFullEditorText(fullEditorText);
|
|
162
|
+
onChangeHandlerForAutocomplete(newEditorState);
|
|
163
|
+
props.onValueChange?.(fullEditorText);
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
<Editable
|
|
167
|
+
renderElement={renderElementMemoized}
|
|
168
|
+
renderPlaceholder={renderPlaceholderMemoized}
|
|
169
|
+
onKeyDown={onKeyDownHandlerForAutocomplete}
|
|
170
|
+
className={moddedClassName}
|
|
171
|
+
{...propsToForward}
|
|
172
|
+
/>
|
|
173
|
+
</Slate>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { RenderElementProps } from "slate-react";
|
|
2
|
+
|
|
3
|
+
export type RenderElementFunction = (props: RenderElementProps) => JSX.Element;
|
|
4
|
+
|
|
5
|
+
export function makeRenderElementFunction(
|
|
6
|
+
suggestionsStyle: React.CSSProperties
|
|
7
|
+
): RenderElementFunction {
|
|
8
|
+
return (props: RenderElementProps) => {
|
|
9
|
+
switch (props.element.type) {
|
|
10
|
+
case "paragraph":
|
|
11
|
+
return <DefaultElement {...props} />;
|
|
12
|
+
case "suggestion":
|
|
13
|
+
return (
|
|
14
|
+
<SuggestionElement {...props} suggestionsStyle={suggestionsStyle} />
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DefaultElement = (props: RenderElementProps) => {
|
|
21
|
+
return <div {...props.attributes}>{props.children}</div>;
|
|
22
|
+
};
|
|
23
|
+
const SuggestionElement = (
|
|
24
|
+
props: RenderElementProps & {
|
|
25
|
+
suggestionsStyle: React.CSSProperties;
|
|
26
|
+
}
|
|
27
|
+
) => {
|
|
28
|
+
return (
|
|
29
|
+
<span
|
|
30
|
+
{...props.attributes}
|
|
31
|
+
style={{
|
|
32
|
+
fontStyle: "italic",
|
|
33
|
+
color: "gray",
|
|
34
|
+
...props.suggestionsStyle,
|
|
35
|
+
}}
|
|
36
|
+
contentEditable={false}
|
|
37
|
+
>
|
|
38
|
+
{props.children /* https://github.com/ianstormtaylor/slate/issues/3930 */}
|
|
39
|
+
{props.element.type === "suggestion" && props.element.content}
|
|
40
|
+
</span>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { RenderElementProps, RenderPlaceholderProps } from "slate-react";
|
|
2
|
+
|
|
3
|
+
export type RenderPlaceholderFunction = (
|
|
4
|
+
props: RenderPlaceholderProps
|
|
5
|
+
) => JSX.Element;
|
|
6
|
+
|
|
7
|
+
export function makeRenderPlaceholderFunction(
|
|
8
|
+
placeholderStyle?: React.CSSProperties
|
|
9
|
+
): RenderPlaceholderFunction {
|
|
10
|
+
return (props: RenderPlaceholderProps) => {
|
|
11
|
+
const { style, ...restAttributes } = props.attributes;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
{...restAttributes}
|
|
16
|
+
style={{
|
|
17
|
+
...style,
|
|
18
|
+
...placeholderStyle,
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
{props.children}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { Debouncer } from "../lib/debouncer";
|
|
3
|
+
import { nullableCompatibleEqualityCheck } from "../lib/utils";
|
|
4
|
+
import { AutosuggestionState } from "../types/autosuggestion-state";
|
|
5
|
+
import {
|
|
6
|
+
EditorAutocompleteState,
|
|
7
|
+
areEqual_autocompleteState,
|
|
8
|
+
} from "../types/editor-autocomplete-state";
|
|
9
|
+
import { AutosuggestionsBareFunction } from "../types/autosuggestions-bare-function";
|
|
10
|
+
|
|
11
|
+
export interface UseAutosuggestionsResult {
|
|
12
|
+
currentAutocompleteSuggestion: AutosuggestionState | null;
|
|
13
|
+
onChangeHandler: (newEditorState: EditorAutocompleteState | null) => void;
|
|
14
|
+
onKeyDownHandler: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useAutosuggestions(
|
|
18
|
+
debounceTime: number,
|
|
19
|
+
acceptAutosuggestionKey: string,
|
|
20
|
+
autosuggestionFunction: AutosuggestionsBareFunction,
|
|
21
|
+
insertAutocompleteSuggestion: (suggestion: AutosuggestionState) => void,
|
|
22
|
+
disableWhenEmpty: boolean
|
|
23
|
+
): UseAutosuggestionsResult {
|
|
24
|
+
const [previousAutocompleteState, setPreviousAutocompleteState] =
|
|
25
|
+
useState<EditorAutocompleteState | null>(null);
|
|
26
|
+
|
|
27
|
+
const [currentAutocompleteSuggestion, setCurrentAutocompleteSuggestion] =
|
|
28
|
+
useState<AutosuggestionState | null>(null);
|
|
29
|
+
|
|
30
|
+
const awaitForAndAppendSuggestion: (
|
|
31
|
+
editorAutocompleteState: EditorAutocompleteState,
|
|
32
|
+
abortSignal: AbortSignal
|
|
33
|
+
) => Promise<void> = useCallback(
|
|
34
|
+
async (
|
|
35
|
+
editorAutocompleteState: EditorAutocompleteState,
|
|
36
|
+
abortSignal: AbortSignal
|
|
37
|
+
) => {
|
|
38
|
+
if (
|
|
39
|
+
disableWhenEmpty &&
|
|
40
|
+
editorAutocompleteState.textBeforeCursor === "" &&
|
|
41
|
+
editorAutocompleteState.textAfterCursor === ""
|
|
42
|
+
) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const suggestion = await autosuggestionFunction(
|
|
47
|
+
editorAutocompleteState.textBeforeCursor,
|
|
48
|
+
editorAutocompleteState.textAfterCursor,
|
|
49
|
+
abortSignal
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// We'll assume for now that the autocomplete function might or might not respect the abort signal.
|
|
53
|
+
if (!suggestion || abortSignal.aborted) {
|
|
54
|
+
throw new DOMException("Aborted", "AbortError");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
setCurrentAutocompleteSuggestion({
|
|
58
|
+
text: suggestion,
|
|
59
|
+
point: editorAutocompleteState.cursorPoint,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
[autosuggestionFunction, setCurrentAutocompleteSuggestion, disableWhenEmpty]
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const debouncedFunction = useMemo(
|
|
66
|
+
() =>
|
|
67
|
+
new Debouncer<[editorAutocompleteState: EditorAutocompleteState]>(
|
|
68
|
+
debounceTime
|
|
69
|
+
),
|
|
70
|
+
[debounceTime]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const onChange = useCallback(
|
|
74
|
+
(newEditorState: EditorAutocompleteState | null) => {
|
|
75
|
+
const editorStateHasChanged = !nullableCompatibleEqualityCheck(
|
|
76
|
+
areEqual_autocompleteState,
|
|
77
|
+
previousAutocompleteState,
|
|
78
|
+
newEditorState
|
|
79
|
+
);
|
|
80
|
+
setPreviousAutocompleteState(newEditorState);
|
|
81
|
+
|
|
82
|
+
// if no change, do nothing
|
|
83
|
+
if (!editorStateHasChanged) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// if change, then first null out the current suggestion
|
|
88
|
+
setCurrentAutocompleteSuggestion(null);
|
|
89
|
+
|
|
90
|
+
// then try to get a new suggestion, debouncing to avoid too many requests while typing
|
|
91
|
+
if (newEditorState) {
|
|
92
|
+
debouncedFunction.debounce(awaitForAndAppendSuggestion, newEditorState);
|
|
93
|
+
} else {
|
|
94
|
+
debouncedFunction.cancel();
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
[
|
|
98
|
+
previousAutocompleteState,
|
|
99
|
+
setPreviousAutocompleteState,
|
|
100
|
+
debouncedFunction,
|
|
101
|
+
awaitForAndAppendSuggestion,
|
|
102
|
+
setCurrentAutocompleteSuggestion,
|
|
103
|
+
]
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const keyDownHandler = useCallback(
|
|
107
|
+
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
108
|
+
if (currentAutocompleteSuggestion) {
|
|
109
|
+
if (event.key === acceptAutosuggestionKey) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
insertAutocompleteSuggestion(currentAutocompleteSuggestion);
|
|
112
|
+
setCurrentAutocompleteSuggestion(null);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
[
|
|
117
|
+
currentAutocompleteSuggestion,
|
|
118
|
+
setCurrentAutocompleteSuggestion,
|
|
119
|
+
insertAutocompleteSuggestion,
|
|
120
|
+
acceptAutosuggestionKey,
|
|
121
|
+
]
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
currentAutocompleteSuggestion,
|
|
126
|
+
onChangeHandler: onChange,
|
|
127
|
+
onKeyDownHandler: keyDownHandler,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createEditor, Element } from "slate";
|
|
2
|
+
import { withReact } from "slate-react";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { CustomEditor } from "../types/custom-editor";
|
|
5
|
+
import {
|
|
6
|
+
withPartialHistory,
|
|
7
|
+
ShouldSaveToHistory,
|
|
8
|
+
defaultShouldSave,
|
|
9
|
+
} from "../lib/slatejs-edits/with-partial-history";
|
|
10
|
+
|
|
11
|
+
const shouldSave: ShouldSaveToHistory = (op, prev) => {
|
|
12
|
+
const excludedNodeType = "suggestion";
|
|
13
|
+
// Check if the operation involves the suggestion inline node type
|
|
14
|
+
if (
|
|
15
|
+
op.type === "insert_node" &&
|
|
16
|
+
Element.isElement(op.node) &&
|
|
17
|
+
op.node.type === excludedNodeType
|
|
18
|
+
) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (
|
|
23
|
+
op.type === "remove_node" &&
|
|
24
|
+
Element.isElement(op.node) &&
|
|
25
|
+
op.node.type === excludedNodeType
|
|
26
|
+
) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
op.type === "set_node" &&
|
|
32
|
+
"type" in op.newProperties &&
|
|
33
|
+
op.newProperties.type === excludedNodeType
|
|
34
|
+
) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (
|
|
39
|
+
op.type == "set_node" &&
|
|
40
|
+
"type" in op.properties &&
|
|
41
|
+
op.properties.type === excludedNodeType
|
|
42
|
+
) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (
|
|
47
|
+
op.type === "merge_node" &&
|
|
48
|
+
"type" in op.properties &&
|
|
49
|
+
op.properties.type === excludedNodeType
|
|
50
|
+
) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
op.type === "split_node" &&
|
|
56
|
+
"type" in op.properties &&
|
|
57
|
+
op.properties.type === excludedNodeType
|
|
58
|
+
) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Otherwise, save the operation to history
|
|
63
|
+
return defaultShouldSave(op, prev);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export function useCopilotTextareaEditor(): CustomEditor {
|
|
67
|
+
const editor = useMemo(() => {
|
|
68
|
+
const editor = withPartialHistory(withReact(createEditor()), shouldSave);
|
|
69
|
+
|
|
70
|
+
const { isVoid } = editor;
|
|
71
|
+
editor.isVoid = (element) => {
|
|
72
|
+
switch (element.type) {
|
|
73
|
+
case "suggestion":
|
|
74
|
+
return true;
|
|
75
|
+
default:
|
|
76
|
+
return isVoid(element);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const { markableVoid } = editor;
|
|
81
|
+
editor.markableVoid = (element) => {
|
|
82
|
+
switch (element.type) {
|
|
83
|
+
case "suggestion":
|
|
84
|
+
return true;
|
|
85
|
+
default:
|
|
86
|
+
return markableVoid(element);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const { isInline } = editor;
|
|
91
|
+
editor.isInline = (element) => {
|
|
92
|
+
switch (element.type) {
|
|
93
|
+
case "suggestion":
|
|
94
|
+
return element.inline;
|
|
95
|
+
default:
|
|
96
|
+
return isInline(element);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return editor;
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
return editor;
|
|
104
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type AsyncFunction<T extends any[]> = (
|
|
2
|
+
...args: [...T, AbortSignal]
|
|
3
|
+
) => Promise<void>;
|
|
4
|
+
|
|
5
|
+
export class Debouncer<T extends any[]> {
|
|
6
|
+
private timeoutId?: number;
|
|
7
|
+
private activeAbortController?: AbortController;
|
|
8
|
+
|
|
9
|
+
constructor(private wait: number) {}
|
|
10
|
+
|
|
11
|
+
debounce = async (func: AsyncFunction<T>, ...args: T) => {
|
|
12
|
+
// Abort the previous promise immediately
|
|
13
|
+
this.cancel();
|
|
14
|
+
|
|
15
|
+
this.timeoutId = setTimeout(async () => {
|
|
16
|
+
try {
|
|
17
|
+
this.activeAbortController = new AbortController();
|
|
18
|
+
|
|
19
|
+
// Pass the signal to the async function, assuming it supports it
|
|
20
|
+
await func(...args, this.activeAbortController.signal);
|
|
21
|
+
|
|
22
|
+
this.activeAbortController = undefined;
|
|
23
|
+
} catch (error) {}
|
|
24
|
+
}, this.wait);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
cancel = () => {
|
|
28
|
+
if (this.activeAbortController) {
|
|
29
|
+
this.activeAbortController.abort();
|
|
30
|
+
this.activeAbortController = undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (this.timeoutId !== undefined) {
|
|
34
|
+
clearTimeout(this.timeoutId);
|
|
35
|
+
this.timeoutId = undefined;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|