@particle-academy/fancy-code 0.1.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/dist/index.js ADDED
@@ -0,0 +1,719 @@
1
+ import { createContext, useState, useCallback, useRef, useMemo, useSyncExternalStore, useEffect, useContext } from 'react';
2
+ import { cn, useControllableState } from '@particle-academy/react-fancy';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { javascript } from '@codemirror/lang-javascript';
5
+ import { html } from '@codemirror/lang-html';
6
+ import { php } from '@codemirror/lang-php';
7
+ import { Compartment, EditorState } from '@codemirror/state';
8
+ import { EditorView, lineNumbers, highlightActiveLineGutter, placeholder, drawSelection, dropCursor, highlightActiveLine, keymap } from '@codemirror/view';
9
+ import { history, defaultKeymap, historyKeymap, indentWithTab } from '@codemirror/commands';
10
+ import { HighlightStyle, foldGutter, indentOnInput, bracketMatching, foldKeymap, syntaxHighlighting } from '@codemirror/language';
11
+ import { highlightSelectionMatches, searchKeymap } from '@codemirror/search';
12
+ import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
13
+ import { tags } from '@lezer/highlight';
14
+
15
+ // src/components/CodeEditor/CodeEditor.tsx
16
+ var CodeEditorContext = createContext(null);
17
+ function useCodeEditor() {
18
+ const ctx = useContext(CodeEditorContext);
19
+ if (!ctx) {
20
+ throw new Error("useCodeEditor must be used within a <CodeEditor> component");
21
+ }
22
+ return ctx;
23
+ }
24
+ function CodeEditorPanel({ className }) {
25
+ const { _containerRef } = useCodeEditor();
26
+ return /* @__PURE__ */ jsx(
27
+ "div",
28
+ {
29
+ "data-fancy-code-panel": "",
30
+ ref: _containerRef,
31
+ className: cn("text-sm", className)
32
+ }
33
+ );
34
+ }
35
+ CodeEditorPanel.displayName = "CodeEditorPanel";
36
+ function CodeEditorToolbarSeparator() {
37
+ return /* @__PURE__ */ jsx(
38
+ "div",
39
+ {
40
+ "data-fancy-code-toolbar-separator": "",
41
+ className: "mx-1 h-4 w-px bg-zinc-200 dark:bg-zinc-700"
42
+ }
43
+ );
44
+ }
45
+ CodeEditorToolbarSeparator.displayName = "CodeEditorToolbarSeparator";
46
+
47
+ // src/languages/registry.ts
48
+ var languageRegistry = /* @__PURE__ */ new Map();
49
+ function registerLanguage(def) {
50
+ languageRegistry.set(def.name.toLowerCase(), def);
51
+ if (def.aliases) {
52
+ for (const alias of def.aliases) {
53
+ languageRegistry.set(alias.toLowerCase(), def);
54
+ }
55
+ }
56
+ }
57
+ function getLanguage(name) {
58
+ return languageRegistry.get(name.toLowerCase());
59
+ }
60
+ function getRegisteredLanguages() {
61
+ const seen = /* @__PURE__ */ new Set();
62
+ const names = [];
63
+ for (const [, def] of languageRegistry) {
64
+ if (!seen.has(def)) {
65
+ seen.add(def);
66
+ names.push(def.name);
67
+ }
68
+ }
69
+ return names;
70
+ }
71
+ registerLanguage({
72
+ name: "JavaScript",
73
+ aliases: ["js", "javascript", "jsx"],
74
+ support: () => javascript({ jsx: true })
75
+ });
76
+ registerLanguage({
77
+ name: "TypeScript",
78
+ aliases: ["ts", "typescript", "tsx"],
79
+ support: () => javascript({ typescript: true, jsx: true })
80
+ });
81
+ registerLanguage({
82
+ name: "HTML",
83
+ aliases: ["html", "htm"],
84
+ support: () => html()
85
+ });
86
+ registerLanguage({
87
+ name: "PHP",
88
+ aliases: ["php"],
89
+ support: () => php()
90
+ });
91
+ var iconBtnClass = "inline-flex items-center justify-center rounded-md p-1 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-200";
92
+ function LanguageSelector() {
93
+ const { language, setLanguage } = useCodeEditor();
94
+ const languages = getRegisteredLanguages();
95
+ return /* @__PURE__ */ jsx(
96
+ "select",
97
+ {
98
+ value: language,
99
+ onChange: (e) => setLanguage(e.target.value),
100
+ className: "h-6 rounded border border-zinc-200 bg-transparent px-1.5 text-[11px] text-zinc-600 outline-none transition-colors hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-600",
101
+ children: languages.map((lang) => /* @__PURE__ */ jsx("option", { value: lang, children: lang }, lang))
102
+ }
103
+ );
104
+ }
105
+ function DefaultToolbarActions() {
106
+ const { copyToClipboard, toggleWordWrap, wordWrap } = useCodeEditor();
107
+ const [copied, setCopied] = useState(false);
108
+ const handleCopy = async () => {
109
+ await copyToClipboard();
110
+ setCopied(true);
111
+ setTimeout(() => setCopied(false), 1500);
112
+ };
113
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
114
+ /* @__PURE__ */ jsx(LanguageSelector, {}),
115
+ /* @__PURE__ */ jsx(CodeEditorToolbarSeparator, {}),
116
+ /* @__PURE__ */ jsx(
117
+ "button",
118
+ {
119
+ type: "button",
120
+ onClick: toggleWordWrap,
121
+ title: wordWrap ? "Disable Word Wrap" : "Enable Word Wrap",
122
+ className: cn(iconBtnClass, wordWrap && "text-blue-500 dark:text-blue-400"),
123
+ children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
124
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
125
+ /* @__PURE__ */ jsx("path", { d: "M3 12h15a3 3 0 110 6h-4" }),
126
+ /* @__PURE__ */ jsx("polyline", { points: "16 16 14 18 16 20" }),
127
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "18", x2: "10", y2: "18" })
128
+ ] })
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: handleCopy, title: "Copy to Clipboard", className: iconBtnClass, children: copied ? /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) }) : /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
132
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
133
+ /* @__PURE__ */ jsx("path", { d: "M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" })
134
+ ] }) })
135
+ ] });
136
+ }
137
+ function CodeEditorToolbar({ children, className }) {
138
+ const hasChildren = children != null;
139
+ return /* @__PURE__ */ jsx(
140
+ "div",
141
+ {
142
+ "data-fancy-code-toolbar": "",
143
+ className: cn(
144
+ "flex items-center gap-0.5 border-b border-zinc-200 px-2 py-1 dark:border-zinc-700",
145
+ className
146
+ ),
147
+ children: hasChildren ? children : /* @__PURE__ */ jsx(DefaultToolbarActions, {})
148
+ }
149
+ );
150
+ }
151
+ CodeEditorToolbar.displayName = "CodeEditorToolbar";
152
+ function CodeEditorStatusBar({ children, className }) {
153
+ const { cursorPosition, selectionLength, language, tabSize } = useCodeEditor();
154
+ return /* @__PURE__ */ jsx(
155
+ "div",
156
+ {
157
+ "data-fancy-code-statusbar": "",
158
+ className: cn(
159
+ "flex items-center gap-3 border-t border-zinc-200 px-3 py-1 text-[11px] text-zinc-500 dark:border-zinc-700 dark:text-zinc-400",
160
+ className
161
+ ),
162
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
163
+ /* @__PURE__ */ jsxs("span", { children: [
164
+ "Ln ",
165
+ cursorPosition.line,
166
+ ", Col ",
167
+ cursorPosition.col
168
+ ] }),
169
+ selectionLength > 0 && /* @__PURE__ */ jsxs("span", { children: [
170
+ selectionLength,
171
+ " selected"
172
+ ] }),
173
+ /* @__PURE__ */ jsx("span", { className: "ml-auto", children: language }),
174
+ /* @__PURE__ */ jsxs("span", { children: [
175
+ tabSize,
176
+ " spaces"
177
+ ] })
178
+ ] })
179
+ }
180
+ );
181
+ }
182
+ CodeEditorStatusBar.displayName = "CodeEditorStatusBar";
183
+
184
+ // src/themes/registry.ts
185
+ var themeRegistry = /* @__PURE__ */ new Map();
186
+ function registerTheme(theme) {
187
+ themeRegistry.set(theme.name.toLowerCase(), theme);
188
+ }
189
+ function getTheme(name) {
190
+ return themeRegistry.get(name.toLowerCase());
191
+ }
192
+ function getRegisteredThemes() {
193
+ return Array.from(themeRegistry.keys());
194
+ }
195
+ var editorTheme = EditorView.theme(
196
+ {
197
+ "&": {
198
+ backgroundColor: "#ffffff",
199
+ color: "#1e1e2e"
200
+ },
201
+ ".cm-content": {
202
+ caretColor: "#3b82f6"
203
+ },
204
+ ".cm-cursor, .cm-dropCursor": {
205
+ borderLeftColor: "#3b82f6"
206
+ },
207
+ "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
208
+ backgroundColor: "#dbeafe"
209
+ },
210
+ ".cm-activeLine": {
211
+ backgroundColor: "#f8fafc"
212
+ },
213
+ ".cm-gutters": {
214
+ backgroundColor: "#f8fafc",
215
+ color: "#94a3b8",
216
+ borderRight: "1px solid #e2e8f0"
217
+ },
218
+ ".cm-activeLineGutter": {
219
+ backgroundColor: "#f1f5f9",
220
+ color: "#475569"
221
+ },
222
+ ".cm-foldPlaceholder": {
223
+ backgroundColor: "#e2e8f0",
224
+ color: "#64748b",
225
+ border: "none"
226
+ },
227
+ ".cm-tooltip": {
228
+ backgroundColor: "#ffffff",
229
+ border: "1px solid #e2e8f0"
230
+ },
231
+ ".cm-tooltip .cm-tooltip-arrow:before": {
232
+ borderTopColor: "#e2e8f0",
233
+ borderBottomColor: "#e2e8f0"
234
+ },
235
+ ".cm-tooltip .cm-tooltip-arrow:after": {
236
+ borderTopColor: "#ffffff",
237
+ borderBottomColor: "#ffffff"
238
+ },
239
+ ".cm-tooltip-autocomplete": {
240
+ "& > ul > li[aria-selected]": {
241
+ backgroundColor: "#dbeafe",
242
+ color: "#1e40af"
243
+ }
244
+ },
245
+ ".cm-searchMatch": {
246
+ backgroundColor: "#fef08a",
247
+ outline: "1px solid #facc15"
248
+ },
249
+ ".cm-searchMatch.cm-searchMatch-selected": {
250
+ backgroundColor: "#bbf7d0",
251
+ outline: "1px solid #22c55e"
252
+ },
253
+ ".cm-selectionMatch": {
254
+ backgroundColor: "#e0f2fe"
255
+ },
256
+ ".cm-matchingBracket, .cm-nonmatchingBracket": {
257
+ outline: "1px solid #94a3b8"
258
+ },
259
+ ".cm-matchingBracket": {
260
+ backgroundColor: "#e0f2fe"
261
+ }
262
+ },
263
+ { dark: false }
264
+ );
265
+ var highlightStyle = HighlightStyle.define([
266
+ { tag: tags.keyword, color: "#8b5cf6" },
267
+ { tag: [tags.name, tags.deleted, tags.character, tags.macroName], color: "#1e1e2e" },
268
+ { tag: [tags.function(tags.variableName), tags.labelName], color: "#2563eb" },
269
+ { tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: "#d97706" },
270
+ { tag: [tags.definition(tags.name), tags.separator], color: "#1e1e2e" },
271
+ { tag: [tags.typeName, tags.className, tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], color: "#d97706" },
272
+ { tag: [tags.operator, tags.operatorKeyword, tags.url, tags.escape, tags.regexp, tags.link, tags.special(tags.string)], color: "#0891b2" },
273
+ { tag: [tags.meta, tags.comment], color: "#94a3b8", fontStyle: "italic" },
274
+ { tag: tags.strong, fontWeight: "bold" },
275
+ { tag: tags.emphasis, fontStyle: "italic" },
276
+ { tag: tags.strikethrough, textDecoration: "line-through" },
277
+ { tag: tags.link, color: "#2563eb", textDecoration: "underline" },
278
+ { tag: tags.heading, fontWeight: "bold", color: "#8b5cf6" },
279
+ { tag: [tags.atom, tags.bool, tags.special(tags.variableName)], color: "#d97706" },
280
+ { tag: [tags.processingInstruction, tags.string, tags.inserted], color: "#059669" },
281
+ { tag: tags.invalid, color: "#ef4444" },
282
+ { tag: tags.tagName, color: "#dc2626" },
283
+ { tag: tags.attributeName, color: "#d97706" },
284
+ { tag: tags.attributeValue, color: "#059669" }
285
+ ]);
286
+ registerTheme({
287
+ name: "light",
288
+ variant: "light",
289
+ editorTheme,
290
+ highlightStyle
291
+ });
292
+ var editorTheme2 = EditorView.theme(
293
+ {
294
+ "&": {
295
+ backgroundColor: "#18181b",
296
+ color: "#e4e4e7"
297
+ },
298
+ ".cm-content": {
299
+ caretColor: "#60a5fa"
300
+ },
301
+ ".cm-cursor, .cm-dropCursor": {
302
+ borderLeftColor: "#60a5fa"
303
+ },
304
+ "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
305
+ backgroundColor: "#1e3a5f"
306
+ },
307
+ ".cm-activeLine": {
308
+ backgroundColor: "#27272a"
309
+ },
310
+ ".cm-gutters": {
311
+ backgroundColor: "#18181b",
312
+ color: "#52525b",
313
+ borderRight: "1px solid #27272a"
314
+ },
315
+ ".cm-activeLineGutter": {
316
+ backgroundColor: "#27272a",
317
+ color: "#a1a1aa"
318
+ },
319
+ ".cm-foldPlaceholder": {
320
+ backgroundColor: "#3f3f46",
321
+ color: "#a1a1aa",
322
+ border: "none"
323
+ },
324
+ ".cm-tooltip": {
325
+ backgroundColor: "#27272a",
326
+ border: "1px solid #3f3f46",
327
+ color: "#e4e4e7"
328
+ },
329
+ ".cm-tooltip .cm-tooltip-arrow:before": {
330
+ borderTopColor: "#3f3f46",
331
+ borderBottomColor: "#3f3f46"
332
+ },
333
+ ".cm-tooltip .cm-tooltip-arrow:after": {
334
+ borderTopColor: "#27272a",
335
+ borderBottomColor: "#27272a"
336
+ },
337
+ ".cm-tooltip-autocomplete": {
338
+ "& > ul > li[aria-selected]": {
339
+ backgroundColor: "#1e3a5f",
340
+ color: "#93c5fd"
341
+ }
342
+ },
343
+ ".cm-searchMatch": {
344
+ backgroundColor: "#854d0e",
345
+ outline: "1px solid #a16207"
346
+ },
347
+ ".cm-searchMatch.cm-searchMatch-selected": {
348
+ backgroundColor: "#166534",
349
+ outline: "1px solid #15803d"
350
+ },
351
+ ".cm-selectionMatch": {
352
+ backgroundColor: "#1e3a5f"
353
+ },
354
+ ".cm-matchingBracket, .cm-nonmatchingBracket": {
355
+ outline: "1px solid #71717a"
356
+ },
357
+ ".cm-matchingBracket": {
358
+ backgroundColor: "#3f3f46"
359
+ }
360
+ },
361
+ { dark: true }
362
+ );
363
+ var highlightStyle2 = HighlightStyle.define([
364
+ { tag: tags.keyword, color: "#c084fc" },
365
+ { tag: [tags.name, tags.deleted, tags.character, tags.macroName], color: "#e4e4e7" },
366
+ { tag: [tags.function(tags.variableName), tags.labelName], color: "#60a5fa" },
367
+ { tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: "#fbbf24" },
368
+ { tag: [tags.definition(tags.name), tags.separator], color: "#e4e4e7" },
369
+ { tag: [tags.typeName, tags.className, tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], color: "#fbbf24" },
370
+ { tag: [tags.operator, tags.operatorKeyword, tags.url, tags.escape, tags.regexp, tags.link, tags.special(tags.string)], color: "#22d3ee" },
371
+ { tag: [tags.meta, tags.comment], color: "#71717a", fontStyle: "italic" },
372
+ { tag: tags.strong, fontWeight: "bold" },
373
+ { tag: tags.emphasis, fontStyle: "italic" },
374
+ { tag: tags.strikethrough, textDecoration: "line-through" },
375
+ { tag: tags.link, color: "#60a5fa", textDecoration: "underline" },
376
+ { tag: tags.heading, fontWeight: "bold", color: "#c084fc" },
377
+ { tag: [tags.atom, tags.bool, tags.special(tags.variableName)], color: "#fbbf24" },
378
+ { tag: [tags.processingInstruction, tags.string, tags.inserted], color: "#34d399" },
379
+ { tag: tags.invalid, color: "#f87171" },
380
+ { tag: tags.tagName, color: "#f87171" },
381
+ { tag: tags.attributeName, color: "#fbbf24" },
382
+ { tag: tags.attributeValue, color: "#34d399" }
383
+ ]);
384
+ registerTheme({
385
+ name: "dark",
386
+ variant: "dark",
387
+ editorTheme: editorTheme2,
388
+ highlightStyle: highlightStyle2
389
+ });
390
+
391
+ // src/hooks/use-codemirror.ts
392
+ function resolveLanguageExtension(name) {
393
+ const def = getLanguage(name);
394
+ if (!def) return [];
395
+ const result = def.support();
396
+ if (result instanceof Promise) {
397
+ return [];
398
+ }
399
+ return result;
400
+ }
401
+ function resolveThemeExtensions(name) {
402
+ const def = getTheme(name);
403
+ if (!def) {
404
+ const fallback = getTheme("dark");
405
+ if (!fallback) return [];
406
+ return [fallback.editorTheme, syntaxHighlighting(fallback.highlightStyle)];
407
+ }
408
+ return [def.editorTheme, syntaxHighlighting(def.highlightStyle)];
409
+ }
410
+ function useCodemirror({
411
+ containerRef,
412
+ value,
413
+ onChange,
414
+ language,
415
+ theme,
416
+ readOnly,
417
+ lineNumbers: lineNumbers$1,
418
+ wordWrap,
419
+ tabSize,
420
+ placeholder: placeholder$1,
421
+ minHeight,
422
+ maxHeight,
423
+ searchEnabled,
424
+ additionalExtensions,
425
+ onCursorChange
426
+ }) {
427
+ const viewRef = useRef(null);
428
+ const isExternalUpdate = useRef(false);
429
+ const onChangeRef = useRef(onChange);
430
+ const onCursorChangeRef = useRef(onCursorChange);
431
+ onChangeRef.current = onChange;
432
+ onCursorChangeRef.current = onCursorChange;
433
+ const languageComp = useRef(new Compartment());
434
+ const themeComp = useRef(new Compartment());
435
+ const lineNumbersComp = useRef(new Compartment());
436
+ const wrapComp = useRef(new Compartment());
437
+ const tabSizeComp = useRef(new Compartment());
438
+ const readOnlyComp = useRef(new Compartment());
439
+ const placeholderComp = useRef(new Compartment());
440
+ const heightComp = useRef(new Compartment());
441
+ function buildHeightExtension(min, max) {
442
+ const styles = {};
443
+ if (min) styles.minHeight = `${min}px`;
444
+ if (max) styles.maxHeight = `${max}px`;
445
+ if (Object.keys(styles).length === 0) return [];
446
+ return EditorView.theme({
447
+ "&": { ...max ? { maxHeight: `${max}px` } : {} },
448
+ ".cm-scroller": { overflow: "auto", ...styles }
449
+ });
450
+ }
451
+ useEffect(() => {
452
+ const container = containerRef.current;
453
+ if (!container) return;
454
+ const updateListener = EditorView.updateListener.of((update) => {
455
+ if (update.docChanged && !isExternalUpdate.current) {
456
+ onChangeRef.current?.(update.state.doc.toString());
457
+ }
458
+ if (update.selectionSet || update.docChanged) {
459
+ const pos = update.state.selection.main;
460
+ const line = update.state.doc.lineAt(pos.head);
461
+ onCursorChangeRef.current?.({
462
+ line: line.number,
463
+ col: pos.head - line.from + 1,
464
+ selectionLength: Math.abs(pos.to - pos.from)
465
+ });
466
+ }
467
+ });
468
+ const state = EditorState.create({
469
+ doc: value,
470
+ extensions: [
471
+ // Compartmentalized extensions
472
+ languageComp.current.of(resolveLanguageExtension(language)),
473
+ themeComp.current.of(resolveThemeExtensions(theme)),
474
+ lineNumbersComp.current.of(lineNumbers$1 ? [lineNumbers(), highlightActiveLineGutter()] : []),
475
+ wrapComp.current.of(wordWrap ? EditorView.lineWrapping : []),
476
+ tabSizeComp.current.of(EditorState.tabSize.of(tabSize)),
477
+ readOnlyComp.current.of(EditorState.readOnly.of(readOnly)),
478
+ placeholderComp.current.of(placeholder$1 ? placeholder(placeholder$1) : []),
479
+ heightComp.current.of(buildHeightExtension(minHeight, maxHeight)),
480
+ // Static extensions
481
+ history(),
482
+ foldGutter(),
483
+ drawSelection(),
484
+ dropCursor(),
485
+ indentOnInput(),
486
+ bracketMatching(),
487
+ closeBrackets(),
488
+ autocompletion(),
489
+ highlightActiveLine(),
490
+ highlightSelectionMatches(),
491
+ keymap.of([
492
+ ...closeBracketsKeymap,
493
+ ...defaultKeymap,
494
+ ...searchKeymap,
495
+ ...historyKeymap,
496
+ ...foldKeymap,
497
+ ...completionKeymap,
498
+ indentWithTab
499
+ ]),
500
+ updateListener,
501
+ ...additionalExtensions ?? []
502
+ ]
503
+ });
504
+ const view = new EditorView({ state, parent: container });
505
+ viewRef.current = view;
506
+ return () => {
507
+ view.destroy();
508
+ viewRef.current = null;
509
+ };
510
+ }, [containerRef]);
511
+ useEffect(() => {
512
+ const view = viewRef.current;
513
+ if (!view) return;
514
+ const currentDoc = view.state.doc.toString();
515
+ if (value !== currentDoc) {
516
+ isExternalUpdate.current = true;
517
+ view.dispatch({
518
+ changes: { from: 0, to: currentDoc.length, insert: value }
519
+ });
520
+ isExternalUpdate.current = false;
521
+ }
522
+ }, [value]);
523
+ useEffect(() => {
524
+ const view = viewRef.current;
525
+ if (!view) return;
526
+ const def = getLanguage(language);
527
+ if (!def) {
528
+ view.dispatch({ effects: languageComp.current.reconfigure([]) });
529
+ return;
530
+ }
531
+ const result = def.support();
532
+ if (result instanceof Promise) {
533
+ result.then((ext) => {
534
+ if (viewRef.current) {
535
+ viewRef.current.dispatch({ effects: languageComp.current.reconfigure(ext) });
536
+ }
537
+ });
538
+ } else {
539
+ view.dispatch({ effects: languageComp.current.reconfigure(result) });
540
+ }
541
+ }, [language]);
542
+ useEffect(() => {
543
+ const view = viewRef.current;
544
+ if (!view) return;
545
+ view.dispatch({ effects: themeComp.current.reconfigure(resolveThemeExtensions(theme)) });
546
+ }, [theme]);
547
+ useEffect(() => {
548
+ const view = viewRef.current;
549
+ if (!view) return;
550
+ view.dispatch({
551
+ effects: lineNumbersComp.current.reconfigure(
552
+ lineNumbers$1 ? [lineNumbers(), highlightActiveLineGutter()] : []
553
+ )
554
+ });
555
+ }, [lineNumbers$1]);
556
+ useEffect(() => {
557
+ const view = viewRef.current;
558
+ if (!view) return;
559
+ view.dispatch({
560
+ effects: wrapComp.current.reconfigure(wordWrap ? EditorView.lineWrapping : [])
561
+ });
562
+ }, [wordWrap]);
563
+ useEffect(() => {
564
+ const view = viewRef.current;
565
+ if (!view) return;
566
+ view.dispatch({
567
+ effects: tabSizeComp.current.reconfigure(EditorState.tabSize.of(tabSize))
568
+ });
569
+ }, [tabSize]);
570
+ useEffect(() => {
571
+ const view = viewRef.current;
572
+ if (!view) return;
573
+ view.dispatch({
574
+ effects: readOnlyComp.current.reconfigure(EditorState.readOnly.of(readOnly))
575
+ });
576
+ }, [readOnly]);
577
+ useEffect(() => {
578
+ const view = viewRef.current;
579
+ if (!view) return;
580
+ view.dispatch({
581
+ effects: placeholderComp.current.reconfigure(placeholder$1 ? placeholder(placeholder$1) : [])
582
+ });
583
+ }, [placeholder$1]);
584
+ return { view: viewRef.current };
585
+ }
586
+ function subscribe(callback) {
587
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
588
+ mq.addEventListener("change", callback);
589
+ return () => mq.removeEventListener("change", callback);
590
+ }
591
+ function getSnapshot() {
592
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
593
+ }
594
+ function getServerSnapshot() {
595
+ return false;
596
+ }
597
+ function useDarkMode() {
598
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
599
+ }
600
+ function CodeEditorRoot({
601
+ children,
602
+ className,
603
+ value: valueProp,
604
+ defaultValue = "",
605
+ onChange,
606
+ language: languageProp = "javascript",
607
+ onLanguageChange,
608
+ theme = "auto",
609
+ readOnly = false,
610
+ lineNumbers: lineNumbersProp = true,
611
+ wordWrap: wordWrapProp = false,
612
+ tabSize: tabSizeProp = 2,
613
+ placeholder,
614
+ minHeight,
615
+ maxHeight,
616
+ extensions: additionalExtensions
617
+ }) {
618
+ const [currentValue, setCurrentValue] = useControllableState(valueProp, defaultValue, onChange);
619
+ const isDark = useDarkMode();
620
+ const resolvedTheme = theme === "auto" ? isDark ? "dark" : "light" : theme;
621
+ const [currentLanguage, setCurrentLanguageState] = useState(() => {
622
+ const def = getLanguage(languageProp);
623
+ return def?.name ?? languageProp;
624
+ });
625
+ const setLanguage = useCallback(
626
+ (lang) => {
627
+ const def = getLanguage(lang);
628
+ const resolved = def?.name ?? lang;
629
+ setCurrentLanguageState(resolved);
630
+ onLanguageChange?.(resolved);
631
+ },
632
+ [onLanguageChange]
633
+ );
634
+ const [showLineNumbers, setShowLineNumbers] = useState(lineNumbersProp);
635
+ const [isWordWrap, setIsWordWrap] = useState(wordWrapProp);
636
+ const [cursorPosition, setCursorPosition] = useState({ line: 1, col: 1 });
637
+ const [selectionLength, setSelectionLength] = useState(0);
638
+ const containerRef = useRef(null);
639
+ const { view } = useCodemirror({
640
+ containerRef,
641
+ value: currentValue,
642
+ onChange: setCurrentValue,
643
+ language: currentLanguage,
644
+ theme: resolvedTheme,
645
+ readOnly,
646
+ lineNumbers: showLineNumbers,
647
+ wordWrap: isWordWrap,
648
+ tabSize: tabSizeProp,
649
+ placeholder,
650
+ minHeight,
651
+ maxHeight,
652
+ searchEnabled: true,
653
+ additionalExtensions,
654
+ onCursorChange: ({ line, col, selectionLength: sel }) => {
655
+ setCursorPosition({ line, col });
656
+ setSelectionLength(sel);
657
+ }
658
+ });
659
+ const contextValue = useMemo(
660
+ () => ({
661
+ view,
662
+ getValue: () => view?.state.doc.toString() ?? currentValue,
663
+ getSelection: () => {
664
+ if (!view) return "";
665
+ const sel = view.state.selection.main;
666
+ return view.state.sliceDoc(sel.from, sel.to);
667
+ },
668
+ setValue: (v) => setCurrentValue(v),
669
+ replaceSelection: (text) => {
670
+ if (!view) return;
671
+ view.dispatch(view.state.replaceSelection(text));
672
+ },
673
+ focus: () => view?.focus(),
674
+ language: currentLanguage,
675
+ setLanguage,
676
+ theme: resolvedTheme,
677
+ readOnly,
678
+ lineNumbers: showLineNumbers,
679
+ wordWrap: isWordWrap,
680
+ tabSize: tabSizeProp,
681
+ toggleWordWrap: () => setIsWordWrap((w) => !w),
682
+ toggleLineNumbers: () => setShowLineNumbers((l) => !l),
683
+ copyToClipboard: async () => {
684
+ const text = view?.state.doc.toString() ?? currentValue;
685
+ await navigator.clipboard.writeText(text);
686
+ },
687
+ cursorPosition,
688
+ selectionLength,
689
+ _containerRef: containerRef,
690
+ _minHeight: minHeight,
691
+ _maxHeight: maxHeight
692
+ }),
693
+ [view, currentValue, currentLanguage, setLanguage, resolvedTheme, readOnly, showLineNumbers, isWordWrap, tabSizeProp, cursorPosition, selectionLength, setCurrentValue, minHeight, maxHeight]
694
+ );
695
+ return /* @__PURE__ */ jsx(CodeEditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
696
+ "div",
697
+ {
698
+ "data-fancy-code-editor": "",
699
+ className: cn(
700
+ "overflow-hidden rounded-xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900",
701
+ className
702
+ ),
703
+ children
704
+ }
705
+ ) });
706
+ }
707
+ CodeEditorRoot.displayName = "CodeEditor";
708
+ var ToolbarWithSeparator = Object.assign(CodeEditorToolbar, {
709
+ Separator: CodeEditorToolbarSeparator
710
+ });
711
+ var CodeEditor = Object.assign(CodeEditorRoot, {
712
+ Toolbar: ToolbarWithSeparator,
713
+ Panel: CodeEditorPanel,
714
+ StatusBar: CodeEditorStatusBar
715
+ });
716
+
717
+ export { CodeEditor, getLanguage, getRegisteredLanguages, getRegisteredThemes, getTheme, registerLanguage, registerTheme, useCodeEditor };
718
+ //# sourceMappingURL=index.js.map
719
+ //# sourceMappingURL=index.js.map