@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.
Files changed (140) hide show
  1. package/.turbo/turbo-build.log +133 -0
  2. package/CHANGELOG.md +50 -0
  3. package/dist/chunk-4LJAS777.mjs +10 -0
  4. package/dist/chunk-4LJAS777.mjs.map +1 -0
  5. package/dist/chunk-5SJM4R4K.mjs +10 -0
  6. package/dist/chunk-5SJM4R4K.mjs.map +1 -0
  7. package/dist/chunk-ETGKLURC.mjs +27 -0
  8. package/dist/chunk-ETGKLURC.mjs.map +1 -0
  9. package/dist/chunk-GOAPCQCR.mjs +124 -0
  10. package/dist/chunk-GOAPCQCR.mjs.map +1 -0
  11. package/dist/chunk-H4VKQGVU.mjs +3 -0
  12. package/dist/chunk-H4VKQGVU.mjs.map +1 -0
  13. package/dist/chunk-IU3WTXLQ.mjs +3 -0
  14. package/dist/chunk-IU3WTXLQ.mjs.map +1 -0
  15. package/dist/chunk-JD7BAH7U.mjs +3 -0
  16. package/dist/chunk-JD7BAH7U.mjs.map +1 -0
  17. package/dist/chunk-JJASB23A.mjs +88 -0
  18. package/dist/chunk-JJASB23A.mjs.map +1 -0
  19. package/dist/chunk-KCHYD3EB.mjs +107 -0
  20. package/dist/chunk-KCHYD3EB.mjs.map +1 -0
  21. package/dist/chunk-KGKLUWKW.mjs +47 -0
  22. package/dist/chunk-KGKLUWKW.mjs.map +1 -0
  23. package/dist/chunk-KKG2RK2T.mjs +16 -0
  24. package/dist/chunk-KKG2RK2T.mjs.map +1 -0
  25. package/dist/chunk-L24ZN4LL.mjs +65 -0
  26. package/dist/chunk-L24ZN4LL.mjs.map +1 -0
  27. package/dist/chunk-MRXNTQOX.mjs +55 -0
  28. package/dist/chunk-MRXNTQOX.mjs.map +1 -0
  29. package/dist/chunk-NKW5OU2S.mjs +33 -0
  30. package/dist/chunk-NKW5OU2S.mjs.map +1 -0
  31. package/dist/chunk-RVJAOO4S.mjs +18 -0
  32. package/dist/chunk-RVJAOO4S.mjs.map +1 -0
  33. package/dist/chunk-TSF4AJIK.mjs +30 -0
  34. package/dist/chunk-TSF4AJIK.mjs.map +1 -0
  35. package/dist/chunk-X4FJ6WVZ.mjs +66 -0
  36. package/dist/chunk-X4FJ6WVZ.mjs.map +1 -0
  37. package/dist/chunk-Y5BUWZOI.mjs +37 -0
  38. package/dist/chunk-Y5BUWZOI.mjs.map +1 -0
  39. package/dist/chunk-YPBKY4KY.mjs +3 -0
  40. package/dist/chunk-YPBKY4KY.mjs.map +1 -0
  41. package/dist/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.d.ts +18 -0
  42. package/dist/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.mjs +17 -0
  43. package/dist/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.mjs.map +1 -0
  44. package/dist/components/copilot-textarea/base-copilot-textarea/render-element.d.ts +6 -0
  45. package/dist/components/copilot-textarea/base-copilot-textarea/render-element.mjs +4 -0
  46. package/dist/components/copilot-textarea/base-copilot-textarea/render-element.mjs.map +1 -0
  47. package/dist/components/copilot-textarea/base-copilot-textarea/render-placeholder.d.ts +6 -0
  48. package/dist/components/copilot-textarea/base-copilot-textarea/render-placeholder.mjs +4 -0
  49. package/dist/components/copilot-textarea/base-copilot-textarea/render-placeholder.mjs.map +1 -0
  50. package/dist/components/index.d.ts +4 -0
  51. package/dist/components/index.mjs +18 -0
  52. package/dist/components/index.mjs.map +1 -0
  53. package/dist/context/index.d.ts +1 -0
  54. package/dist/context/index.mjs +3 -0
  55. package/dist/context/index.mjs.map +1 -0
  56. package/dist/hooks/index.d.ts +1 -0
  57. package/dist/hooks/index.mjs +3 -0
  58. package/dist/hooks/index.mjs.map +1 -0
  59. package/dist/hooks/use-autosuggestions.d.ts +13 -0
  60. package/dist/hooks/use-autosuggestions.mjs +7 -0
  61. package/dist/hooks/use-autosuggestions.mjs.map +1 -0
  62. package/dist/hooks/use-copilot-textarea-editor.d.ts +8 -0
  63. package/dist/hooks/use-copilot-textarea-editor.mjs +5 -0
  64. package/dist/hooks/use-copilot-textarea-editor.mjs.map +1 -0
  65. package/dist/index.css +340 -0
  66. package/dist/index.css.map +1 -0
  67. package/dist/index.d.ts +4 -0
  68. package/dist/index.mjs +21 -0
  69. package/dist/index.mjs.map +1 -0
  70. package/dist/lib/debouncer.d.ts +11 -0
  71. package/dist/lib/debouncer.mjs +4 -0
  72. package/dist/lib/debouncer.mjs.map +1 -0
  73. package/dist/lib/editor-to-text.d.ts +7 -0
  74. package/dist/lib/editor-to-text.mjs +45 -0
  75. package/dist/lib/editor-to-text.mjs.map +1 -0
  76. package/dist/lib/get-text-around-cursor.d.ts +8 -0
  77. package/dist/lib/get-text-around-cursor.mjs +4 -0
  78. package/dist/lib/get-text-around-cursor.mjs.map +1 -0
  79. package/dist/lib/slatejs-edits/add-autocompletions.d.ts +8 -0
  80. package/dist/lib/slatejs-edits/add-autocompletions.mjs +4 -0
  81. package/dist/lib/slatejs-edits/add-autocompletions.mjs.map +1 -0
  82. package/dist/lib/slatejs-edits/clear-autocompletions.d.ts +8 -0
  83. package/dist/lib/slatejs-edits/clear-autocompletions.mjs +4 -0
  84. package/dist/lib/slatejs-edits/clear-autocompletions.mjs.map +1 -0
  85. package/dist/lib/slatejs-edits/replace-text.d.ts +5 -0
  86. package/dist/lib/slatejs-edits/replace-text.mjs +4 -0
  87. package/dist/lib/slatejs-edits/replace-text.mjs.map +1 -0
  88. package/dist/lib/slatejs-edits/with-partial-history.d.ts +10 -0
  89. package/dist/lib/slatejs-edits/with-partial-history.mjs +4 -0
  90. package/dist/lib/slatejs-edits/with-partial-history.mjs.map +1 -0
  91. package/dist/lib/utils.d.ts +10 -0
  92. package/dist/lib/utils.mjs +4 -0
  93. package/dist/lib/utils.mjs.map +1 -0
  94. package/dist/types/autosuggestion-state.d.ts +8 -0
  95. package/dist/types/autosuggestion-state.mjs +3 -0
  96. package/dist/types/autosuggestion-state.mjs.map +1 -0
  97. package/dist/types/autosuggestions-bare-function.d.ts +3 -0
  98. package/dist/types/autosuggestions-bare-function.mjs +3 -0
  99. package/dist/types/autosuggestions-bare-function.mjs.map +1 -0
  100. package/dist/types/base-autosuggestions-config.d.ts +9 -0
  101. package/dist/types/base-autosuggestions-config.mjs +4 -0
  102. package/dist/types/base-autosuggestions-config.mjs.map +1 -0
  103. package/dist/types/custom-editor.d.ts +29 -0
  104. package/dist/types/custom-editor.mjs +3 -0
  105. package/dist/types/custom-editor.mjs.map +1 -0
  106. package/dist/types/editor-autocomplete-state.d.ts +10 -0
  107. package/dist/types/editor-autocomplete-state.mjs +5 -0
  108. package/dist/types/editor-autocomplete-state.mjs.map +1 -0
  109. package/dist/types/index.d.ts +2 -0
  110. package/dist/types/index.mjs +5 -0
  111. package/dist/types/index.mjs.map +1 -0
  112. package/package.json +53 -0
  113. package/postcss.config.js +9 -0
  114. package/src/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.tsx +175 -0
  115. package/src/components/copilot-textarea/base-copilot-textarea/render-element.tsx +42 -0
  116. package/src/components/copilot-textarea/base-copilot-textarea/render-placeholder.tsx +25 -0
  117. package/src/components/index.ts +2 -0
  118. package/src/context/index.ts +1 -0
  119. package/src/hooks/index.ts +1 -0
  120. package/src/hooks/use-autosuggestions.ts +129 -0
  121. package/src/hooks/use-copilot-textarea-editor.tsx +104 -0
  122. package/src/index.tsx +7 -0
  123. package/src/lib/debouncer.ts +38 -0
  124. package/src/lib/editor-to-text.ts +64 -0
  125. package/src/lib/get-text-around-cursor.ts +82 -0
  126. package/src/lib/slatejs-edits/add-autocompletions.ts +30 -0
  127. package/src/lib/slatejs-edits/clear-autocompletions.ts +15 -0
  128. package/src/lib/slatejs-edits/replace-text.ts +32 -0
  129. package/src/lib/slatejs-edits/with-partial-history.ts +156 -0
  130. package/src/lib/utils.ts +59 -0
  131. package/src/styles.css +3 -0
  132. package/src/types/autosuggestion-state.ts +6 -0
  133. package/src/types/autosuggestions-bare-function.ts +5 -0
  134. package/src/types/base-autosuggestions-config.tsx +15 -0
  135. package/src/types/custom-editor.tsx +29 -0
  136. package/src/types/editor-autocomplete-state.ts +19 -0
  137. package/src/types/index.ts +4 -0
  138. package/tailwind.config.js +7 -0
  139. package/tsconfig.json +5 -0
  140. 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,3 @@
