@djangocfg/widget-code 0.1.1

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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/package.json +119 -0
  4. package/src/diff-viewer/DiffViewer.tsx +168 -0
  5. package/src/diff-viewer/README.md +33 -0
  6. package/src/diff-viewer/components/CopyButton.tsx +49 -0
  7. package/src/diff-viewer/components/DiffLineContent.tsx +48 -0
  8. package/src/diff-viewer/components/SplitView.tsx +270 -0
  9. package/src/diff-viewer/components/UnifiedView.tsx +189 -0
  10. package/src/diff-viewer/diff-viewer.stories.tsx +168 -0
  11. package/src/diff-viewer/hooks/useDiff.ts +47 -0
  12. package/src/diff-viewer/hooks/useHighlighter.ts +54 -0
  13. package/src/diff-viewer/index.ts +22 -0
  14. package/src/diff-viewer/lazy.tsx +25 -0
  15. package/src/diff-viewer/types.ts +125 -0
  16. package/src/diff-viewer/utils/computeDiff.ts +171 -0
  17. package/src/editor/README.md +187 -0
  18. package/src/editor/components/DiffEditor.tsx +158 -0
  19. package/src/editor/components/Editor.tsx +293 -0
  20. package/src/editor/components/index.ts +2 -0
  21. package/src/editor/context/EditorProvider.tsx +212 -0
  22. package/src/editor/context/index.ts +1 -0
  23. package/src/editor/editor.stories.tsx +397 -0
  24. package/src/editor/hooks/index.ts +4 -0
  25. package/src/editor/hooks/useEditor.ts +36 -0
  26. package/src/editor/hooks/useEditorTheme.ts +91 -0
  27. package/src/editor/hooks/useLanguage.ts +29 -0
  28. package/src/editor/hooks/useMonaco.ts +79 -0
  29. package/src/editor/index.ts +16 -0
  30. package/src/editor/lazy.tsx +76 -0
  31. package/src/editor/lib/index.ts +2 -0
  32. package/src/editor/lib/languages.ts +227 -0
  33. package/src/editor/lib/themes.ts +42 -0
  34. package/src/editor/types/index.ts +137 -0
  35. package/src/editor/workers/index.ts +1 -0
  36. package/src/editor/workers/setup.ts +58 -0
  37. package/src/index.ts +10 -0
  38. package/src/json-editor/JsonEditor.tsx +115 -0
  39. package/src/json-editor/index.ts +1 -0
  40. package/src/json-editor/lazy.tsx +24 -0
  41. package/src/markdown-message/ActionRow.tsx +53 -0
  42. package/src/markdown-message/ChatMessageRow.tsx +103 -0
  43. package/src/markdown-message/CodeBlock.tsx +137 -0
  44. package/src/markdown-message/CollapseToggle.tsx +69 -0
  45. package/src/markdown-message/MarkdownMessage.tsx +376 -0
  46. package/src/markdown-message/README.md +221 -0
  47. package/src/markdown-message/builtinRules.tsx +92 -0
  48. package/src/markdown-message/components.tsx +224 -0
  49. package/src/markdown-message/index.ts +22 -0
  50. package/src/markdown-message/linkRules.ts +83 -0
  51. package/src/markdown-message/plainText.ts +83 -0
  52. package/src/markdown-message/sanitize.ts +88 -0
  53. package/src/markdown-message/streamingBlocks.ts +168 -0
  54. package/src/markdown-message/types.ts +168 -0
  55. package/src/panel-header/CodePanelHeader.tsx +121 -0
  56. package/src/panel-header/index.ts +1 -0
  57. package/src/pretty-code/PrettyCode.client.tsx +402 -0
  58. package/src/pretty-code/README.md +81 -0
  59. package/src/pretty-code/index.tsx +72 -0
  60. package/src/pretty-code/lazy.tsx +69 -0
  61. package/src/pretty-code/pretty-code.stories.tsx +353 -0
  62. package/src/pretty-code/registerPrismLanguages.ts +116 -0
  63. package/src/role-tokens.ts +39 -0
  64. package/src/use-collapsible-content.ts +236 -0
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * `@djangocfg/widget-code/editor` subpath entrypoint.
5
+ *
6
+ * Monaco itself weighs ~550 KB minified — we must never pay that cost
7
+ * unless the editor actually mounts. The `Lazy*` wrappers here dynamically
8
+ * import the `Editor` and `DiffEditor` components, which transitively pull
9
+ * in `monaco-editor` and the worker setup.
10
+ *
11
+ * The rest of the surface (types, EditorProvider, useMonaco hook, helpers)
12
+ * is light:
13
+ * - `monaco-editor` is referenced only as a TS type (`type *`) — erased
14
+ * at compile time.
15
+ * - `useMonaco` performs a dynamic `import('monaco-editor')` itself, so
16
+ * importing the hook is free until the caller actually invokes it.
17
+ *
18
+ * That means consumers can wire up keyboard shortcuts, theme toggles, or
19
+ * file-state hooks at the top of their tree without paying Monaco's cost,
20
+ * and only render `<LazyEditor>` lower in the tree where it's needed.
21
+ */
22
+
23
+ import { createLazyComponent, LoadingFallback } from '@djangocfg/widget-kit/lazy';
24
+ import type { EditorProps, DiffEditorProps } from './types';
25
+
26
+ // ============================================================================
27
+ // Lazy components
28
+ // ============================================================================
29
+
30
+ export const LazyEditor = createLazyComponent<EditorProps>(
31
+ () => import('./components/Editor').then((m) => ({ default: m.Editor })),
32
+ {
33
+ displayName: 'LazyEditor',
34
+ fallback: <LoadingFallback minHeight={320} text="Loading editor…" />,
35
+ },
36
+ );
37
+
38
+ export const LazyDiffEditor = createLazyComponent<DiffEditorProps>(
39
+ () => import('./components/DiffEditor').then((m) => ({ default: m.DiffEditor })),
40
+ {
41
+ displayName: 'LazyDiffEditor',
42
+ fallback: <LoadingFallback minHeight={320} text="Loading diff editor…" />,
43
+ },
44
+ );
45
+
46
+ // `Editor` / `DiffEditor` are the plain named exports — same lazy components,
47
+ // kept so callers can `import { Editor } from '@djangocfg/widget-code/editor'`
48
+ // without knowing about the `Lazy*` naming.
49
+ export { LazyEditor as Editor };
50
+ export { LazyDiffEditor as DiffEditor };
51
+
52
+ // ============================================================================
53
+ // Light surface
54
+ // ============================================================================
55
+
56
+ // Hooks — `useMonaco` does its own dynamic import; the others are pure.
57
+ export {
58
+ useMonaco,
59
+ useEditor,
60
+ useLanguage,
61
+ useEditorTheme,
62
+ } from './hooks';
63
+
64
+ // Provider + context hook
65
+ export { EditorProvider, useEditorContext } from './context';
66
+
67
+ // All types
68
+ export type {
69
+ EditorFile,
70
+ EditorOptions,
71
+ EditorProps,
72
+ EditorContextValue,
73
+ UseEditorReturn,
74
+ UseMonacoReturn,
75
+ DiffEditorProps,
76
+ } from './types';
@@ -0,0 +1,2 @@
1
+ export { getLanguageByExtension, getLanguageByFilename, LANGUAGE_MAP } from './languages';
2
+ export { EDITOR_THEMES } 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,42 @@
1
+ /**
2
+ * Editor Themes
3
+ *
4
+ * The editor is always dark (devtool-style), so only dark themes are exposed.
5
+ */
6
+
7
+ export type EditorTheme = 'vs-dark' | 'hc-black' | 'cmdop-dark';
8
+
9
+ export const EDITOR_THEMES = {
10
+ // Built-in themes
11
+ 'vs-dark': 'Dark (VS Code)',
12
+ 'hc-black': 'High Contrast Dark',
13
+ // Custom themes
14
+ 'cmdop-dark': 'CMDOP Dark',
15
+ } as const;
16
+
17
+ /**
18
+ * CMDOP Dark Theme Definition
19
+ * Can be registered with monaco.editor.defineTheme()
20
+ */
21
+ export const cmdopDarkTheme = {
22
+ base: 'vs-dark' as const,
23
+ inherit: true,
24
+ rules: [
25
+ { token: 'comment', foreground: '6A9955', fontStyle: 'italic' },
26
+ { token: 'keyword', foreground: 'C586C0' },
27
+ { token: 'string', foreground: 'CE9178' },
28
+ { token: 'number', foreground: 'B5CEA8' },
29
+ { token: 'type', foreground: '4EC9B0' },
30
+ { token: 'function', foreground: 'DCDCAA' },
31
+ { token: 'variable', foreground: '9CDCFE' },
32
+ ],
33
+ colors: {
34
+ 'editor.background': '#1a1a1a',
35
+ 'editor.foreground': '#D4D4D4',
36
+ 'editor.lineHighlightBackground': '#2d2d2d',
37
+ 'editor.selectionBackground': '#264F78',
38
+ 'editorCursor.foreground': '#AEAFAD',
39
+ 'editorLineNumber.foreground': '#858585',
40
+ 'editorLineNumber.activeForeground': '#C6C6C6',
41
+ },
42
+ };
@@ -0,0 +1,137 @@
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
+ /** Focus the editor once Monaco mounts. Pair with `key={path}` upstream
65
+ * for per-file focus reset. */
66
+ autoFocus?: boolean;
67
+ /** Bound to Cmd/Ctrl+S inside the editor via Monaco's command palette.
68
+ * Receives the current value. The browser default is suppressed by
69
+ * Monaco when a command is registered for that chord. */
70
+ onSave?: (value: string) => void;
71
+ }
72
+
73
+ export interface DiffEditorProps {
74
+ /** Original content (left side) */
75
+ original: string;
76
+ /** Modified content (right side) */
77
+ modified: string;
78
+ /** Programming language */
79
+ language?: string;
80
+ /** Editor options */
81
+ options?: EditorOptions;
82
+ /** CSS class name */
83
+ className?: string;
84
+ /** Height (default: '100%') */
85
+ height?: string | number;
86
+ }
87
+
88
+ // ============================================================================
89
+ // Context Types
90
+ // ============================================================================
91
+
92
+ export interface EditorContextValue {
93
+ /** Currently open files */
94
+ openFiles: EditorFile[];
95
+ /** Currently active file */
96
+ activeFile: EditorFile | null;
97
+ /** Monaco instance (null during SSR) */
98
+ monaco: typeof monaco | null;
99
+ /** Editor instance */
100
+ editor: monaco.editor.IStandaloneCodeEditor | null;
101
+ /** Whether editor is ready */
102
+ isReady: boolean;
103
+
104
+ // File operations
105
+ openFile: (path: string, content: string, language?: string) => void;
106
+ closeFile: (path: string) => void;
107
+ setActiveFile: (path: string) => void;
108
+ updateContent: (path: string, content: string) => void;
109
+ saveFile: (path: string) => Promise<void>;
110
+
111
+ // State queries
112
+ isDirty: (path: string) => boolean;
113
+ getContent: (path: string) => string | null;
114
+ getFile: (path: string) => EditorFile | null;
115
+ }
116
+
117
+ // ============================================================================
118
+ // Hook Return Types
119
+ // ============================================================================
120
+
121
+ export interface UseEditorReturn {
122
+ /** Editor instance */
123
+ editor: monaco.editor.IStandaloneCodeEditor | null;
124
+ /** Whether editor is mounted and ready */
125
+ isReady: boolean;
126
+ /** Set editor reference */
127
+ setEditor: (editor: monaco.editor.IStandaloneCodeEditor | null) => void;
128
+ }
129
+
130
+ export interface UseMonacoReturn {
131
+ /** Monaco namespace */
132
+ monaco: typeof monaco | null;
133
+ /** Whether Monaco is loaded */
134
+ isLoading: boolean;
135
+ /** Loading error if any */
136
+ error: Error | null;
137
+ }
@@ -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
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Code surfaces: an editor, a diff, a highlighted listing, a JSON editor and
3
+ * the markdown renderer that embeds them.
4
+ *
5
+ * No root barrel worth importing — Monaco alone is ~550 KB and `json-edit-react`
6
+ * and Prism sit behind their own subpaths. Import the one you want:
7
+ * `@djangocfg/widget-code/editor`, `/pretty-code`, `/diff-viewer`,
8
+ * `/json-editor`, `/markdown-message`.
9
+ */
10
+ export { ANCHOR, TOGGLE } from './role-tokens';
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * JsonEditor — editable JSON tree.
5
+ *
6
+ * Thin wrapper over `json-edit-react` (MIT, CarlosNZ). The library
7
+ * handles the hard parts (inline editing, drag-reorder, JSON-Schema
8
+ * validation, undo/redo, keyboard nav, copy-to-clipboard) — we apply
9
+ * our theme tokens and a sane default surface, nothing more.
10
+ *
11
+ * When to use:
12
+ * - User is *editing* JSON live (debug Store panel, config UI).
13
+ * - You need schema validation or controlled mutations with
14
+ * `onUpdate`.
15
+ *
16
+ * When NOT to use:
17
+ * - Read-only display → use `@djangocfg/widget-data/json-tree` (≈ 10×
18
+ * lighter bundle, search + copy-path included).
19
+ */
20
+
21
+ import { JsonEditor as RawEditor, type JsonEditorProps as RawProps } from 'json-edit-react';
22
+ import { useMemo } from 'react';
23
+ import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
24
+ import { cn } from '@djangocfg/ui-core/lib';
25
+
26
+ export interface JsonEditorProps extends Omit<RawProps, 'theme'> {
27
+ /** Extra class on the root container. */
28
+ className?: string;
29
+ /**
30
+ * Hide chrome the host already provides. When `false`, no border /
31
+ * background — the editor sits flush in the surrounding pane.
32
+ * Default `true`.
33
+ */
34
+ bordered?: boolean;
35
+ }
36
+
37
+ /**
38
+ * Hand-rolled theme bridge: maps our semantic CSS vars onto
39
+ * json-edit-react's theme slots. Light/dark resolution lives in our
40
+ * `useResolvedTheme`; the library only sees concrete colours.
41
+ *
42
+ * We keep this minimal — most slots inherit `currentColor`, only the
43
+ * data-typed tokens get explicit Tailwind palette values so the
44
+ * keys/strings/numbers stay legible on every host surface.
45
+ */
46
+ function useEditorTheme(): RawProps['theme'] {
47
+ const resolved = useResolvedTheme();
48
+ return useMemo(
49
+ () => ({
50
+ displayName: 'djangocfg',
51
+ styles: {
52
+ container: {
53
+ backgroundColor: 'transparent',
54
+ fontFamily:
55
+ "'SF Mono', ui-monospace, 'JetBrains Mono', Menlo, Consolas, monospace",
56
+ fontSize: '12px',
57
+ },
58
+ property:
59
+ resolved === 'dark' ? 'rgb(167 139 250)' : 'rgb(124 58 237)', // violet-400 / violet-600
60
+ bracket:
61
+ resolved === 'dark' ? 'rgb(148 163 184)' : 'rgb(100 116 139)', // slate-400 / slate-500
62
+ itemCount:
63
+ resolved === 'dark' ? 'rgb(100 116 139)' : 'rgb(148 163 184)',
64
+ string:
65
+ resolved === 'dark' ? 'rgb(52 211 153)' : 'rgb(5 150 105)', // emerald-400 / emerald-600
66
+ number:
67
+ resolved === 'dark' ? 'rgb(56 189 248)' : 'rgb(2 132 199)', // sky-400 / sky-600
68
+ boolean:
69
+ resolved === 'dark' ? 'rgb(251 191 36)' : 'rgb(217 119 6)', // amber-400 / amber-600
70
+ null: {
71
+ color: resolved === 'dark' ? 'rgb(100 116 139)' : 'rgb(148 163 184)',
72
+ fontStyle: 'italic',
73
+ },
74
+ input: {
75
+ backgroundColor:
76
+ resolved === 'dark' ? 'rgb(30 41 59)' : 'rgb(241 245 249)',
77
+ color: resolved === 'dark' ? 'white' : 'rgb(15 23 42)',
78
+ borderRadius: 4,
79
+ padding: '2px 6px',
80
+ },
81
+ inputHighlight:
82
+ resolved === 'dark' ? 'rgba(96, 165, 250, 0.25)' : 'rgba(59, 130, 246, 0.15)',
83
+ iconCollection:
84
+ resolved === 'dark' ? 'rgb(148 163 184)' : 'rgb(100 116 139)',
85
+ iconEdit: 'currentColor',
86
+ iconDelete: 'rgb(239 68 68)',
87
+ iconAdd: 'rgb(34 197 94)',
88
+ iconCopy: 'currentColor',
89
+ iconOk: 'rgb(34 197 94)',
90
+ iconCancel: 'rgb(239 68 68)',
91
+ },
92
+ }),
93
+ [resolved],
94
+ );
95
+ }
96
+
97
+ export function JsonEditor({
98
+ className,
99
+ bordered = true,
100
+ ...props
101
+ }: JsonEditorProps) {
102
+ const theme = useEditorTheme();
103
+ return (
104
+ <div
105
+ className={cn(
106
+ 'json-editor-host w-full',
107
+ bordered &&
108
+ 'overflow-hidden rounded-md border border-border/60 bg-card px-3 py-2',
109
+ className,
110
+ )}
111
+ >
112
+ <RawEditor theme={theme} {...props} />
113
+ </div>
114
+ );
115
+ }
@@ -0,0 +1 @@
1
+ export * from './lazy';