@actualwave/react-native-codeditor 1.0.0 → 1.2.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/CHANGELOG.md +28 -0
- package/README.md +53 -3
- package/SKILL.md +469 -0
- package/THIRD_PARTY_NOTICES +120 -0
- package/app.plugin.js +1 -1
- package/lib/module/CodeEditor.js +73 -2
- package/lib/module/WebViewAPI.js +4 -0
- package/lib/typescript/src/CodeEditor.d.ts +21 -2
- package/lib/typescript/src/EditorAPI.d.ts +44 -1
- package/lib/typescript/src/WebViewAPI.d.ts +5 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/package.json +12 -4
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-core.js +39 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-css.js +19 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-glsl.js +1 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-graphql.js +50 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-icu-messageformat.js +1 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-react-native.js +6 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-sksl.js +7 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-sql.js +5 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-embed-tailwind.js +63 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-glsl.js +20 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-icu-messageformat.js +11 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-react-native.js +71 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-sksl.js +7 -2
- package/src/assets/codemirror/@codemirror_autocomplete.js +9 -9
- package/src/assets/codemirror/@codemirror_commands.js +86 -86
- package/src/assets/codemirror/@codemirror_lang-css.js +2 -2
- package/src/assets/codemirror/@codemirror_lang-go.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-html.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-javascript.js +4 -4
- package/src/assets/codemirror/@codemirror_lang-jinja.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-json.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-less.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-liquid.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-markdown.js +2 -2
- package/src/assets/codemirror/@codemirror_lang-python.js +1 -1
- package/src/assets/codemirror/@codemirror_lang-sql.js +4 -4
- package/src/assets/codemirror/@codemirror_lang-xml.js +1 -1
- package/src/assets/codemirror/@codemirror_language.js +11 -11
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_clike.js +1 -1
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_mathematica.js +1 -1
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_oz.js +1 -1
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_verilog.js +1 -1
- package/src/assets/codemirror/@codemirror_lint.js +5 -5
- package/src/assets/codemirror/@codemirror_merge.js +4 -4
- package/src/assets/codemirror/@codemirror_search.js +8 -8
- package/src/assets/codemirror/@codemirror_state.js +1 -1
- package/src/assets/codemirror/@codemirror_theme-one-dark.js +3 -3
- package/src/assets/codemirror/@codemirror_view.js +6 -6
- package/src/assets/codemirror/@lezer_common.js +2 -2
- package/src/assets/codemirror/@lezer_css.js +1 -1
- package/src/assets/codemirror/@lezer_javascript.js +1 -1
- package/src/assets/codemirror/@lezer_lr.js +1 -1
- package/src/assets/codemirror/@lezer_markdown.js +4 -4
- package/src/assets/codemirror/@lezer_sass.js +1 -1
- package/src/assets/codemirror/_core.js +129 -129
- package/src/assets/codemirror-editor.umd.js +130 -17
- package/src/assets/editor.html +49 -2
- package/lib/typescript/app.plugin.d.ts +0 -3
- package/lib/typescript/babel.config.d.ts +0 -10
- package/lib/typescript/react-native.config.d.ts +0 -2
- package/lib/typescript/scripts/copy-assets.d.ts +0 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `languages` prop on `<CodeEditor>` and `api.editor.registerLanguage(name, packageName)` for
|
|
7
|
+
language packages that don't follow the `@codemirror/lang-{name}` naming convention
|
|
8
|
+
(e.g. `{ sksl: '@actualwave/codemirror-lang-sksl' }`). The prop registers before the initial
|
|
9
|
+
`language` is resolved.
|
|
10
|
+
- `languageConfig` prop and `setLanguage(name, config?, embeds?)` — options passed to the
|
|
11
|
+
language factory (e.g. `{ jsx: true, typescript: true }`).
|
|
12
|
+
- `embeds` prop, `api.editor.registerTaggedTemplate()` and `api.editor.registerSupportExtension()`
|
|
13
|
+
for tagged-template DSL embeddings and support extensions in `javascript`.
|
|
14
|
+
- `CompletionItem` and `EmbedsConfig` types are now exported.
|
|
15
|
+
- `THIRD_PARTY_NOTICES`, `SKILL.md` and `CHANGELOG.md` are included in the package.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Updated bundled `@actualwave/codemirror-package` assets to 0.1.4.
|
|
19
|
+
- `sksl` (and other `@actualwave/codemirror-lang-*` packages) are no longer pre-registered by the
|
|
20
|
+
bundled codemirror-package (it was an undocumented built-in in 1.1.0). Pass
|
|
21
|
+
`languages={{ sksl: '@actualwave/codemirror-lang-sksl' }}` or call `api.editor.registerLanguage()`
|
|
22
|
+
before using them; otherwise `setLanguage` rejects with "No language support found".
|
|
23
|
+
- `lib/typescript` no longer contains declaration files for repo config files.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- Documentation and example for the iOS `editorUri` used `FileSystem.bundleDirectory`, which is not
|
|
27
|
+
available from the default `expo-file-system` entry point in Expo SDK 55. Use
|
|
28
|
+
`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
|
|
147
|
+
import { File, Paths } from 'expo-file-system';
|
|
148
148
|
|
|
149
149
|
const IOS_EDITOR_URI = Platform.OS === 'ios'
|
|
150
|
-
? (
|
|
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
|
-
`
|
|
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
|
|
|
@@ -216,6 +216,7 @@ export default function EditorScreen() {
|
|
|
216
216
|
| `onLog` | `(...args: unknown[]) => void` | Receives `window.log(...)` calls from inside the WebView. |
|
|
217
217
|
| `onError` | `(error: unknown) => void` | Receives `window.onerror` events from inside the WebView. |
|
|
218
218
|
| `onSelectionChange` | `(text: string) => void` | Optional. Called when the user changes the selection; receives the selected text (empty string when selection collapses). |
|
|
219
|
+
| `onShortcut` | `(name: string) => void` | Optional. Called when a keyboard shortcut registered via `api.editor.registerShortcut()` is triggered. Receives the name string passed at registration time. |
|
|
219
220
|
|
|
220
221
|
### Editor configuration
|
|
221
222
|
|
|
@@ -223,6 +224,7 @@ export default function EditorScreen() {
|
|
|
223
224
|
|---|---|---|---|
|
|
224
225
|
| `content` | `string` | `''` | Initial document content (see [Content model](#content-model) below). |
|
|
225
226
|
| `language` | `string` | `undefined` | Syntax language, e.g. `'javascript'`, `'python'`, `'sql'`. The language module is loaded from the bundled assets on first use. |
|
|
227
|
+
| `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. |
|
|
226
228
|
| `extensions` | `ExtensionSpec[]` | `[]` | Additional CodeMirror 6 extension specs. See [Extension specs](#extension-specs). |
|
|
227
229
|
| `theme` | `string` | `undefined` | Theme name. See [Themes](#themes). |
|
|
228
230
|
| `viewport` | `ViewportSettings` | `undefined` | Controls the `<meta name="viewport">` tag inside the WebView. |
|
|
@@ -313,6 +315,12 @@ api.editor.setLanguage(name: string): Promise<void>
|
|
|
313
315
|
// Switches syntax language. Loads the language module on demand.
|
|
314
316
|
// e.g. await api.editor.setLanguage('python')
|
|
315
317
|
|
|
318
|
+
api.editor.registerLanguage(name: string, packageName: string): Promise<void>
|
|
319
|
+
// Maps a language name to a package that doesn't follow @codemirror/lang-{name}.
|
|
320
|
+
// Takes effect on the next setLanguage(). Alternative to the `languages` prop.
|
|
321
|
+
// e.g. await api.editor.registerLanguage('sksl', '@actualwave/codemirror-lang-sksl');
|
|
322
|
+
// await api.editor.setLanguage('sksl')
|
|
323
|
+
|
|
316
324
|
api.editor.setExtensions(specs: ExtensionSpec[]): Promise<void>
|
|
317
325
|
// Replaces the active extension set.
|
|
318
326
|
// e.g. await api.editor.setExtensions(['@codemirror/search'])
|
|
@@ -426,6 +434,45 @@ api.editor.setSoftKeyboard(enabled: boolean): Promise<void>
|
|
|
426
434
|
// Enable or disable the soft keyboard for the WebView editor area.
|
|
427
435
|
```
|
|
428
436
|
|
|
437
|
+
### Keyboard shortcuts
|
|
438
|
+
|
|
439
|
+
Register keyboard shortcuts from the React Native side. When triggered inside the editor,
|
|
440
|
+
the shortcut fires `onShortcut` on the `<CodeEditor>` component with the registered name.
|
|
441
|
+
|
|
442
|
+
Key strings follow [CodeMirror's key binding format](https://codemirror.net/docs/ref/#view.KeyBinding):
|
|
443
|
+
`Mod` resolves to `Cmd` on macOS / iOS and `Ctrl` on Android / Windows.
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
api.editor.registerShortcut(key: string, name: string): Promise<void>
|
|
447
|
+
// Register a keyboard shortcut. When the key combination is pressed, onShortcut(name)
|
|
448
|
+
// fires on the React Native side.
|
|
449
|
+
// e.g. await api.editor.registerShortcut('Mod-s', 'save')
|
|
450
|
+
// await api.editor.registerShortcut('Mod-Enter', 'run')
|
|
451
|
+
// await api.editor.registerShortcut('Mod-Shift-Enter', 'fullRun')
|
|
452
|
+
|
|
453
|
+
api.editor.unregisterShortcut(key: string): Promise<void>
|
|
454
|
+
// Remove a previously registered shortcut.
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Example:**
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
<CodeEditor
|
|
461
|
+
onInitialized={async (api) => {
|
|
462
|
+
await api.editor.registerShortcut('Mod-s', 'save');
|
|
463
|
+
await api.editor.registerShortcut('Mod-Enter', 'run');
|
|
464
|
+
}}
|
|
465
|
+
onShortcut={(name) => {
|
|
466
|
+
if (name === 'save') handleSave();
|
|
467
|
+
if (name === 'run') handleRun();
|
|
468
|
+
}}
|
|
469
|
+
...
|
|
470
|
+
/>
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Shortcuts are registered with `Prec.highest` so they take priority over CodeMirror's
|
|
474
|
+
built-in key bindings.
|
|
475
|
+
|
|
429
476
|
### Advanced
|
|
430
477
|
|
|
431
478
|
```ts
|
|
@@ -527,6 +574,9 @@ Pass any of these to the `language` prop or `api.editor.setLanguage()`:
|
|
|
527
574
|
`less` `lezer` `liquid` `markdown` `php` `python` `rust` `sass` `sql`
|
|
528
575
|
`vue` `wast` `xml` `yaml`
|
|
529
576
|
|
|
577
|
+
Other bundled packages such as `sksl` are not auto-detected — register them with the
|
|
578
|
+
`languages` prop or `api.editor.registerLanguage()`.
|
|
579
|
+
|
|
530
580
|
Legacy modes (100+ additional languages via `@codemirror/legacy-modes`) can be loaded
|
|
531
581
|
manually with `api.editor.loadExtension()`:
|
|
532
582
|
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
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` | — | Tagged-template DSL embeddings and support extensions mixed into `'javascript'`: `{ taggedTemplates?: string[], supportExtensions?: (string \| [string, 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
|
+
## Common pitfalls
|
|
452
|
+
|
|
453
|
+
**DDA calls not dispatched** — forgetting `await` silently does nothing. Every `api.editor.*` must be awaited.
|
|
454
|
+
|
|
455
|
+
**`onInitialized` timing** — `api` is not available until this fires. Guard all API calls with `if (!apiRef.current) return`.
|
|
456
|
+
|
|
457
|
+
**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.
|
|
458
|
+
|
|
459
|
+
**`setLanguage` rejects for unregistered names** — custom packages like `sksl` must be registered via the `languages` prop or `registerLanguage()` first.
|
|
460
|
+
|
|
461
|
+
**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.
|
|
462
|
+
|
|
463
|
+
**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`).
|
|
464
|
+
|
|
465
|
+
**Upgrading the library** — always re-copy assets after upgrading:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* android/app/src/main/assets/codeditor/
|
|
469
|
+
```
|