1
+
2
+ //# sourceMappingURL=out.js.map
3
+ //# sourceMappingURL=custom-editor.mjs.map
@@ -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,5 @@
1
+ export { areEqual_autocompleteState } from '../chunk-4LJAS777.mjs';
2
+ import '../chunk-KGKLUWKW.mjs';
3
+ import '../chunk-MRXNTQOX.mjs';
4
+ //# sourceMappingURL=out.js.map
5
+ //# sourceMappingURL=editor-autocomplete-state.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export { BaseAutosuggestionsConfig, defaultBaseAutosuggestionsConfig } from './base-autosuggestions-config.js';
2
+ export { AutosuggestionsBareFunction } from './autosuggestions-bare-function.js';
@@ -0,0 +1,5 @@
1
+ import '../chunk-YPBKY4KY.mjs';
2
+ export { defaultBaseAutosuggestionsConfig } from '../chunk-5SJM4R4K.mjs';
3
+ import '../chunk-MRXNTQOX.mjs';
4
+ //# sourceMappingURL=out.js.map
5
+ //# sourceMappingURL=index.mjs.map
@@ -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,9 @@
1
+ // If you want to use other PostCSS plugins, see the following:
2
+ // https://tailwindcss.com/docs/using-with-preprocessors
3
+
4
+ module.exports = {
5
+ plugins: {
6
+ tailwindcss: {},
7
+ autoprefixer: {},
8
+ },
9
+ };
@@ -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,2 @@
1
+ export { BaseCopilotTextarea } from "./copilot-textarea/base-copilot-textarea/base-copilot-textarea";
2
+ export type { BaseCopilotTextareaProps } from "./copilot-textarea/base-copilot-textarea/base-copilot-textarea";
@@ -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,7 @@
1
+ // styles
2
+ import "./styles.css";
3
+
4
+ export * from "./components";
5
+ export * from "./context";
6
+ export * from "./hooks";
7
+ export * from "./types";
@@ -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
+ }