@codespark/react 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.
@@ -0,0 +1,36 @@
1
+ import { ComponentType } from "react";
2
+ import { EditorProps } from "@monaco-editor/react";
3
+ import * as monaco from "monaco-editor";
4
+ import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
5
+
6
+ //#region src/lib/editor-adapter/index.d.ts
7
+ declare enum EditorEngine {
8
+ Monaco = 0,
9
+ CodeMirror = 1,
10
+ }
11
+ interface EditorInstance {
12
+ [EditorEngine.Monaco]: monaco.editor.IStandaloneCodeEditor;
13
+ [EditorEngine.CodeMirror]: ReactCodeMirrorRef;
14
+ }
15
+ interface EditorAdapter<E extends EditorEngine = any> {
16
+ kind: E;
17
+ instance: EditorInstance[E];
18
+ getValue(): string;
19
+ setValue(value: string, addToHistory?: boolean): void;
20
+ format(): Promise<void>;
21
+ }
22
+ interface EditorEngineComponent<E extends EditorEngine = any, P = object, I = unknown> {
23
+ kind: E;
24
+ Component: ComponentType<P>;
25
+ createAdapter: (instance: I) => EditorAdapter<E>;
26
+ }
27
+ //#endregion
28
+ //#region src/components/editor/monaco/index.d.ts
29
+ interface MonacoProps extends EditorProps {
30
+ readonly id?: string;
31
+ files?: Record<string, string>;
32
+ imports?: Record<string, string>;
33
+ }
34
+ declare const Monaco: EditorEngineComponent<EditorEngine.Monaco, MonacoProps, monaco.editor.IStandaloneCodeEditor>;
35
+ //#endregion
36
+ export { Monaco, MonacoProps };
package/dist/monaco.js ADDED
@@ -0,0 +1,323 @@
1
+ import { shikiToMonaco } from "@shikijs/monaco";
2
+ import parserEstree from "prettier/plugins/estree";
3
+ import parserTypescript from "prettier/plugins/typescript";
4
+ import prettier from "prettier/standalone";
5
+ import { memo, useEffect, useRef, useState } from "react";
6
+ import { createHighlighterCore } from "shiki/core";
7
+ import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
8
+ import { clsx } from "clsx";
9
+ import { twMerge } from "tailwind-merge";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+
12
+ //#region src/lib/editor-adapter/index.ts
13
+ let EditorEngine = /* @__PURE__ */ function(EditorEngine$1) {
14
+ EditorEngine$1[EditorEngine$1["Monaco"] = 0] = "Monaco";
15
+ EditorEngine$1[EditorEngine$1["CodeMirror"] = 1] = "CodeMirror";
16
+ return EditorEngine$1;
17
+ }({});
18
+
19
+ //#endregion
20
+ //#region src/lib/utils.ts
21
+ function cn(...inputs) {
22
+ return twMerge(clsx(inputs));
23
+ }
24
+
25
+ //#endregion
26
+ //#region src/ui/skeleton.tsx
27
+ function Skeleton({ className, ...props }) {
28
+ return /* @__PURE__ */ jsx("div", {
29
+ "data-slot": "skeleton",
30
+ className: cn("bg-accent animate-pulse rounded-md", className),
31
+ ...props
32
+ });
33
+ }
34
+
35
+ //#endregion
36
+ //#region src/components/editor/monaco/adapter.ts
37
+ var MonacoEditorAdapter = class {
38
+ constructor(kind, instance) {
39
+ this.kind = kind;
40
+ this.instance = instance;
41
+ }
42
+ getValue() {
43
+ return this.instance.getModel()?.getValue() ?? "";
44
+ }
45
+ setValue(value, _addToHistory) {
46
+ this.instance.getModel()?.setValue(value);
47
+ }
48
+ async format() {
49
+ await this.instance.getAction("editor.action.formatDocument")?.run();
50
+ }
51
+ };
52
+
53
+ //#endregion
54
+ //#region src/components/editor/monaco/index.tsx
55
+ const addedLibs = /* @__PURE__ */ new Set();
56
+ const dtsCacheMap = /* @__PURE__ */ new Map();
57
+ const setup = async () => {
58
+ if (typeof window === "undefined") return;
59
+ const [mod, highlighter] = await Promise.all([import("@monaco-editor/react"), createHighlighterCore({
60
+ themes: [import("shiki/themes/vitesse-light.mjs"), import("shiki/themes/vitesse-dark.mjs")],
61
+ langs: [
62
+ import("shiki/langs/typescript.mjs"),
63
+ import("shiki/langs/tsx.mjs"),
64
+ import("shiki/langs/javascript.mjs"),
65
+ import("shiki/langs/jsx.mjs"),
66
+ import("shiki/langs/json.mjs"),
67
+ import("shiki/langs/css.mjs"),
68
+ import("shiki/langs/html.mjs")
69
+ ],
70
+ langAlias: {
71
+ typescript: "tsx",
72
+ javascript: "jsx"
73
+ },
74
+ engine: createJavaScriptRegexEngine()
75
+ })]);
76
+ return mod.loader.init().then((Monaco$1) => {
77
+ Monaco$1.languages.register({ id: "tsx" });
78
+ Monaco$1.languages.register({ id: "jsx" });
79
+ shikiToMonaco(highlighter, Monaco$1);
80
+ Monaco$1.typescript.typescriptDefaults.setEagerModelSync(true);
81
+ Monaco$1.typescript.typescriptDefaults.setDiagnosticsOptions({
82
+ noSemanticValidation: false,
83
+ noSyntaxValidation: false
84
+ });
85
+ Monaco$1.typescript.typescriptDefaults.setCompilerOptions({
86
+ strict: true,
87
+ noImplicitAny: false,
88
+ noUnusedLocals: false,
89
+ noUnusedParameters: false,
90
+ allowUnreachableCode: true,
91
+ allowUnusedLabels: true,
92
+ allowImportingTsExtensions: true,
93
+ target: Monaco$1.typescript.ScriptTarget.ESNext,
94
+ allowNonTsExtensions: true,
95
+ moduleResolution: Monaco$1.typescript.ModuleResolutionKind.NodeJs,
96
+ module: Monaco$1.typescript.ModuleKind.ESNext,
97
+ noEmit: true,
98
+ jsx: Monaco$1.typescript.JsxEmit.Preserve,
99
+ esModuleInterop: true
100
+ });
101
+ Monaco$1.languages.registerDocumentFormattingEditProvider("typescript", { async provideDocumentFormattingEdits(model, options) {
102
+ const text = model.getValue();
103
+ const formatted = await prettier.format(text, {
104
+ parser: "typescript",
105
+ plugins: [parserTypescript, parserEstree],
106
+ tabWidth: options.tabSize,
107
+ useTabs: !options.insertSpaces
108
+ });
109
+ return [{
110
+ range: model.getFullModelRange(),
111
+ text: formatted
112
+ }];
113
+ } });
114
+ }).then(() => mod.default);
115
+ };
116
+ const MONACO_DEFAULT_OPTIONS = {
117
+ fontSize: 14,
118
+ fontFamily: "Fira Code",
119
+ lineHeight: 24,
120
+ padding: {
121
+ top: 16,
122
+ bottom: 16
123
+ },
124
+ automaticLayout: true,
125
+ folding: false,
126
+ scrollBeyondLastLine: false,
127
+ find: { addExtraSpaceOnTop: false },
128
+ minimap: { enabled: false },
129
+ scrollbar: {
130
+ useShadows: false,
131
+ vertical: "auto",
132
+ horizontal: "auto",
133
+ verticalScrollbarSize: 0,
134
+ horizontalScrollbarSize: 0,
135
+ verticalSliderSize: 0,
136
+ horizontalSliderSize: 0
137
+ },
138
+ guides: { indentation: false },
139
+ cursorStyle: "line-thin",
140
+ overviewRulerBorder: false,
141
+ contextmenu: false,
142
+ renderLineHighlightOnlyWhenFocus: true,
143
+ formatOnPaste: true,
144
+ formatOnType: true,
145
+ tabSize: 2,
146
+ insertSpaces: true,
147
+ detectIndentation: false,
148
+ quickSuggestions: true,
149
+ suggestOnTriggerCharacters: true,
150
+ parameterHints: { enabled: true }
151
+ };
152
+ const Monaco = {
153
+ kind: EditorEngine.Monaco,
154
+ Component: memo(function Monaco$1(props) {
155
+ const { value = "", options = {}, onChange, onMount, width, height, id, language, files, imports, ...rest } = props;
156
+ const editorInstance = useRef(null);
157
+ const [monacoInstance, setMonacoInstance] = useState(null);
158
+ const [MonacoEditor, setMonacoEditor] = useState(null);
159
+ const mergedOptions = {
160
+ ...MONACO_DEFAULT_OPTIONS,
161
+ ...Object.fromEntries(Object.entries(options).filter(([, v]) => v !== void 0))
162
+ };
163
+ const handleEditorDidMount = (editor, monaco) => {
164
+ onMount?.(editor, monaco);
165
+ editorInstance.current = editor;
166
+ setMonacoInstance(monaco);
167
+ };
168
+ const handleEditorContentChange = (value$1, evt) => {
169
+ onChange?.(value$1, evt);
170
+ };
171
+ const addExtraLib = (dts = {}) => {
172
+ Object.entries(dts).forEach(([module, content]) => {
173
+ if (addedLibs.has(module)) return;
174
+ if (module.startsWith("http://") || module.startsWith("https://")) monacoInstance.typescript.typescriptDefaults.addExtraLib(`declare module '${module}' { ${content} }`, module);
175
+ else monacoInstance.typescript.typescriptDefaults.addExtraLib(content || `declare module '${module}'`, `file:///node_modules/${module}/index.d.ts`);
176
+ addedLibs.add(module);
177
+ });
178
+ };
179
+ const createModels = (files$1 = {}) => {
180
+ const prefix = `file:///${id}/`;
181
+ const filePaths = new Set(Object.keys(files$1).map((p) => p.replace(/^(\.\.?\/)+/, "")));
182
+ monacoInstance.editor.getModels().forEach((model) => {
183
+ const uriStr = model.uri.toString();
184
+ if (uriStr.startsWith(prefix)) {
185
+ const modelPath = uriStr.slice(prefix.length);
186
+ if (!filePaths.has(modelPath)) model.dispose();
187
+ }
188
+ });
189
+ Object.entries(files$1).forEach(([filePath, code]) => {
190
+ const normalizedPath = filePath.replace(/^(\.\.?\/)+/, "");
191
+ const uri = monacoInstance.Uri.parse(`${prefix}${normalizedPath}`);
192
+ if (!monacoInstance.editor.getModel(uri)) {
193
+ const ext = filePath.split(".").pop();
194
+ const lang = ["ts", "tsx"].includes(ext) ? "typescript" : ext === "css" ? "css" : ext === "json" ? "json" : "javascript";
195
+ monacoInstance.editor.createModel(code, lang, uri);
196
+ }
197
+ });
198
+ };
199
+ const addSuggestions = (paths) => {
200
+ const getRelativePath = (from, to) => {
201
+ const fromParts = from.replace(/^\.\//, "").split("/").slice(0, -1);
202
+ const toParts = to.replace(/^\.\//, "").split("/");
203
+ let commonLength = 0;
204
+ while (commonLength < fromParts.length && commonLength < toParts.length - 1 && fromParts[commonLength] === toParts[commonLength]) commonLength++;
205
+ const upCount = fromParts.length - commonLength;
206
+ return (upCount > 0 ? Array(upCount).fill("..") : ["."]).concat(toParts.slice(commonLength)).join("/");
207
+ };
208
+ return monacoInstance.languages.registerCompletionItemProvider([
209
+ "typescript",
210
+ "typescriptreact",
211
+ "javascript",
212
+ "javascriptreact"
213
+ ], {
214
+ triggerCharacters: [
215
+ "/",
216
+ "'",
217
+ "\""
218
+ ],
219
+ provideCompletionItems(model, position) {
220
+ const importMatch = model.getValueInRange({
221
+ startLineNumber: position.lineNumber,
222
+ startColumn: 1,
223
+ endLineNumber: position.lineNumber,
224
+ endColumn: position.column
225
+ }).match(/(?:import\s+.*?\s+from\s+|import\s+)(['"])(\.[^'"]*?)$/);
226
+ if (!importMatch) return { suggestions: [] };
227
+ const typedPath = importMatch[2];
228
+ const filePaths = paths.filter((p) => !p.endsWith("/"));
229
+ const suggestions = [];
230
+ const addedPaths = /* @__PURE__ */ new Set();
231
+ const currentPath = model.uri.path.replace(/^\/[^/]+\//, "./");
232
+ for (const filePath of filePaths) {
233
+ if (filePath === currentPath) continue;
234
+ const relativePath = getRelativePath(currentPath, filePath);
235
+ if (!relativePath.startsWith(typedPath)) continue;
236
+ let displayPath = relativePath;
237
+ if (/\.(tsx?|jsx?)$/.test(displayPath)) displayPath = displayPath.replace(/\.(tsx?|jsx?)$/, "");
238
+ if (!addedPaths.has(displayPath)) {
239
+ addedPaths.add(displayPath);
240
+ suggestions.push({
241
+ label: displayPath,
242
+ kind: monacoInstance.languages.CompletionItemKind.File,
243
+ insertText: displayPath.slice(typedPath.length),
244
+ range: {
245
+ startLineNumber: position.lineNumber,
246
+ startColumn: position.column,
247
+ endLineNumber: position.lineNumber,
248
+ endColumn: position.column
249
+ }
250
+ });
251
+ }
252
+ }
253
+ return { suggestions };
254
+ }
255
+ });
256
+ };
257
+ useEffect(() => {
258
+ (async () => {
259
+ const MonacoEditorReact = await setup();
260
+ setTimeout(() => {
261
+ if (MonacoEditorReact) setMonacoEditor(() => MonacoEditorReact);
262
+ }, 1e3);
263
+ })();
264
+ }, []);
265
+ useEffect(() => {
266
+ if (typeof window === "undefined" || !monacoInstance || !id) return;
267
+ createModels(files);
268
+ const provider = addSuggestions(Object.keys(files || {}));
269
+ return () => provider?.dispose();
270
+ }, [files, monacoInstance]);
271
+ useEffect(() => {
272
+ if (typeof window === "undefined" || !monacoInstance) return;
273
+ const controllers = /* @__PURE__ */ new Map();
274
+ Promise.all(Object.entries(imports || {}).map(async ([name, url]) => {
275
+ if (dtsCacheMap.has(name)) return [name, dtsCacheMap.get(name)];
276
+ const controller = new AbortController();
277
+ controllers.set(name, controller);
278
+ try {
279
+ const { headers } = await fetch(url, {
280
+ method: "HEAD",
281
+ signal: controller.signal
282
+ });
283
+ const dtsUrl = headers.get("X-TypeScript-Types");
284
+ if (dtsUrl) {
285
+ const dtsContent = await fetch(dtsUrl, { signal: controller.signal }).then((r) => r.text());
286
+ dtsCacheMap.set(name, dtsContent);
287
+ return [name, dtsContent];
288
+ }
289
+ return [name, ""];
290
+ } catch {
291
+ return [name, ""];
292
+ }
293
+ })).then((results) => Object.fromEntries(results)).then(addExtraLib);
294
+ return () => {
295
+ controllers.forEach((controller) => controller.abort());
296
+ };
297
+ }, [imports, monacoInstance]);
298
+ if (!MonacoEditor) return /* @__PURE__ */ jsxs("div", {
299
+ className: "flex flex-col space-y-3 p-5",
300
+ style: { height },
301
+ children: [/* @__PURE__ */ jsx(Skeleton, { className: "w-full flex-1 rounded-xl" }), /* @__PURE__ */ jsxs("div", {
302
+ className: "space-y-3",
303
+ children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-[80%]" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-[65%]" })]
304
+ })]
305
+ });
306
+ return /* @__PURE__ */ jsx(MonacoEditor, {
307
+ value,
308
+ language,
309
+ options: mergedOptions,
310
+ width: width ?? "100%",
311
+ height,
312
+ ...rest,
313
+ onMount: handleEditorDidMount,
314
+ onChange: handleEditorContentChange
315
+ });
316
+ }),
317
+ createAdapter: (instance) => {
318
+ return new MonacoEditorAdapter(EditorEngine.Monaco, instance);
319
+ }
320
+ };
321
+
322
+ //#endregion
323
+ export { Monaco };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@codespark/react",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "React components for codespark ecosystem",
6
+ "keywords": [
7
+ "react-components",
8
+ "code-editor",
9
+ "live-preview",
10
+ "codespark"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ },
17
+ "./*": {
18
+ "import": "./dist/*.js",
19
+ "types": "./dist/*.d.ts"
20
+ },
21
+ "./index.css": {
22
+ "style": "./dist/index.css",
23
+ "import": "./dist/index.css"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsdown"
31
+ },
32
+ "author": "TonyL1u",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/codesparkjs/codespark.git",
37
+ "directory": "packages/react"
38
+ },
39
+ "homepage": "https://codesparkjs.com",
40
+ "dependencies": {
41
+ "@codemirror/lang-javascript": "^6.2.4",
42
+ "@codemirror/lang-markdown": "^6.5.0",
43
+ "@codemirror/language-data": "^6.5.2",
44
+ "@codespark/framework": "^1.0.0",
45
+ "@monaco-editor/react": "^4.7.0",
46
+ "@shikijs/monaco": "^3.20.0",
47
+ "@uiw/react-codemirror": "^4.25.4",
48
+ "class-variance-authority": "^0.7.1",
49
+ "clsx": "^2.1.1",
50
+ "lodash-es": "^4.17.23",
51
+ "lucide-react": "^0.562.0",
52
+ "prettier": "^3.7.4",
53
+ "radix-ui": "^1.4.3",
54
+ "react": "^19.2.3",
55
+ "react-dom": "^19.2.3",
56
+ "react-is": "^19.2.3",
57
+ "shiki": "^3.20.0",
58
+ "tailwind-merge": "^3.4.0",
59
+ "tailwindcss": "^4.1.18"
60
+ },
61
+ "devDependencies": {
62
+ "@types/lodash-es": "^4.17.12",
63
+ "@types/mdx": "^2.0.13",
64
+ "@types/node": "^22.19.2",
65
+ "@types/react": "^19.2.7",
66
+ "@types/react-is": "^19.2.0",
67
+ "monaco-editor": "^0.55.1",
68
+ "rollup-plugin-import-raw": "^1.0.2",
69
+ "tsdown": "^0.17.3"
70
+ }
71
+ }