@djangocfg/ui-tools 2.1.239 → 2.1.241

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,64 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import type * as MonacoEditor from 'monaco-editor';
5
+
6
+ import { setupMonacoWorkers } from '../workers/setup';
7
+ import type { UseMonacoReturn } from '../types';
8
+
9
+ /**
10
+ * Hook to load and access Monaco Editor instance
11
+ *
12
+ * Handles:
13
+ * - Dynamic import of Monaco (client-side only)
14
+ * - Web worker configuration
15
+ * - Loading state management
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * const { monaco, isLoading, error } = useMonaco();
20
+ *
21
+ * if (isLoading) return <Spinner />;
22
+ * if (error) return <Error message={error.message} />;
23
+ *
24
+ * // Use monaco API
25
+ * monaco.editor.create(...)
26
+ * ```
27
+ */
28
+ export function useMonaco(): UseMonacoReturn {
29
+ const [monaco, setMonaco] = useState<typeof MonacoEditor | null>(null);
30
+ const [isLoading, setIsLoading] = useState(true);
31
+ const [error, setError] = useState<Error | null>(null);
32
+
33
+ useEffect(() => {
34
+ let mounted = true;
35
+
36
+ async function loadMonaco() {
37
+ try {
38
+ // Setup workers first
39
+ setupMonacoWorkers();
40
+
41
+ // Dynamic import Monaco
42
+ const monacoModule = await import('monaco-editor');
43
+
44
+ if (mounted) {
45
+ setMonaco(monacoModule);
46
+ setIsLoading(false);
47
+ }
48
+ } catch (err) {
49
+ if (mounted) {
50
+ setError(err instanceof Error ? err : new Error('Failed to load Monaco Editor'));
51
+ setIsLoading(false);
52
+ }
53
+ }
54
+ }
55
+
56
+ loadMonaco();
57
+
58
+ return () => {
59
+ mounted = false;
60
+ };
61
+ }, []);
62
+
63
+ return { monaco, isLoading, error };
64
+ }
@@ -0,0 +1,16 @@
1
+ // ============================================================================
2
+ // CodeEditor - Monaco Editor Integration (~550KB)
3
+ // Code editor components with Next.js support
4
+ // ============================================================================
5
+
6
+ // Components
7
+ export * from './components';
8
+
9
+ // Hooks
10
+ export * from './hooks';
11
+
12
+ // Context
13
+ export * from './context';
14
+
15
+ // Types
16
+ export * from './types';
@@ -0,0 +1,2 @@
1
+ export { getLanguageByExtension, getLanguageByFilename, LANGUAGE_MAP } from './languages';
2
+ export { EDITOR_THEMES, getDefaultTheme } from './themes';
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Language Detection & Mapping
3
+ *
4
+ * Maps file extensions to Monaco Editor language IDs.
5
+ * Monaco supports 80+ languages out of the box.
6
+ */
7
+
8
+ /**
9
+ * File extension to Monaco language ID mapping
10
+ */
11
+ export const LANGUAGE_MAP: Record<string, string> = {
12
+ // Web
13
+ '.html': 'html',
14
+ '.htm': 'html',
15
+ '.xhtml': 'html',
16
+ '.vue': 'html',
17
+ '.svelte': 'html',
18
+
19
+ // CSS
20
+ '.css': 'css',
21
+ '.scss': 'scss',
22
+ '.sass': 'scss',
23
+ '.less': 'less',
24
+
25
+ // JavaScript/TypeScript
26
+ '.js': 'javascript',
27
+ '.mjs': 'javascript',
28
+ '.cjs': 'javascript',
29
+ '.jsx': 'javascript',
30
+ '.ts': 'typescript',
31
+ '.tsx': 'typescript',
32
+ '.mts': 'typescript',
33
+ '.cts': 'typescript',
34
+
35
+ // Data formats
36
+ '.json': 'json',
37
+ '.jsonc': 'json',
38
+ '.json5': 'json',
39
+ '.yaml': 'yaml',
40
+ '.yml': 'yaml',
41
+ '.toml': 'ini',
42
+ '.xml': 'xml',
43
+ '.svg': 'xml',
44
+ '.xsl': 'xml',
45
+ '.xsd': 'xml',
46
+
47
+ // Markdown & Documentation
48
+ '.md': 'markdown',
49
+ '.mdx': 'markdown',
50
+ '.markdown': 'markdown',
51
+ '.rst': 'restructuredtext',
52
+ '.txt': 'plaintext',
53
+ '.text': 'plaintext',
54
+
55
+ // Programming languages
56
+ '.py': 'python',
57
+ '.pyw': 'python',
58
+ '.pyi': 'python',
59
+ '.rb': 'ruby',
60
+ '.rake': 'ruby',
61
+ '.gemspec': 'ruby',
62
+ '.php': 'php',
63
+ '.phtml': 'php',
64
+ '.java': 'java',
65
+ '.kt': 'kotlin',
66
+ '.kts': 'kotlin',
67
+ '.scala': 'scala',
68
+ '.go': 'go',
69
+ '.rs': 'rust',
70
+ '.swift': 'swift',
71
+ '.c': 'c',
72
+ '.h': 'c',
73
+ '.cpp': 'cpp',
74
+ '.cc': 'cpp',
75
+ '.cxx': 'cpp',
76
+ '.hpp': 'cpp',
77
+ '.hxx': 'cpp',
78
+ '.cs': 'csharp',
79
+ '.fs': 'fsharp',
80
+ '.fsx': 'fsharp',
81
+ '.vb': 'vb',
82
+ '.lua': 'lua',
83
+ '.r': 'r',
84
+ '.R': 'r',
85
+ '.m': 'objective-c',
86
+ '.mm': 'objective-c',
87
+ '.pl': 'perl',
88
+ '.pm': 'perl',
89
+ '.ex': 'elixir',
90
+ '.exs': 'elixir',
91
+ '.erl': 'erlang',
92
+ '.hrl': 'erlang',
93
+ '.clj': 'clojure',
94
+ '.cljs': 'clojure',
95
+ '.cljc': 'clojure',
96
+ '.hs': 'haskell',
97
+ '.lhs': 'haskell',
98
+ '.ml': 'fsharp',
99
+ '.mli': 'fsharp',
100
+ '.dart': 'dart',
101
+ '.groovy': 'groovy',
102
+ '.gradle': 'groovy',
103
+ '.jl': 'julia',
104
+
105
+ // Shell & Scripts
106
+ '.sh': 'shell',
107
+ '.bash': 'shell',
108
+ '.zsh': 'shell',
109
+ '.fish': 'shell',
110
+ '.ps1': 'powershell',
111
+ '.psm1': 'powershell',
112
+ '.psd1': 'powershell',
113
+ '.bat': 'bat',
114
+ '.cmd': 'bat',
115
+
116
+ // Config files
117
+ '.ini': 'ini',
118
+ '.cfg': 'ini',
119
+ '.conf': 'ini',
120
+ '.properties': 'ini',
121
+ '.env': 'ini',
122
+ '.gitignore': 'ini',
123
+ '.gitattributes': 'ini',
124
+ '.editorconfig': 'ini',
125
+ '.npmrc': 'ini',
126
+
127
+ // Database
128
+ '.sql': 'sql',
129
+ '.mysql': 'mysql',
130
+ '.pgsql': 'pgsql',
131
+ '.plsql': 'plsql',
132
+ '.redis': 'redis',
133
+
134
+ // Templates
135
+ '.hbs': 'handlebars',
136
+ '.handlebars': 'handlebars',
137
+ '.mustache': 'handlebars',
138
+ '.ejs': 'html',
139
+ '.pug': 'pug',
140
+ '.jade': 'pug',
141
+ '.twig': 'twig',
142
+ '.liquid': 'liquid',
143
+
144
+ // GraphQL
145
+ '.graphql': 'graphql',
146
+ '.gql': 'graphql',
147
+
148
+ // Docker
149
+ '.dockerfile': 'dockerfile',
150
+
151
+ // Other
152
+ '.diff': 'diff',
153
+ '.patch': 'diff',
154
+ '.log': 'log',
155
+ '.tex': 'latex',
156
+ '.cls': 'latex',
157
+ '.sty': 'latex',
158
+ '.proto': 'protobuf',
159
+ '.sol': 'sol',
160
+ '.asm': 'mips',
161
+ '.s': 'mips',
162
+ '.wasm': 'wasm',
163
+ };
164
+
165
+ /**
166
+ * Special filename mappings (without extension)
167
+ */
168
+ const FILENAME_MAP: Record<string, string> = {
169
+ Dockerfile: 'dockerfile',
170
+ 'docker-compose.yml': 'yaml',
171
+ 'docker-compose.yaml': 'yaml',
172
+ Makefile: 'makefile',
173
+ makefile: 'makefile',
174
+ Gemfile: 'ruby',
175
+ Rakefile: 'ruby',
176
+ Jenkinsfile: 'groovy',
177
+ Vagrantfile: 'ruby',
178
+ '.bashrc': 'shell',
179
+ '.bash_profile': 'shell',
180
+ '.zshrc': 'shell',
181
+ '.profile': 'shell',
182
+ '.vimrc': 'plaintext',
183
+ '.gitconfig': 'ini',
184
+ '.htaccess': 'ini',
185
+ 'nginx.conf': 'ini',
186
+ 'package.json': 'json',
187
+ 'tsconfig.json': 'json',
188
+ 'jsconfig.json': 'json',
189
+ '.prettierrc': 'json',
190
+ '.eslintrc': 'json',
191
+ 'composer.json': 'json',
192
+ 'Cargo.toml': 'ini',
193
+ 'go.mod': 'go',
194
+ 'go.sum': 'plaintext',
195
+ 'requirements.txt': 'plaintext',
196
+ 'pyproject.toml': 'ini',
197
+ 'setup.py': 'python',
198
+ 'setup.cfg': 'ini',
199
+ };
200
+
201
+ /**
202
+ * Get Monaco language ID from file extension
203
+ */
204
+ export function getLanguageByExtension(extension: string): string {
205
+ const ext = extension.startsWith('.') ? extension.toLowerCase() : `.${extension.toLowerCase()}`;
206
+ return LANGUAGE_MAP[ext] || 'plaintext';
207
+ }
208
+
209
+ /**
210
+ * Get Monaco language ID from filename
211
+ * Checks special filenames first, then falls back to extension
212
+ */
213
+ export function getLanguageByFilename(filename: string): string {
214
+ // Check special filenames first
215
+ if (FILENAME_MAP[filename]) {
216
+ return FILENAME_MAP[filename];
217
+ }
218
+
219
+ // Extract extension
220
+ const lastDot = filename.lastIndexOf('.');
221
+ if (lastDot === -1) {
222
+ return 'plaintext';
223
+ }
224
+
225
+ const extension = filename.slice(lastDot).toLowerCase();
226
+ return LANGUAGE_MAP[extension] || 'plaintext';
227
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Editor Themes
3
+ *
4
+ * Built-in Monaco themes and custom CMDOP themes
5
+ */
6
+
7
+ export type EditorTheme = 'vs' | 'vs-dark' | 'hc-black' | 'hc-light' | 'cmdop-dark' | 'cmdop-light';
8
+
9
+ export const EDITOR_THEMES = {
10
+ // Built-in themes
11
+ 'vs': 'Light (VS Code)',
12
+ 'vs-dark': 'Dark (VS Code)',
13
+ 'hc-black': 'High Contrast Dark',
14
+ 'hc-light': 'High Contrast Light',
15
+ // Custom themes (to be defined)
16
+ 'cmdop-dark': 'CMDOP Dark',
17
+ 'cmdop-light': 'CMDOP Light',
18
+ } as const;
19
+
20
+ /**
21
+ * Get default theme based on system preference
22
+ */
23
+ export function getDefaultTheme(): EditorTheme {
24
+ if (typeof window === 'undefined') return 'vs-dark';
25
+
26
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
27
+ return prefersDark ? 'vs-dark' : 'vs';
28
+ }
29
+
30
+ /**
31
+ * CMDOP Dark Theme Definition
32
+ * Can be registered with monaco.editor.defineTheme()
33
+ */
34
+ export const cmdopDarkTheme = {
35
+ base: 'vs-dark' as const,
36
+ inherit: true,
37
+ rules: [
38
+ { token: 'comment', foreground: '6A9955', fontStyle: 'italic' },
39
+ { token: 'keyword', foreground: 'C586C0' },
40
+ { token: 'string', foreground: 'CE9178' },
41
+ { token: 'number', foreground: 'B5CEA8' },
42
+ { token: 'type', foreground: '4EC9B0' },
43
+ { token: 'function', foreground: 'DCDCAA' },
44
+ { token: 'variable', foreground: '9CDCFE' },
45
+ ],
46
+ colors: {
47
+ 'editor.background': '#1a1a1a',
48
+ 'editor.foreground': '#D4D4D4',
49
+ 'editor.lineHighlightBackground': '#2d2d2d',
50
+ 'editor.selectionBackground': '#264F78',
51
+ 'editorCursor.foreground': '#AEAFAD',
52
+ 'editorLineNumber.foreground': '#858585',
53
+ 'editorLineNumber.activeForeground': '#C6C6C6',
54
+ },
55
+ };
56
+
57
+ /**
58
+ * CMDOP Light Theme Definition
59
+ */
60
+ export const cmdopLightTheme = {
61
+ base: 'vs' as const,
62
+ inherit: true,
63
+ rules: [
64
+ { token: 'comment', foreground: '008000', fontStyle: 'italic' },
65
+ { token: 'keyword', foreground: 'AF00DB' },
66
+ { token: 'string', foreground: 'A31515' },
67
+ { token: 'number', foreground: '098658' },
68
+ { token: 'type', foreground: '267F99' },
69
+ { token: 'function', foreground: '795E26' },
70
+ { token: 'variable', foreground: '001080' },
71
+ ],
72
+ colors: {
73
+ 'editor.background': '#FFFFFF',
74
+ 'editor.foreground': '#000000',
75
+ 'editor.lineHighlightBackground': '#F5F5F5',
76
+ 'editor.selectionBackground': '#ADD6FF',
77
+ },
78
+ };
@@ -0,0 +1,130 @@
1
+ import type * as monaco from 'monaco-editor';
2
+
3
+ // ============================================================================
4
+ // Editor Types
5
+ // ============================================================================
6
+
7
+ export interface EditorFile {
8
+ /** Unique file path */
9
+ path: string;
10
+ /** File content */
11
+ content: string;
12
+ /** Detected or specified language */
13
+ language: string;
14
+ /** Whether file has unsaved changes */
15
+ isDirty: boolean;
16
+ /** Monaco model reference */
17
+ model?: monaco.editor.ITextModel;
18
+ }
19
+
20
+ export interface EditorOptions {
21
+ /** Editor theme: 'vs' | 'vs-dark' | 'hc-black' | custom */
22
+ theme?: string;
23
+ /** Font size in pixels */
24
+ fontSize?: number;
25
+ /** Font family */
26
+ fontFamily?: string;
27
+ /** Tab size */
28
+ tabSize?: number;
29
+ /** Insert spaces instead of tabs */
30
+ insertSpaces?: boolean;
31
+ /** Word wrap mode */
32
+ wordWrap?: 'off' | 'on' | 'wordWrapColumn' | 'bounded';
33
+ /** Show minimap */
34
+ minimap?: boolean;
35
+ /** Show line numbers */
36
+ lineNumbers?: 'on' | 'off' | 'relative' | 'interval';
37
+ /** Read-only mode */
38
+ readOnly?: boolean;
39
+ }
40
+
41
+ export interface EditorProps {
42
+ /** File content */
43
+ value?: string;
44
+ /** Programming language for syntax highlighting */
45
+ language?: string;
46
+ /** Called when content changes */
47
+ onChange?: (value: string) => void;
48
+ /** Called when editor is mounted */
49
+ onMount?: (editor: monaco.editor.IStandaloneCodeEditor) => void;
50
+ /** Editor options */
51
+ options?: EditorOptions;
52
+ /** CSS class name */
53
+ className?: string;
54
+ /** Height — fixed px, CSS string, or '100%' (default: '100%'). Ignored when autoHeight=true */
55
+ height?: string | number;
56
+ /** Width (default: '100%') */
57
+ width?: string | number;
58
+ /** Auto-resize height to fit content. Editor grows/shrinks with content lines */
59
+ autoHeight?: boolean;
60
+ /** Min height in px when autoHeight is enabled (default: 100) */
61
+ minHeight?: number;
62
+ /** Max height in px when autoHeight is enabled (default: 600) */
63
+ maxHeight?: number;
64
+ }
65
+
66
+ export interface DiffEditorProps {
67
+ /** Original content (left side) */
68
+ original: string;
69
+ /** Modified content (right side) */
70
+ modified: string;
71
+ /** Programming language */
72
+ language?: string;
73
+ /** Editor options */
74
+ options?: EditorOptions;
75
+ /** CSS class name */
76
+ className?: string;
77
+ /** Height (default: '100%') */
78
+ height?: string | number;
79
+ }
80
+
81
+ // ============================================================================
82
+ // Context Types
83
+ // ============================================================================
84
+
85
+ export interface EditorContextValue {
86
+ /** Currently open files */
87
+ openFiles: EditorFile[];
88
+ /** Currently active file */
89
+ activeFile: EditorFile | null;
90
+ /** Monaco instance (null during SSR) */
91
+ monaco: typeof monaco | null;
92
+ /** Editor instance */
93
+ editor: monaco.editor.IStandaloneCodeEditor | null;
94
+ /** Whether editor is ready */
95
+ isReady: boolean;
96
+
97
+ // File operations
98
+ openFile: (path: string, content: string, language?: string) => void;
99
+ closeFile: (path: string) => void;
100
+ setActiveFile: (path: string) => void;
101
+ updateContent: (path: string, content: string) => void;
102
+ saveFile: (path: string) => Promise<void>;
103
+
104
+ // State queries
105
+ isDirty: (path: string) => boolean;
106
+ getContent: (path: string) => string | null;
107
+ getFile: (path: string) => EditorFile | null;
108
+ }
109
+
110
+ // ============================================================================
111
+ // Hook Return Types
112
+ // ============================================================================
113
+
114
+ export interface UseEditorReturn {
115
+ /** Editor instance */
116
+ editor: monaco.editor.IStandaloneCodeEditor | null;
117
+ /** Whether editor is mounted and ready */
118
+ isReady: boolean;
119
+ /** Set editor reference */
120
+ setEditor: (editor: monaco.editor.IStandaloneCodeEditor | null) => void;
121
+ }
122
+
123
+ export interface UseMonacoReturn {
124
+ /** Monaco namespace */
125
+ monaco: typeof monaco | null;
126
+ /** Whether Monaco is loaded */
127
+ isLoading: boolean;
128
+ /** Loading error if any */
129
+ error: Error | null;
130
+ }
@@ -0,0 +1 @@
1
+ export { setupMonacoWorkers } from './setup';
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Monaco Editor Web Worker Setup
5
+ *
6
+ * Workers improve performance for language services (TypeScript type-checking,
7
+ * JSON validation, etc.) but require bundler-specific configuration:
8
+ *
9
+ * **Vite**: Use `?worker` imports (see example below)
10
+ * **Next.js/Webpack**: Use `monaco-editor-webpack-plugin`
11
+ * **No config**: Monaco falls back to main-thread execution (fully functional)
12
+ *
13
+ * @example Vite setup (call once in app entry):
14
+ * ```ts
15
+ * import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
16
+ * import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
17
+ * import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
18
+ * import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
19
+ * import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
20
+ *
21
+ * setupMonacoWorkers((label) => {
22
+ * switch (label) {
23
+ * case 'typescript': case 'javascript': return new tsWorker()
24
+ * case 'json': return new jsonWorker()
25
+ * case 'css': case 'scss': case 'less': return new cssWorker()
26
+ * case 'html': case 'handlebars': case 'razor': return new htmlWorker()
27
+ * default: return new editorWorker()
28
+ * }
29
+ * })
30
+ * ```
31
+ *
32
+ * @example Next.js setup:
33
+ * ```ts
34
+ * // next.config.js — add monaco-editor-webpack-plugin
35
+ * // Then just call setupMonacoWorkers() without arguments
36
+ * setupMonacoWorkers()
37
+ * ```
38
+ */
39
+
40
+ type GetWorkerFn = (label: string) => Worker;
41
+
42
+ let isSetup = false;
43
+
44
+ export function setupMonacoWorkers(getWorker?: GetWorkerFn): void {
45
+ if (isSetup || typeof window === 'undefined') return;
46
+
47
+ if (getWorker) {
48
+ // App provides bundler-specific worker factory
49
+ (self as unknown as { MonacoEnvironment: object }).MonacoEnvironment = {
50
+ getWorker: (_workerId: string, label: string) => getWorker(label),
51
+ };
52
+ }
53
+ // else: don't set MonacoEnvironment at all.
54
+ // - If webpack plugin is installed, it sets MonacoEnvironment automatically.
55
+ // - If nothing sets it, Monaco falls back to main-thread execution.
56
+
57
+ isSetup = true;
58
+ }
@@ -197,6 +197,31 @@ export type {
197
197
  CronSchedulerContextValue,
198
198
  } from './CronScheduler';
199
199
 
200
+ // Export CodeEditor (Monaco ~550KB)
201
+ export {
202
+ Editor,
203
+ DiffEditor,
204
+ } from './CodeEditor/components';
205
+ export type { EditorRef } from './CodeEditor/components/Editor';
206
+ export {
207
+ EditorProvider,
208
+ useEditorContext,
209
+ } from './CodeEditor/context';
210
+ export {
211
+ useMonaco,
212
+ useEditor,
213
+ useLanguage,
214
+ } from './CodeEditor/hooks';
215
+ export type {
216
+ EditorFile,
217
+ EditorOptions,
218
+ EditorProps,
219
+ DiffEditorProps,
220
+ EditorContextValue,
221
+ UseEditorReturn,
222
+ UseMonacoReturn,
223
+ } from './CodeEditor/types';
224
+
200
225
  // Export Media Cache Store
201
226
  export {
202
227
  useMediaCacheStore,