@actualwave/react-native-codeditor 1.1.0 → 1.2.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 (63) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +67 -7
  3. package/SKILL.md +504 -0
  4. package/THIRD_PARTY_NOTICES +120 -0
  5. package/app.plugin.js +1 -1
  6. package/lib/module/CodeEditor.js +69 -2
  7. package/lib/typescript/src/CodeEditor.d.ts +20 -2
  8. package/lib/typescript/src/EditorAPI.d.ts +42 -1
  9. package/lib/typescript/src/WebViewAPI.d.ts +4 -1
  10. package/lib/typescript/src/index.d.ts +1 -1
  11. package/package.json +12 -4
  12. package/src/assets/codemirror/@actualwave_codemirror-lang-embed-core.js +39 -0
  13. package/src/assets/codemirror/@actualwave_codemirror-lang-embed-css.js +19 -0
  14. package/src/assets/codemirror/@actualwave_codemirror-lang-embed-glsl.js +1 -0
  15. package/src/assets/codemirror/@actualwave_codemirror-lang-embed-graphql.js +50 -0
  16. package/src/assets/codemirror/@actualwave_codemirror-lang-embed-icu-messageformat.js +1 -0
  17. package/src/assets/codemirror/@actualwave_codemirror-lang-embed-react-native.js +6 -0
  18. package/src/assets/codemirror/@actualwave_codemirror-lang-embed-sksl.js +7 -0
  19. package/src/assets/codemirror/@actualwave_codemirror-lang-embed-sql.js +5 -0
  20. package/src/assets/codemirror/@actualwave_codemirror-lang-embed-tailwind.js +63 -0
  21. package/src/assets/codemirror/@actualwave_codemirror-lang-glsl.js +20 -0
  22. package/src/assets/codemirror/@actualwave_codemirror-lang-icu-messageformat.js +11 -0
  23. package/src/assets/codemirror/@actualwave_codemirror-lang-js-completion.js +72 -0
  24. package/src/assets/codemirror/@actualwave_codemirror-lang-react-native.js +71 -0
  25. package/src/assets/codemirror/@actualwave_codemirror-lang-sksl.js +7 -2
  26. package/src/assets/codemirror/@codemirror_autocomplete.js +9 -9
  27. package/src/assets/codemirror/@codemirror_commands.js +86 -86
  28. package/src/assets/codemirror/@codemirror_lang-css.js +2 -2
  29. package/src/assets/codemirror/@codemirror_lang-go.js +1 -1
  30. package/src/assets/codemirror/@codemirror_lang-html.js +1 -1
  31. package/src/assets/codemirror/@codemirror_lang-javascript.js +4 -4
  32. package/src/assets/codemirror/@codemirror_lang-jinja.js +1 -1
  33. package/src/assets/codemirror/@codemirror_lang-json.js +1 -1
  34. package/src/assets/codemirror/@codemirror_lang-less.js +1 -1
  35. package/src/assets/codemirror/@codemirror_lang-liquid.js +1 -1
  36. package/src/assets/codemirror/@codemirror_lang-markdown.js +2 -2
  37. package/src/assets/codemirror/@codemirror_lang-python.js +1 -1
  38. package/src/assets/codemirror/@codemirror_lang-sql.js +4 -4
  39. package/src/assets/codemirror/@codemirror_lang-xml.js +1 -1
  40. package/src/assets/codemirror/@codemirror_language.js +11 -11
  41. package/src/assets/codemirror/@codemirror_legacy-modes_mode_clike.js +1 -1
  42. package/src/assets/codemirror/@codemirror_legacy-modes_mode_mathematica.js +1 -1
  43. package/src/assets/codemirror/@codemirror_legacy-modes_mode_oz.js +1 -1
  44. package/src/assets/codemirror/@codemirror_legacy-modes_mode_verilog.js +1 -1
  45. package/src/assets/codemirror/@codemirror_lint.js +5 -5
  46. package/src/assets/codemirror/@codemirror_merge.js +4 -4
  47. package/src/assets/codemirror/@codemirror_search.js +8 -8
  48. package/src/assets/codemirror/@codemirror_state.js +1 -1
  49. package/src/assets/codemirror/@codemirror_theme-one-dark.js +3 -3
  50. package/src/assets/codemirror/@codemirror_view.js +6 -6
  51. package/src/assets/codemirror/@lezer_common.js +2 -2
  52. package/src/assets/codemirror/@lezer_css.js +1 -1
  53. package/src/assets/codemirror/@lezer_javascript.js +1 -1
  54. package/src/assets/codemirror/@lezer_lr.js +1 -1
  55. package/src/assets/codemirror/@lezer_markdown.js +4 -4
  56. package/src/assets/codemirror/@lezer_sass.js +1 -1
  57. package/src/assets/codemirror/_core.js +129 -129
  58. package/src/assets/codemirror-editor.umd.js +130 -17
  59. package/src/assets/editor.html +37 -2
  60. package/lib/typescript/app.plugin.d.ts +0 -3
  61. package/lib/typescript/babel.config.d.ts +0 -10
  62. package/lib/typescript/react-native.config.d.ts +0 -2
  63. package/lib/typescript/scripts/copy-assets.d.ts +0 -2
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ ## 1.2.1
4
+
5
+ ### Changed
6
+ - Updated bundled `@actualwave/codemirror-package` assets to 0.1.5, which adds the
7
+ `@actualwave/codemirror-lang-js-completion` language package for JavaScript completion.
8
+ - `js-completion` is added to the example app's `supportExtensions`.
9
+ - Static completions set via `setCompletions()` are now registered as language data instead of
10
+ `autocompletion({ override })`, so they merge with the language's own completion sources
11
+ (including embedded-language ones such as `sql`/`sksl` tagged templates, Tailwind, React Native)
12
+ and are suppressed inside embedded languages.
13
+
14
+ ## 1.2.0
15
+
16
+ ### Added
17
+ - `languages` prop on `<CodeEditor>` and `api.editor.registerLanguage(name, packageName)` for
18
+ language packages that don't follow the `@codemirror/lang-{name}` naming convention
19
+ (e.g. `{ sksl: '@actualwave/codemirror-lang-sksl' }`). The prop registers before the initial
20
+ `language` is resolved.
21
+ - `languageConfig` prop and `setLanguage(name, config?, embeds?)` — options passed to the
22
+ language factory (e.g. `{ jsx: true, typescript: true }`).
23
+ - `embeds` prop, `api.editor.registerTaggedTemplate()` and `api.editor.registerSupportExtension()`
24
+ for tagged-template DSL embeddings and support extensions in `javascript`.
25
+ - `CompletionItem` and `EmbedsConfig` types are now exported.
26
+ - `THIRD_PARTY_NOTICES`, `SKILL.md` and `CHANGELOG.md` are included in the package.
27
+
28
+ ### Changed
29
+ - Updated bundled `@actualwave/codemirror-package` assets to 0.1.4.
30
+ - `sksl` (and other `@actualwave/codemirror-lang-*` packages) are no longer pre-registered by the
31
+ bundled codemirror-package (it was an undocumented built-in in 1.1.0). Pass
32
+ `languages={{ sksl: '@actualwave/codemirror-lang-sksl' }}` or call `api.editor.registerLanguage()`
33
+ before using them; otherwise `setLanguage` rejects with "No language support found".
34
+ - `lib/typescript` no longer contains declaration files for repo config files.
35
+
36
+ ### Fixed
37
+ - Documentation and example for the iOS `editorUri` used `FileSystem.bundleDirectory`, which is not
38
+ available from the default `expo-file-system` entry point in Expo SDK 55. Use
39
+ `new File(Paths.bundle, 'assets/codeditor/editor.html').uri`.
package/README.md CHANGED
@@ -144,16 +144,16 @@ npm install expo-file-system
144
144
 
