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