145
145
  ```tsx
146
146
  import { Platform } from 'react-native';
147
- import * as FileSystem from 'expo-file-system';
147
+ import { File, Paths } from 'expo-file-system';
148
148
 
149
149
  const IOS_EDITOR_URI = Platform.OS === 'ios'
150
- ? (FileSystem.bundleDirectory ?? '') + 'assets/codeditor/editor.html'
150
+ ? new File(Paths.bundle, 'assets/codeditor/editor.html').uri
151
151
  : undefined;
152
152
 
153
153
  <CodeEditor editorUri={IOS_EDITOR_URI} ... />
154
154
  ```
155
155
 
156
- `FileSystem.bundleDirectory` returns the `file://` path to the `.app` folder (e.g.
156
+ `Paths.bundle` is the `.app` folder in the bundle (e.g.
157
157
  `file:///var/containers/Bundle/Application/<UUID>/MyApp.app/`), which is stable on both
158
158
  simulators and real devices regardless of where iOS installed the app.
159
159
 
@@ -224,6 +224,9 @@ export default function EditorScreen() {
224
224
  |---|---|---|---|
225
225
  | `content` | `string` | `''` | Initial document content (see [Content model](#content-model) below). |
226
226
  | `language` | `string` | `undefined` | Syntax language, e.g. `'javascript'`, `'python'`, `'sql'`. The language module is loaded from the bundled assets on first use. |
227
+ | `languageConfig` | `Record<string, unknown>` | `undefined` | Options passed to the language factory, e.g. `{ jsx: true, typescript: true }` for `'javascript'`. Must be plain serializable data. |
228
+ | `embeds` | `EmbedsConfig` | `undefined` | DSLs embedded in `'javascript'` — highlighting and suggestions for tagged templates (`sql`, `gql`, `css`…) and support extensions (Tailwind, React Native). See [Embedded DSLs](#embedded-dsls-in-javascript). Changing it re-resolves the current language. |
229
+ | `languages` | `Record<string, string>` | `undefined` | Custom language packages as `{ name: packageName }`, for languages that don't follow the `@codemirror/lang-{name}` convention, e.g. `{ sksl: '@actualwave/codemirror-lang-sksl' }`. Registered before the initial `language` is resolved. The package must be bundled in the WebView assets. |
227
230
  | `extensions` | `ExtensionSpec[]` | `[]` | Additional CodeMirror 6 extension specs. See [Extension specs](#extension-specs). |
228
231
  | `theme` | `string` | `undefined` | Theme name. See [Themes](#themes). |
229
232
  | `viewport` | `ViewportSettings` | `undefined` | Controls the `<meta name="viewport">` tag inside the WebView. |
@@ -310,9 +313,23 @@ api.editor.resetValue(value?: string): Promise<void>
310
313
  ### Language, extensions, theme
311
314
 
312
315
  ```ts
313
- api.editor.setLanguage(name: string): Promise<void>
316
+ api.editor.setLanguage(name: string, config?: object, embeds?: EmbedsConfig): Promise<void>
314
317
  // Switches syntax language. Loads the language module on demand.
318
+ // `config` goes to the language factory; `embeds` applies to 'javascript' only.
315
319
  // e.g. await api.editor.setLanguage('python')
320
+ // await api.editor.setLanguage('javascript', { jsx: true }, { taggedTemplates: [...] })
321
+
322
+ api.editor.registerTaggedTemplate(packageName: string): Promise<void>
323
+ api.editor.registerSupportExtension(packageName: string, config?: object): Promise<void>
324
+ // Register an embed globally for the page. Call before the language is set to
325
+ // 'javascript', or follow with setLanguage('javascript', ...) to re-resolve.
326
+ // Alternative to the `embeds` prop — see "Embedded DSLs".
327
+
328
+ api.editor.registerLanguage(name: string, packageName: string): Promise<void>
329
+ // Maps a language name to a package that doesn't follow @codemirror/lang-{name}.
330
+ // Takes effect on the next setLanguage(). Alternative to the `languages` prop.
331
+ // e.g. await api.editor.registerLanguage('sksl', '@actualwave/codemirror-lang-sksl');
332
+ // await api.editor.setLanguage('sksl')
316
333
 
317
334
  api.editor.setExtensions(specs: ExtensionSpec[]): Promise<void>
318
335
  // Replaces the active extension set.
@@ -451,9 +468,9 @@ api.editor.unregisterShortcut(key: string): Promise<void>
451
468
 
452
469
  ```tsx
453
470
  <CodeEditor
454
- onInitialized={(api) => {
455
- void api.editor.registerShortcut('Mod-s', 'save');
456
- void api.editor.registerShortcut('Mod-Enter', 'run');
471
+ onInitialized={async (api) => {
472
+ await api.editor.registerShortcut('Mod-s', 'save');
473
+ await api.editor.registerShortcut('Mod-Enter', 'run');
457
474
  }}
458
475
  onShortcut={(name) => {
459
476
  if (name === 'save') handleSave();
@@ -559,6 +576,46 @@ await api.editor.setTheme(undefined); // remove theme, use CM6 default
559
576
 
560
577
  ---
561
578
 
579
+ ## Embedded DSLs in `javascript`
580
+
581
+ The `embeds` prop (or `setLanguage(name, config, embeds)`) mixes extra syntax into the
582
+ `'javascript'` language: highlighting **and** autocomplete suggestions. Packages are bundled
583
+ in the WebView assets; embeds only take effect for `language="javascript"`.
584
+
585
+ ```tsx
586
+ <CodeEditor
587
+ language="javascript"
588
+ languageConfig={{ jsx: true, typescript: true }}
589
+ embeds={{
590
+ taggedTemplates: [
591
+ '@actualwave/codemirror-lang-embed-css',
592
+ '@actualwave/codemirror-lang-embed-sql',
593
+ '@actualwave/codemirror-lang-embed-graphql',
594
+ ],
595
+ supportExtensions: [
596
+ '@actualwave/codemirror-lang-embed-react-native',
597
+ '@actualwave/codemirror-lang-js-completion',
598
+ ['@actualwave/codemirror-lang-embed-tailwind', {}], // [name, config]
599
+ ],
600
+ }}
601
+ {...otherProps}
602
+ />
603
+ ```
604
+
605
+ A package must be in the list matching what it exports, otherwise it silently fails to load:
606
+
607
+ | List | Packages | Provides |
608
+ |---|---|---|
609
+ | `taggedTemplates` | `embed-css`, `embed-sql`, `embed-glsl`, `embed-sksl`, `embed-icu-messageformat`, `embed-graphql` | Nested language highlighting + its suggestions (`embed-graphql`: highlighting only) |
610
+ | `supportExtensions` | `embed-tailwind`, `embed-react-native`, `js-completion` | Suggestions (and Tailwind class coloring) on the JS language; no nested language |
611
+
612
+ Tags are matched by name: `css`/`styled`/`styled.X`/`styled(X)` → CSS, `sql`, `gql`, `glsl`,
613
+ `sksl`, `t` → ICU MessageFormat, `tw` → Tailwind. `embed-react-native` needs no tag (JSX, imports,
614
+ `StyleSheet.create`). Only `supportExtensions` accept a config. Pass a stable (memoized/module-level)
615
+ `embeds` object — a new reference re-resolves the language.
616
+
617
+ ---
618
+
562
619
  ## Supported languages
563
620
 
564
621
  Pass any of these to the `language` prop or `api.editor.setLanguage()`:
@@ -567,6 +624,9 @@ Pass any of these to the `language` prop or `api.editor.setLanguage()`:
567
624
  `less` `lezer` `liquid` `markdown` `php` `python` `rust` `sass` `sql`
568
625
  `vue` `wast` `xml` `yaml`
569
626
 
627
+ Other bundled packages such as `sksl` are not auto-detected — register them with the
628
+ `languages` prop or `api.editor.registerLanguage()`.
629
+
570
630
  Legacy modes (100+ additional languages via `@codemirror/legacy-modes`) can be loaded
571
631
  manually with `api.editor.loadExtension()`:
572
632
 
package/SKILL.md ADDED
@@ -0,0 +1,504 @@
1
+ ---
2
+ name: react-native-codeditor
3
+ description: >
4
+ Embed and control a CodeMirror 6 code editor inside a React Native WebView using
5
+ @actualwave/react-native-codeditor. Use when adding a code editor to a React Native
6
+ app, controlling editor content/language/theme/cursor/selection/history programmatically,
7
+ registering custom keyboard shortcuts, handling editor events (content changes, history
8
+ size, selection), or setting up native asset copying for Android/iOS.
9
+ license: MIT
10
+ compatibility: React Native ≥ 0.71, react-native-webview ≥ 11, Android WebView 61+, iOS with WKWebView. expo-file-system required for iOS URI resolution.
11
+ metadata:
12
+ package: "@actualwave/react-native-codeditor"
13
+ npm: "https://www.npmjs.com/package/@actualwave/react-native-codeditor"
14
+ repository: "https://github.com/burdiuz/react-native-codeditor"
15
+ ---
16
+
17
+ # @actualwave/react-native-codeditor — Agent Skill
18
+
19
+ CodeMirror 6 code editor for React Native, embedded in a WebView with a full bidirectional RPC bridge. Fully offline — all CodeMirror assets are bundled. Works on Android and iOS.
20
+
21
+ ---
22
+
23
+ ## Exports
24
+
25
+ ```ts
26
+ import CodeEditor from '@actualwave/react-native-codeditor';
27
+ // or named (WebViewAPI is the class itself, exported as a value):
28
+ import { CodeEditor, WebViewAPI } from '@actualwave/react-native-codeditor';
29
+
30
+ // Types
31
+ import type {
32
+ CodeEditorProps, // all props for <CodeEditor />
33
+ WebViewAPIHandlers, // handler callbacks
34
+ WebViewRef, // { injectJavaScript, requestFocus? }
35
+ InitialConfig, // { content?, language?, languageConfig?, embeds?, languages?, extensions?, theme?, viewport? }
36
+ EditorAPI, // full interface of api.editor proxy methods
37
+ ExtensionSpec, // string | [string, string] | [string, object] | unknown
38
+ ViewportSettings, // { intialScale?, maximumScale?, minimumScale?, userScalable?, viewportWidth? }
39
+ CursorPosition, // { line: number, ch: number, index: number }
40
+ HistorySize, // { undo: number, redo: number }
41
+ CompletionItem, // { label, type?, detail?, info? }
42
+ EmbedsConfig, // { taggedTemplates?, supportExtensions? }
43
+ } from '@actualwave/react-native-codeditor';
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ npm install @actualwave/react-native-codeditor react-native-webview
52
+ ```
53
+
54
+ **Copy assets to Android** (required — WebView loads these files at runtime):
55
+
56
+ ```bash
57
+ mkdir -p android/app/src/main/assets/codeditor
58
+ cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* android/app/src/main/assets/codeditor/
59
+ ```
60
+
61
+ **Expo (recommended)** — add plugin to `app.json`, then `expo prebuild`:
62
+
63
+ ```json
64
+ { "expo": { "plugins": ["@actualwave/react-native-codeditor"] } }
65
+ ```
66
+
67
+ **iOS** — requires `expo-file-system` for the runtime bundle path:
68
+
69
+ ```bash
70
+ npm install expo-file-system
71
+ ```
72
+
73
+ ```tsx
74
+ import { File, Paths } from 'expo-file-system';
75
+ const IOS_EDITOR_URI = Platform.OS === 'ios'
76
+ ? new File(Paths.bundle, 'assets/codeditor/editor.html').uri
77
+ : undefined;
78
+ <CodeEditor editorUri={IOS_EDITOR_URI} ... />
79
+ ```
80
+
81
+ **Android manifest** — add `adjustResize` so the editor shrinks when the keyboard appears:
82
+
83
+ ```xml
84
+ <activity android:windowSoftInputMode="adjustResize" ... />
85
+ ```
86
+
87
+ **After upgrading** — always re-copy assets:
88
+
89
+ ```bash
90
+ cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* android/app/src/main/assets/codeditor/
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Basic usage
96
+
97
+ ```tsx
98
+ import { useCallback, useRef } from 'react';
99
+ import { KeyboardAvoidingView, Platform } from 'react-native';
100
+ import CodeEditor from '@actualwave/react-native-codeditor';
101
+ import type { WebViewAPI, HistorySize } from '@actualwave/react-native-codeditor';
102
+
103
+ export default function EditorScreen() {
104
+ const apiRef = useRef<WebViewAPI | null>(null);
105
+
106
+ const handleInitialized = useCallback((api: WebViewAPI) => {
107
+ apiRef.current = api;
108
+ void api.focus(); // opens Android soft keyboard
109
+ }, []);
110
+
111
+ return (
112
+ <KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
113
+ <CodeEditor
114
+ content="const x = 42;"
115
+ language="javascript"
116
+ theme="darcula"
117
+ onInitialized={handleInitialized}
118
+ onContentUpdate={(content) => console.log(content.length)}
119
+ onHistorySizeUpdate={(size) => console.log(size.undo, size.redo)}
120
+ onLog={(...args) => console.log('[editor]', ...args)}
121
+ onError={(err) => console.error('[editor]', err)}
122
+ />
123
+ </KeyboardAvoidingView>
124
+ );
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Architecture
131
+
132
+ Two runtimes connected via DDA (Deferred Data Access) RPC over the WebView bridge:
133
+
134
+ ```
135
+ React Native (HOST) WebView (GUEST)
136
+ ────────────────── ───────────────
137
+ CodeEditor component
138
+ └─ WebViewAPI
139
+ └─ initializeHost() ◄──► initializeGuest()
140
+ api.editor.* ──────► EditorController methods
141
+ (DDA proxy) ◄────── Promise results
142
+ ```
143
+
144
+ **Out-of-band messages** (bypass DDA, sent via `ReactNativeWebView.postMessage`):
145
+
146
+ | Type | Direction | Meaning |
147
+ |---|---|---|
148
+ | `__editorReady__` | WebView→RN | Editor fully painted; `onInitialized` fires |
149
+ | `__contentChange__` | WebView→RN | Edit, debounced 300ms — carries `{ value, undo, redo }` |
150
+ | `__selectionChange__` | WebView→RN | Selection changed — carries selected text |
151
+ | `__shortcut__` | WebView→RN | Registered shortcut triggered — carries name string |
152
+ | `__editorLog__` | WebView→RN | `window.log(...)` from WebView |
153
+ | `__editorError__` | WebView→RN | `window.onerror` from WebView |
154
+
155
+ ---
156
+
157
+ ## DDA lazy mode — critical rule
158
+
159
+ DDA operates in **lazy mode**: a proxy call builds a command chain but does NOT send it until the returned value is awaited. **Every `api.editor.*` call MUST be awaited.**
160
+
161
+ ```ts
162
+ // ✅ correct
163
+ await apiRef.current?.editor?.historyUndo();
164
+
165
+ // ✅ correct — async IIFE in useEffect
166
+ void (async () => { await apiRef.current!.editor.setLanguage('python'); })();
167
+
168
+ // ❌ wrong — command is never sent
169
+ void apiRef.current?.editor.historyUndo();
170
+ apiRef.current?.editor.historyUndo();
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Props
176
+
177
+ ### Required callbacks
178
+
179
+ | Prop | Type | Description |
180
+ |---|---|---|
181
+ | `onInitialized` | `(api: WebViewAPI) => void` | Fires when editor is fully ready. Store `api` here. Call `api.focus()` for Android keyboard. |
182
+ | `onContentUpdate` | `(content: string) => void` | After edits (debounced 300ms), full document text. |
183
+ | `onHistorySizeUpdate` | `(size: HistorySize) => void` | After edits (debounced 300ms), `{ undo, redo }` depths. |
184
+ | `onLog` | `(...args: unknown[]) => void` | `window.log()` from the WebView. |
185
+ | `onError` | `(error: unknown) => void` | `window.onerror` from the WebView. |
186
+
187
+ ### Optional callbacks
188
+
189
+ | Prop | Type | Description |
190
+ |---|---|---|
191
+ | `onSelectionChange` | `(text: string) => void` | Called when selection changes; receives selected text (empty string when collapsed). |
192
+ | `onShortcut` | `(name: string) => void` | Called when a shortcut registered via `api.editor.registerShortcut()` is triggered. Receives the name string passed at registration time. |
193
+
194
+ ### Editor configuration
195
+
196
+ | Prop | Type | Default | Description |
197
+ |---|---|---|---|
198
+ | `content` | `string` | `''` | Document content. Initial value is delivered at startup; later prop changes call `setValue` if they differ from the editor's current text. |
199
+ | `language` | `string` | — | Syntax language, e.g. `'javascript'`, `'python'`. Loaded on demand. |
200
+ | `languageConfig` | `Record<string, unknown>` | — | Options passed to the language factory, e.g. `{ jsx: true, typescript: true }` for `'javascript'`. |
201
+ | `languages` | `Record<string, string>` | — | Custom language packages `{ name: packageName }` for names that don't follow `@codemirror/lang-{name}`, e.g. `{ sksl: '@actualwave/codemirror-lang-sksl' }`. Registered before the initial `language` is resolved. |
202
+ | `embeds` | `EmbedsConfig` | — | DSL highlighting + suggestions mixed into `'javascript'`: `{ taggedTemplates?: string[], supportExtensions?: (string \| [string, object])[] }`. See [Embedded DSLs](#embedded-dsls-in-javascript). Changing it re-resolves the current language — pass a stable object. |
203
+ | `extensions` | `ExtensionSpec[]` | `[]` | CM6 extension specs. |
204
+ | `theme` | `string` | — | Theme name, e.g. `'darcula'`, `'monokai'`. |
205
+ | `viewport` | `ViewportSettings` | — | `<meta name="viewport">` options. |
206
+
207
+ ### WebView / layout
208
+
209
+ | Prop | Type | Default | Description |
210
+ |---|---|---|---|
211
+ | `editorUri` | `string` | `'file:///android_asset/codeditor/editor.html'` | Override for iOS — use `new File(Paths.bundle, 'assets/codeditor/editor.html').uri` from `expo-file-system`. |
212
+ | `allowFileAccess` | `boolean` | `true` | Required for `file://` asset loading. |
213
+ | `renderBlockingView` | `() => ReactNode` | `() => <BlockingView />` | Loading overlay while editor initialises. |
214
+
215
+ ---
216
+
217
+ ## `WebViewAPI` — editor control
218
+
219
+ `api` is received in `onInitialized`. `api.editor` is a DDA proxy — all its methods return Promises.
220
+
221
+ ### Focus
222
+
223
+ ```ts
224
+ api.focus(): Promise<void>
225
+ // Also calls webView.requestFocus() for Android keyboard.
226
+ ```
227
+
228
+ ### Content
229
+
230
+ ```ts
231
+ api.editor.getValue(): Promise<string>
232
+ api.editor.setValue(value: string): Promise<void> // preserves history
233
+ api.editor.resetValue(value?: string): Promise<void> // clears history
234
+ ```
235
+
236
+ ### Language / theme / extensions
237
+
238
+ ```ts
239
+ api.editor.setLanguage(name: string, config?: Record<string, unknown>, embeds?: EmbedsConfig): Promise<void>
240
+ api.editor.registerLanguage(name: string, packageName: string): Promise<void>
241
+ // Maps a name to a package not following @codemirror/lang-{name}; applies on the next setLanguage().
242
+ // e.g. await registerLanguage('sksl', '@actualwave/codemirror-lang-sksl'); await setLanguage('sksl');
243
+ api.editor.registerTaggedTemplate(packageName: string): Promise<void>
244
+ api.editor.registerSupportExtension(packageName: string, config?: Record<string, unknown>): Promise<void>
245
+ // Both register globally for the page's lifetime ('javascript' only); re-call setLanguage('javascript') to apply.
246
+ api.editor.setTheme(themeName?: string): Promise<void> // undefined removes theme
247
+ api.editor.setExtensions(specs: ExtensionSpec[]): Promise<void>
248
+ ```
249
+
250
+ ### Cursor and selection
251
+
252
+ ```ts
253
+ api.editor.getCursor(where?: 'from' | 'to' | 'head'): Promise<CursorPosition>
254
+ // CursorPosition = { line: number, ch: number, index: number } (line is 0-based)
255
+ api.editor.setCursor(line: number, ch?: number): Promise<void>
256
+ api.editor.getSelection(): Promise<string>
257
+ api.editor.setSelection(anchor: number, head?: number): Promise<void>
258
+ api.editor.replaceSelection(text: string): Promise<void>
259
+ api.editor.cancelSelection(): Promise<void>
260
+ ```
261
+
262
+ ### History
263
+
264
+ ```ts
265
+ api.editor.historyUndo(): Promise<boolean>
266
+ api.editor.historyRedo(): Promise<boolean>
267
+ api.editor.historyClear(): Promise<void>
268
+ api.editor.historySize(): Promise<HistorySize> // { undo, redo }
269
+ ```
270
+
271
+ ### Editing commands
272
+
273
+ ```ts
274
+ api.editor.indentMore(): Promise<boolean>
275
+ api.editor.indentLess(): Promise<boolean>
276
+ api.editor.toggleComment(): Promise<boolean>
277
+ api.editor.moveLineUp(): Promise<boolean>
278
+ api.editor.moveLineDown(): Promise<boolean>
279
+ api.editor.deleteLine(): Promise<boolean>
280
+ api.editor.selectLine(): Promise<boolean>
281
+ api.editor.selectParentSyntax(): Promise<boolean>
282
+ ```
283
+
284
+ ### Autocomplete
285
+
286
+ ```ts
287
+ api.editor.startCompletion(): Promise<void>
288
+ api.editor.setCompletions(items: CompletionItem[]): Promise<void>
289
+ // CompletionItem = { label, type?, detail?, info? }
290
+ ```
291
+
292
+ ### Custom shortcuts (hardware keyboard)
293
+
294
+ Register key bindings from the React Native side. When triggered in the editor, fires `onShortcut` on the `<CodeEditor>` component with the given name. Shortcuts use `Prec.highest` priority, overriding all built-in keymaps.
295
+
296
+ ```ts
297
+ api.editor.registerShortcut(key: string, name: string): Promise<void>
298
+ api.editor.unregisterShortcut(key: string): Promise<void>
299
+ ```
300
+
301
+ Key notation: `Mod` = Cmd on macOS/iOS, Ctrl on Android/Windows. Examples: `'Mod-s'`, `'Mod-Enter'`, `'Mod-Shift-f'`, `'F5'`.
302
+
303
+ ```tsx
304
+ // Register in onInitialized, handle in onShortcut
305
+ <CodeEditor
306
+ onInitialized={async (api) => {
307
+ await api.editor.registerShortcut('Mod-s', 'save');
308
+ await api.editor.registerShortcut('Mod-Enter', 'run');
309
+ }}
310
+ onShortcut={(name) => {
311
+ if (name === 'save') handleSave();
312
+ if (name === 'run') handleRun();
313
+ }}
314
+ ...
315
+ />
316
+ ```
317
+
318
+ ### Font / keyboard / scroll / advanced
319
+
320
+ ```ts
321
+ api.editor.setFontSize(size: number): Promise<void>
322
+ api.editor.setSoftKeyboard(enabled: boolean): Promise<void>
323
+ api.editor.setViewport(options: ViewportSettings): Promise<void>
324
+ api.editor.scrollToCursor(margin?: number): Promise<void>
325
+ api.editor.loadExtension(moduleName: string): Promise<object>
326
+ api.editor.destroy(): Promise<void>
327
+ ```
328
+
329
+ ### Synchronous RN-side only
330
+
331
+ ```ts
332
+ api.injectJavaScript(code: string): void
333
+ api.requestFocus(): void
334
+ ```
335
+
336
+ ---
337
+
338
+ ## Content model
339
+
340
+ `content` sets the initial document, and later prop changes are pushed to the editor via `setValue` — but only when they differ from the text the editor last reported, so writing `onContentUpdate` output back to `content` is a harmless no-op. Because `onContentUpdate` is debounced (300ms), avoid transforming the text and feeding it back while the user is typing.
341
+
342
+ To push content programmatically you can also use the API:
343
+
344
+ ```ts
345
+ await api.editor.setValue(newCode); // preserve history
346
+ await api.editor.resetValue(newCode); // clear history (e.g. opening new file)
347
+ ```
348
+
349
+ ---
350
+
351
+ ## Extension specs
352
+
353
+ `ExtensionSpec` accepted by `extensions` prop and `api.editor.setExtensions()`:
354
+
355
+ | Form | Example | Behaviour |
356
+ |---|---|---|
357
+ | Package name string | `'@codemirror/search'` | Loaded via built-in registry |
358
+ | `[packageName, exportName]` | `['@uiw/codemirror-theme-nord', 'nord']` | Returns `mod[exportName]` |
359
+ | `[packageName, options]` | `['@codemirror/search', { top: true }]` | Resolver called with options |
360
+ | Pre-built extension | `myExtension` | Used as-is |
361
+
362
+ Built-in registry: `@codemirror/autocomplete`, `@codemirror/search`, `@codemirror/lint`, `@codemirror/collab`, `@codemirror/theme-one-dark`.
363
+
364
+ ---
365
+
366
+ ## Supported languages
367
+
368
+ `angular` `cpp` `css` `go` `html` `java` `javascript` `jinja` `json` `less` `lezer`
369
+ `liquid` `markdown` `php` `python` `rust` `sass` `sql` `vue` `wast` `xml` `yaml`
370
+
371
+ Bundled packages that don't follow the `@codemirror/lang-{name}` naming (e.g. `sksl` →
372
+ `@actualwave/codemirror-lang-sksl`) are not auto-detected — map them with the `languages`
373
+ prop or `api.editor.registerLanguage()` first, otherwise `setLanguage` rejects with
374
+ "No language support found".
375
+
376
+ Legacy modes (100+ more) via `loadExtension`:
377
+
378
+ ```ts
379
+ const { swift } = await api.editor.loadExtension('@codemirror/legacy-modes/mode/swift');
380
+ ```
381
+
382
+ ---
383
+
384
+ ## Themes
385
+
386
+ `androidstudio` `andromeda` `atomone` `aura` `basic` `bbedit` `copilot` `darcula`
387
+ `dracula` `duotone` `eclipse` `github` `material` `monokai` `nord` `okaidia` `solarized`
388
+ `sublime` `tokyoNight` `vscode` `xcode`
389
+
390
+ Two-word themes use camelCase: `tokyoNight`, `githubDark`, `githubLight`, `vscodeDark`, `xcodeDark`, `xcodeLight`, `solarizedDark`, `solarizedLight`.
391
+
392
+ ---
393
+
394
+ ## Built-in keyboard shortcuts
395
+
396
+ The editor ships with standard CodeMirror 6 keymaps active by default.
397
+
398
+ ### Editing (`defaultKeymap`)
399
+ | Key | Action |
400
+ |---|---|
401
+ | `Enter` | Insert newline |
402
+ | `Backspace` / `Delete` | Delete character |
403
+ | `Tab` | Indent |
404
+ | `Shift-Tab` | Unindent |
405
+ | `Mod-a` | Select all |
406
+
407
+ ### History (`historyKeymap`)
408
+ | Key | Action |
409
+ |---|---|
410
+ | `Mod-z` | Undo |
411
+ | `Mod-y` / `Mod-Shift-z` | Redo |
412
+
413
+ ### Search (`searchKeymap`)
414
+ | Key | Action |
415
+ |---|---|
416
+ | `Mod-f` | Open search panel |
417
+ | `F3` / `Mod-g` | Find next |
418
+ | `Escape` | Close search panel |
419
+
420
+ ### Code folding (`foldKeymap`)
421
+ | Key | Action |
422
+ |---|---|
423
+ | `Mod-Shift-[` | Fold at cursor |
424
+ | `Mod-Shift-]` | Unfold at cursor |
425
+
426
+ ### Autocomplete (`completionKeymap`)
427
+ | Key | Action |
428
+ |---|---|
429
+ | `Ctrl-Space` | Trigger completion |
430
+ | `Enter` / `Tab` | Accept completion |
431
+ | `Escape` | Close completion |
432
+
433
+ > On mobile, most shortcuts require a hardware keyboard. Use `api.editor.*` commands programmatically instead.
434
+
435
+ ---
436
+
437
+ ## Keyboard / layout
438
+
439
+ Wrap in `KeyboardAvoidingView` so the editor shrinks when the soft keyboard appears:
440
+
441
+ ```tsx
442
+ <KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
443
+ <CodeEditor ... />
444
+ </KeyboardAvoidingView>
445
+ ```
446
+
447
+ Call `api.focus()` inside `onInitialized` to trigger the Android soft keyboard immediately.
448
+
449
+ ---
450
+
451
+ ## Embedded DSLs in `javascript`
452
+
453
+ `embeds` (prop, or 3rd arg of `setLanguage`) adds highlighting **and** autocomplete suggestions for DSLs inside `'javascript'`. Only applies when the language is `'javascript'`; merged with anything registered via `registerTaggedTemplate` / `registerSupportExtension`. Must be plain serializable data (package names + configs).
454
+
455
+ ```tsx
456
+ <CodeEditor
457
+ language="javascript"
458
+ languageConfig={{ jsx: true, typescript: true }}
459
+ embeds={{
460
+ taggedTemplates: [
461
+ '@actualwave/codemirror-lang-embed-css',
462
+ '@actualwave/codemirror-lang-embed-sql',
463
+ '@actualwave/codemirror-lang-embed-graphql',
464
+ ],
465
+ supportExtensions: [
466
+ '@actualwave/codemirror-lang-embed-react-native',
467
+ '@actualwave/codemirror-lang-embed-tailwind',
468
+ ],
469
+ }}
470
+ />
471
+ ```
472
+
473
+ A package must be in the list matching what it exports, else it fails to load:
474
+
475
+ | List | Packages | Provides |
476
+ |---|---|---|
477
+ | `taggedTemplates` | `embed-css`, `embed-sql`, `embed-glsl`, `embed-sksl`, `embed-icu-messageformat`, `embed-graphql` | Nested language highlighting + its suggestions (`embed-graphql`: highlighting only) |
478
+ | `supportExtensions` | `embed-tailwind`, `embed-react-native`, `js-completion` | Suggestions (and Tailwind class coloring) on the JS language; no nested language |
479
+
480
+ Tags match by name only: `css`/`styled`/`styled.X`/`styled(X)` → CSS, `sql`, `gql` (not `graphql`), `glsl`, `sksl`, `t` → ICU MessageFormat, `tw` → Tailwind. `embed-react-native` needs no tag (JSX, imports, `StyleSheet.create`). Only `supportExtensions` accept a config: `['pkg', config]`. No extra setup is needed for suggestions.
481
+
482
+ ---
483
+
484
+ ## Common pitfalls
485
+
486
+ **DDA calls not dispatched** — forgetting `await` silently does nothing. Every `api.editor.*` must be awaited.
487
+
488
+ **`onInitialized` timing** — `api` is not available until this fires. Guard all API calls with `if (!apiRef.current) return`.
489
+
490
+ **Content updates are debounced** — `onContentUpdate` fires 300ms after the last edit, so `content` may briefly lag behind the editor. Don't rewrite `content` with modified text mid-typing.
491
+
492
+ **`setLanguage` rejects for unregistered names** — custom packages like `sksl` must be registered via the `languages` prop or `registerLanguage()` first.
493
+
494
+ **Embeds silently do nothing** — a package in the wrong list, a typo, or a missing module is skipped with no error, and embeds are ignored unless `language` is `'javascript'`. Tags are exact (`gql`, not `graphql`).
495
+
496
+ **Android assets not copied** — if the WebView shows "Error loading page", the `src/assets/` files haven't been copied to `android/app/src/main/assets/codeditor/`. Re-run the copy command.
497
+
498
+ **iOS URI** — the default `editorUri` is Android-only. iOS requires `new File(Paths.bundle, 'assets/codeditor/editor.html').uri` from `expo-file-system` (SDK 54+ API; on older SDKs use `FileSystem.bundleDirectory`).
499
+
500
+ **Upgrading the library** — always re-copy assets after upgrading:
501
+
502
+ ```bash
503
+ cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* android/app/src/main/assets/codeditor/
504
+ ```