@actualwave/react-native-codeditor 1.0.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/LICENSE +20 -0
- package/README.md +790 -0
- package/app.plugin.js +133 -0
- package/lib/module/BlockingView.js +25 -0
- package/lib/module/CodeEditor.js +239 -0
- package/lib/module/EditorAPI.js +2 -0
- package/lib/module/WebViewAPI.js +133 -0
- package/lib/module/index.js +6 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/app.plugin.d.ts +3 -0
- package/lib/typescript/babel.config.d.ts +10 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/react-native.config.d.ts +2 -0
- package/lib/typescript/scripts/copy-assets.d.ts +2 -0
- package/lib/typescript/src/BlockingView.d.ts +3 -0
- package/lib/typescript/src/CodeEditor.d.ts +33 -0
- package/lib/typescript/src/EditorAPI.d.ts +66 -0
- package/lib/typescript/src/WebViewAPI.d.ts +46 -0
- package/lib/typescript/src/index.d.ts +7 -0
- package/package.json +106 -0
- package/react-native.config.js +3 -0
- package/src/assets/codemirror/@actualwave_codemirror-lang-sksl.js +15 -0
- package/src/assets/codemirror/@babel_runtime_helpers_interopRequireDefault.js +1 -0
- package/src/assets/codemirror/@babel_runtime_helpers_objectSpread2.js +1 -0
- package/src/assets/codemirror/@babel_runtime_helpers_toConsumableArray.js +1 -0
- package/src/assets/codemirror/@codemirror_autocomplete.js +206 -0
- package/src/assets/codemirror/@codemirror_collab.js +31 -0
- package/src/assets/codemirror/@codemirror_commands.js +411 -0
- package/src/assets/codemirror/@codemirror_lang-angular.js +7 -0
- package/src/assets/codemirror/@codemirror_lang-cpp.js +7 -0
- package/src/assets/codemirror/@codemirror_lang-css.js +14 -0
- package/src/assets/codemirror/@codemirror_lang-go.js +13 -0
- package/src/assets/codemirror/@codemirror_lang-html.js +20 -0
- package/src/assets/codemirror/@codemirror_lang-java.js +7 -0
- package/src/assets/codemirror/@codemirror_lang-javascript.js +54 -0
- package/src/assets/codemirror/@codemirror_lang-jinja.js +15 -0
- package/src/assets/codemirror/@codemirror_lang-json.js +10 -0
- package/src/assets/codemirror/@codemirror_lang-less.js +10 -0
- package/src/assets/codemirror/@codemirror_lang-lezer.js +7 -0
- package/src/assets/codemirror/@codemirror_lang-liquid.js +15 -0
- package/src/assets/codemirror/@codemirror_lang-markdown.js +55 -0
- package/src/assets/codemirror/@codemirror_lang-php.js +7 -0
- package/src/assets/codemirror/@codemirror_lang-python.js +19 -0
- package/src/assets/codemirror/@codemirror_lang-rust.js +7 -0
- package/src/assets/codemirror/@codemirror_lang-sass.js +10 -0
- package/src/assets/codemirror/@codemirror_lang-sql.js +52 -0
- package/src/assets/codemirror/@codemirror_lang-vue.js +6 -0
- package/src/assets/codemirror/@codemirror_lang-wast.js +2 -0
- package/src/assets/codemirror/@codemirror_lang-xml.js +13 -0
- package/src/assets/codemirror/@codemirror_lang-yaml.js +13 -0
- package/src/assets/codemirror/@codemirror_language-data.js +5 -0
- package/src/assets/codemirror/@codemirror_language.js +524 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_apl.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_asciiarmor.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_asn1.js +3 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_asterisk.js +9 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_brainfuck.js +13 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_clike.js +31 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_clojure.js +5 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_cmake.js +6 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_cobol.js +7 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_coffeescript.js +14 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_commonlisp.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_crystal.js +18 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_css.js +5 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_cypher.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_d.js +2 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_diff.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_dockerfile.js +9 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_dtd.js +3 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_dylan.js +35 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_ebnf.js +9 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_ecl.js +3 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_eiffel.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_elm.js +3 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_erlang.js +52 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_factor.js +14 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_fcl.js +2 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_forth.js +5 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_fortran.js +2 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_gas.js +13 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_gherkin.js +5 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_go.js +2 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_groovy.js +3 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_haskell.js +5 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_haxe.js +12 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_http.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_idl.js +7 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_javascript.js +23 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_jinja2.js +8 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_julia.js +17 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_livescript.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_lua.js +2 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_mathematica.js +26 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_mbox.js +11 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_mirc.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_mllike.js +8 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_modelica.js +12 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_mscgen.js +3 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_mumps.js +16 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_nginx.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_nsis.js +20 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_ntriples.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_octave.js +8 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_oz.js +17 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_pascal.js +2 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_pegjs.js +25 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_perl.js +247 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_pig.js +6 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_powershell.js +7 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_properties.js +5 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_protobuf.js +7 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_pug.js +24 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_puppet.js +42 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_python.js +15 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_q.js +2 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_r.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_rpm.js +9 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_ruby.js +4 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_rust.js +5 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_sas.js +23 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_sass.js +26 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_scheme.js +14 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_shell.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_sieve.js +7 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_simple-mode.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_smalltalk.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_solr.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_sparql.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_spreadsheet.js +6 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_sql.js +84 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_stex.js +11 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_stylus.js +45 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_swift.js +2 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_tcl.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_textile.js +2 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_tiddlywiki.js +33 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_tiki.js +20 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_toml.js +4 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_troff.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_ttcn-cfg.js +3 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_ttcn.js +3 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_turtle.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_vb.js +11 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_vbscript.js +26 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_velocity.js +15 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_verilog.js +67 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_vhdl.js +2 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_wast.js +7 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_webidl.js +25 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_xml.js +6 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_xquery.js +51 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_yacas.js +16 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_yaml.js +1 -0
- package/src/assets/codemirror/@codemirror_legacy-modes_mode_z80.js +1 -0
- package/src/assets/codemirror/@codemirror_lint.js +46 -0
- package/src/assets/codemirror/@codemirror_merge.js +167 -0
- package/src/assets/codemirror/@codemirror_search.js +121 -0
- package/src/assets/codemirror/@codemirror_state.js +793 -0
- package/src/assets/codemirror/@codemirror_theme-one-dark.js +12 -0
- package/src/assets/codemirror/@codemirror_view.js +1210 -0
- package/src/assets/codemirror/@lezer_common.js +407 -0
- package/src/assets/codemirror/@lezer_cpp.js +4 -0
- package/src/assets/codemirror/@lezer_css.js +4 -0
- package/src/assets/codemirror/@lezer_go.js +3 -0
- package/src/assets/codemirror/@lezer_highlight.js +380 -0
- package/src/assets/codemirror/@lezer_html.js +20 -0
- package/src/assets/codemirror/@lezer_java.js +2 -0
- package/src/assets/codemirror/@lezer_javascript.js +7 -0
- package/src/assets/codemirror/@lezer_json.js +2 -0
- package/src/assets/codemirror/@lezer_lezer.js +2 -0
- package/src/assets/codemirror/@lezer_lr.js +325 -0
- package/src/assets/codemirror/@lezer_markdown.js +286 -0
- package/src/assets/codemirror/@lezer_php.js +3 -0
- package/src/assets/codemirror/@lezer_python.js +5 -0
- package/src/assets/codemirror/@lezer_rust.js +3 -0
- package/src/assets/codemirror/@lezer_sass.js +5 -0
- package/src/assets/codemirror/@lezer_xml.js +3 -0
- package/src/assets/codemirror/@lezer_yaml.js +12 -0
- package/src/assets/codemirror/@marijn_find-cluster-break.js +10 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-androidstudio.js +3 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-andromeda.js +1 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-atomone.js +6 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-aura.js +1 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-basic.js +1 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-bbedit.js +1 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-copilot.js +1 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-darcula.js +6 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-dracula.js +6 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-duotone.js +5 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-eclipse.js +1 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-github.js +3 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-material.js +1 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-monokai.js +1 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-nord.js +2 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-okaidia.js +2 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-solarized.js +1 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-sublime.js +2 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-tokyo-night.js +1 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-vscode.js +5 -0
- package/src/assets/codemirror/@uiw_codemirror-theme-xcode.js +3 -0
- package/src/assets/codemirror/@uiw_codemirror-themes.js +1 -0
- package/src/assets/codemirror/_core.js +4494 -0
- package/src/assets/codemirror/codemirror.js +46 -0
- package/src/assets/codemirror/crelt.js +1 -0
- package/src/assets/codemirror/style-mod.js +36 -0
- package/src/assets/codemirror/w3c-keyname.js +8 -0
- package/src/assets/codemirror-editor.umd.js +415 -0
- package/src/assets/editor.html +377 -0
- package/src/assets/webview-interface.umd.js +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
# @actualwave/react-native-codeditor
|
|
2
|
+
|
|
3
|
+
A React Native code editor component powered by [CodeMirror 6](https://codemirror.net/), embedded inside a `<WebView>`
|
|
4
|
+
with full bidirectional RPC. Syntax highlighting, themes, history, cursor/selection control —
|
|
5
|
+
all offline, no CDN required.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- CodeMirror 6 with mobile-optimized setup (line numbers, bracket matching, search, autocomplete, …)
|
|
12
|
+
- 20+ syntax languages loaded on demand (`javascript`, `python`, `rust`, `sql`, …)
|
|
13
|
+
- 20+ themes from `@uiw/codemirror-themes` (`darcula`, `monokai`, `github`, `nord`, …)
|
|
14
|
+
- Fully offline — all CM6 assets bundled into the library
|
|
15
|
+
- Real-time `onChange` content updates (no polling)
|
|
16
|
+
- Full editor API: getValue, setValue, cursor, selection, history undo/redo, viewport
|
|
17
|
+
- Transparent RPC bridge via `@actualwave/webview-interface` (DDA)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- React Native ≥ 0.71
|
|
24
|
+
- `react-native-webview` ≥ 11
|
|
25
|
+
- Android: WebView 61+ (Chromium-based)
|
|
26
|
+
- iOS: WebView with file:// access; bundle path must be provided via the `editorUri` prop
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
### 1. Install the packages
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm install @actualwave/react-native-codeditor react-native-webview
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Copy native assets
|
|
39
|
+
|
|
40
|
+
The library ships CodeMirror 6 as static files that must be copied into your app's
|
|
41
|
+
native asset directories.
|
|
42
|
+
|
|
43
|
+
#### Expo managed / prebuild (recommended)
|
|
44
|
+
|
|
45
|
+
The library ships an Expo config plugin. Add it to your `app.json` or `app.config.js`:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"expo": {
|
|
50
|
+
"plugins": ["@actualwave/react-native-codeditor"]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then run `expo prebuild` — the plugin handles everything:
|
|
56
|
+
- Copies assets → `android/app/src/main/assets/codeditor/`
|
|
57
|
+
- Copies assets → `ios/<ProjectName>/assets/codeditor/`
|
|
58
|
+
- Adds a folder reference to `project.pbxproj` so Xcode bundles the files (iOS)
|
|
59
|
+
|
|
60
|
+
The plugin is idempotent — safe to re-run on subsequent prebuilds.
|
|
61
|
+
|
|
62
|
+
#### Manual copy (bare RN or without expo prebuild)
|
|
63
|
+
|
|
64
|
+
**Android:**
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
mkdir -p android/app/src/main/assets/codeditor
|
|
68
|
+
cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* android/app/src/main/assets/codeditor/
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Re-run after every `@actualwave/react-native-codeditor` upgrade.
|
|
72
|
+
|
|
73
|
+
#### iOS
|
|
74
|
+
|
|
75
|
+
**Step 1 — copy files**
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
mkdir -p ios/<YourProject>/assets/codeditor
|
|
79
|
+
cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* ios/<YourProject>/assets/codeditor/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Step 2 — add a folder reference in Xcode**
|
|
83
|
+
|
|
84
|
+
Copied files are not bundled automatically — Xcode must know about the folder:
|
|
85
|
+
|
|
86
|
+
1. Open your `.xcworkspace` in Xcode.
|
|
87
|
+
2. In the Project Navigator, right-click your app group → **Add Files to "\<YourProject\>"…**
|
|
88
|
+
3. Select the `assets` folder inside `ios/<YourProject>/`.
|
|
89
|
+
4. In the options sheet, set **Added folders** to **Create folder references** (blue icon).
|
|
90
|
+
Make sure **Add to targets: \<YourProject\>** is checked.
|
|
91
|
+
5. Click **Add**.
|
|
92
|
+
|
|
93
|
+
The folder appears with a blue icon. A yellow group would break when new language or
|
|
94
|
+
theme files are added; a blue folder reference copies the whole tree automatically.
|
|
95
|
+
|
|
96
|
+
> Re-run the file copy after every `@actualwave/react-native-codeditor` upgrade. The Xcode folder
|
|
97
|
+
> reference only needs to be added once.
|
|
98
|
+
|
|
99
|
+
#### After upgrading `@actualwave/react-native-codeditor`
|
|
100
|
+
|
|
101
|
+
Re-run the asset copy after every upgrade — the bundled CodeMirror files may change
|
|
102
|
+
between versions:
|
|
103
|
+
|
|
104
|
+
**Expo (recommended):**
|
|
105
|
+
```sh
|
|
106
|
+
npx expo prebuild
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Manual — Android:**
|
|
110
|
+
```sh
|
|
111
|
+
cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* android/app/src/main/assets/codeditor/
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Manual — iOS:**
|
|
115
|
+
```sh
|
|
116
|
+
cp -r node_modules/@actualwave/react-native-codeditor/src/assets/* ios/<YourProject>/assets/codeditor/
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The Xcode folder reference only needs to be added once (see iOS steps above).
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### 3. Android: keyboard resize mode
|
|
124
|
+
|
|
125
|
+
Add `adjustResize` to your Activity in `AndroidManifest.xml` so the editor shrinks when the
|
|
126
|
+
soft keyboard appears (instead of being hidden behind it):
|
|
127
|
+
|
|
128
|
+
```xml
|
|
129
|
+
<activity
|
|
130
|
+
android:windowSoftInputMode="adjustResize"
|
|
131
|
+
...
|
|
132
|
+
/>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 4. iOS: provide the editor URI
|
|
136
|
+
|
|
137
|
+
On Android the editor page loads from `file:///android_asset/codeditor/editor.html` (the
|
|
138
|
+
default). On iOS the `.app` bundle path varies per device and build. Compute it at
|
|
139
|
+
runtime using `expo-file-system` and pass it via the `editorUri` prop:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
npm install expo-file-system
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { Platform } from 'react-native';
|
|
147
|
+
import * as FileSystem from 'expo-file-system';
|
|
148
|
+
|
|
149
|
+
const IOS_EDITOR_URI = Platform.OS === 'ios'
|
|
150
|
+
? (FileSystem.bundleDirectory ?? '') + 'assets/codeditor/editor.html'
|
|
151
|
+
: undefined;
|
|
152
|
+
|
|
153
|
+
<CodeEditor editorUri={IOS_EDITOR_URI} ... />
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`FileSystem.bundleDirectory` returns the `file://` path to the `.app` folder (e.g.
|
|
157
|
+
`file:///var/containers/Bundle/Application/<UUID>/MyApp.app/`), which is stable on both
|
|
158
|
+
simulators and real devices regardless of where iOS installed the app.
|
|
159
|
+
|
|
160
|
+
After installing `expo-file-system`, re-run `pod install` to link its native module:
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
cd ios && pod install
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Basic usage
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { useCallback, useRef } from 'react';
|
|
172
|
+
import { KeyboardAvoidingView, Platform } from 'react-native';
|
|
173
|
+
import CodeEditor from '@actualwave/react-native-codeditor';
|
|
174
|
+
import type { WebViewAPI, HistorySize } from '@actualwave/react-native-codeditor';
|
|
175
|
+
|
|
176
|
+
export default function EditorScreen() {
|
|
177
|
+
const apiRef = useRef<WebViewAPI | null>(null);
|
|
178
|
+
|
|
179
|
+
const handleInitialized = useCallback((api: WebViewAPI) => {
|
|
180
|
+
apiRef.current = api;
|
|
181
|
+
void api.focus(); // show soft keyboard on Android
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
// Shrinks the editor when the soft keyboard appears.
|
|
186
|
+
<KeyboardAvoidingView
|
|
187
|
+
style={{ flex: 1 }}
|
|
188
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
189
|
+
>
|
|
190
|
+
<CodeEditor
|
|
191
|
+
content="const x = 42;"
|
|
192
|
+
language="javascript"
|
|
193
|
+
theme="darcula"
|
|
194
|
+
onInitialized={handleInitialized}
|
|
195
|
+
onContentUpdate={(content) => console.log('length:', content.length)}
|
|
196
|
+
onHistorySizeUpdate={(size) => console.log('undo:', size.undo)}
|
|
197
|
+
onLog={(...args) => console.log('[editor]', ...args)}
|
|
198
|
+
onError={(err) => console.error('[editor]', err)}
|
|
199
|
+
/>
|
|
200
|
+
</KeyboardAvoidingView>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Props
|
|
208
|
+
|
|
209
|
+
### Required callbacks
|
|
210
|
+
|
|
211
|
+
| Prop | Type | Description |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| `onInitialized` | `(api: WebViewAPI) => void` | Called once the editor is fully ready — after the theme and language modules have loaded and the editor is painted. Store the `api` handle here. Call `api.focus()` to open the Android keyboard. |
|
|
214
|
+
| `onContentUpdate` | `(content: string) => void` | Called on every keystroke with the full document text. |
|
|
215
|
+
| `onHistorySizeUpdate` | `(size: HistorySize) => void` | Called on every keystroke with `{ undo: number, redo: number }`. |
|
|
216
|
+
| `onLog` | `(...args: unknown[]) => void` | Receives `window.log(...)` calls from inside the WebView. |
|
|
217
|
+
| `onError` | `(error: unknown) => void` | Receives `window.onerror` events from inside the WebView. |
|
|
218
|
+
| `onSelectionChange` | `(text: string) => void` | Optional. Called when the user changes the selection; receives the selected text (empty string when selection collapses). |
|
|
219
|
+
|
|
220
|
+
### Editor configuration
|
|
221
|
+
|
|
222
|
+
| Prop | Type | Default | Description |
|
|
223
|
+
|---|---|---|---|
|
|
224
|
+
| `content` | `string` | `''` | Initial document content (see [Content model](#content-model) below). |
|
|
225
|
+
| `language` | `string` | `undefined` | Syntax language, e.g. `'javascript'`, `'python'`, `'sql'`. The language module is loaded from the bundled assets on first use. |
|
|
226
|
+
| `extensions` | `ExtensionSpec[]` | `[]` | Additional CodeMirror 6 extension specs. See [Extension specs](#extension-specs). |
|
|
227
|
+
| `theme` | `string` | `undefined` | Theme name. See [Themes](#themes). |
|
|
228
|
+
| `viewport` | `ViewportSettings` | `undefined` | Controls the `<meta name="viewport">` tag inside the WebView. |
|
|
229
|
+
|
|
230
|
+
### WebView / layout
|
|
231
|
+
|
|
232
|
+
| Prop | Type | Default | Description |
|
|
233
|
+
|---|---|---|---|
|
|
234
|
+
| `editorUri` | `string` | `'file:///android_asset/codeditor/editor.html'` | URI of the editor HTML page. Override for iOS — see [iOS: provide the editor URI](#4-ios-provide-the-editor-uri). |
|
|
235
|
+
| `allowFileAccess` | `boolean` | `true` | Passed to `<WebView>`. Required for `file://` asset loading. |
|
|
236
|
+
| `renderBlockingView` | `() => ReactNode` | `() => <BlockingView />` | Overlay rendered while the editor is initialising. Replace with your own loading UI. |
|
|
237
|
+
| `onWebViewRefUpdated` | `(ref) => void` | — | Called when the internal WebView ref changes. |
|
|
238
|
+
| `onLoad` | func | — | WebView `onLoad` pass-through. |
|
|
239
|
+
| `onLoadStart` | func | — | WebView `onLoadStart` pass-through. |
|
|
240
|
+
| `onLoadProgress` | func | — | WebView `onLoadProgress` pass-through. |
|
|
241
|
+
| `onLoadEnd` | func | — | WebView `onLoadEnd` pass-through. |
|
|
242
|
+
| `onNavigationStateChange` | func | — | WebView `onNavigationStateChange` pass-through. |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Content model
|
|
247
|
+
|
|
248
|
+
`CodeEditor` is **uncontrolled** — the editor owns its document state after the first render,
|
|
249
|
+
similar to an `<input defaultValue="…">`. The `content` prop sets the initial document content
|
|
250
|
+
once (delivered via the DDA handshake), but it is not kept in sync with what the user types.
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
// Initial content is set once; subsequent user edits are not reflected back to the prop.
|
|
254
|
+
<CodeEditor content="const x = 42;" ... />
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Changes are reported out via `onContentUpdate` on every keystroke, but writing them back to
|
|
258
|
+
`content` would create a feedback loop where every keystroke triggers a `setValue` call that
|
|
259
|
+
resets the cursor — so **don't** pass user-typed content back as the `content` prop.
|
|
260
|
+
|
|
261
|
+
To push new content into the editor programmatically, use the API:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
// Replace content, preserve undo/redo history:
|
|
265
|
+
await api.editor.setValue(newCode);
|
|
266
|
+
|
|
267
|
+
// Replace content AND clear history (e.g. when opening a new file):
|
|
268
|
+
await api.editor.resetValue(newCode);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`content` prop changes after the initial render do call `api.editor.setValue` internally, so
|
|
272
|
+
prop-driven replacement works (e.g. switching between files stored in React state). Just don't
|
|
273
|
+
do it in response to `onContentUpdate` — only in response to an external event like a file load
|
|
274
|
+
or language switch.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## `WebViewAPI` — editor control
|
|
279
|
+
|
|
280
|
+
The `api` object received in `onInitialized` provides:
|
|
281
|
+
|
|
282
|
+
- `api.focus()` — focuses the editor and triggers the Android soft keyboard.
|
|
283
|
+
- `api.editor` — a DDA proxy to the live CM6 editor inside the WebView. All `api.editor.*`
|
|
284
|
+
methods return Promises resolved by the WebView. **Always `await` these calls** — DDA
|
|
285
|
+
operates in lazy mode and only dispatches the command when `.then` is accessed on the
|
|
286
|
+
returned Promise. Calling without `await` (or `void`) silently does nothing.
|
|
287
|
+
- `api.injectJavaScript(code)` / `api.requestFocus()` — synchronous, React Native side only.
|
|
288
|
+
|
|
289
|
+
### Focus
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
api.focus(): Promise<void>
|
|
293
|
+
// Focuses the editor. Also calls webView.requestFocus() to trigger the Android keyboard.
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Content
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
api.editor.getValue(): Promise<string>
|
|
300
|
+
// Returns the current document text.
|
|
301
|
+
|
|
302
|
+
api.editor.setValue(value: string): Promise<void>
|
|
303
|
+
// Replaces the document content. Preserves history.
|
|
304
|
+
|
|
305
|
+
api.editor.resetValue(value?: string): Promise<void>
|
|
306
|
+
// Replaces content AND clears undo/redo history.
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Language, extensions, theme
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
api.editor.setLanguage(name: string): Promise<void>
|
|
313
|
+
// Switches syntax language. Loads the language module on demand.
|
|
314
|
+
// e.g. await api.editor.setLanguage('python')
|
|
315
|
+
|
|
316
|
+
api.editor.setExtensions(specs: ExtensionSpec[]): Promise<void>
|
|
317
|
+
// Replaces the active extension set.
|
|
318
|
+
// e.g. await api.editor.setExtensions(['@codemirror/search'])
|
|
319
|
+
|
|
320
|
+
api.editor.setTheme(themeName?: string): Promise<void>
|
|
321
|
+
// Switches theme. Pass undefined to remove the theme.
|
|
322
|
+
// e.g. await api.editor.setTheme('monokai')
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Viewport
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
api.editor.setViewport(options: ViewportSettings): Promise<void>
|
|
329
|
+
// Updates <meta name="viewport"> inside the WebView.
|
|
330
|
+
// options: { intialScale?, maximumScale?, minimumScale?, userScalable?, viewportWidth? }
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Cursor and selection
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
api.editor.getCursor(where?: 'from' | 'to' | 'head'): Promise<CursorPosition>
|
|
337
|
+
// Returns { line, ch, index } of the cursor (or selection boundary).
|
|
338
|
+
// line is 0-based; index is the absolute character offset.
|
|
339
|
+
|
|
340
|
+
api.editor.setCursor(line: number, ch?: number): Promise<void>
|
|
341
|
+
// Moves the cursor to line (0-based) + character offset.
|
|
342
|
+
|
|
343
|
+
api.editor.getSelection(): Promise<string>
|
|
344
|
+
// Returns the currently selected text.
|
|
345
|
+
|
|
346
|
+
api.editor.setSelection(anchor: number, head?: number): Promise<void>
|
|
347
|
+
// Sets the selection by absolute character offsets.
|
|
348
|
+
|
|
349
|
+
api.editor.replaceSelection(text: string): Promise<void>
|
|
350
|
+
// Replaces the current selection with text.
|
|
351
|
+
|
|
352
|
+
api.editor.cancelSelection(): Promise<void>
|
|
353
|
+
// Collapses the selection to the cursor position.
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### History
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
api.editor.historyUndo(): Promise<boolean>
|
|
360
|
+
// Undoes the last change. Returns true if an undo was performed.
|
|
361
|
+
|
|
362
|
+
api.editor.historyRedo(): Promise<boolean>
|
|
363
|
+
// Redoes the last undone change. Returns true if a redo was performed.
|
|
364
|
+
|
|
365
|
+
api.editor.historyClear(): Promise<void>
|
|
366
|
+
// Clears the undo/redo history without changing the document.
|
|
367
|
+
|
|
368
|
+
api.editor.historySize(): Promise<HistorySize>
|
|
369
|
+
// Returns the current { undo, redo } depth.
|
|
370
|
+
// Note: history size is also reported automatically via onHistorySizeUpdate.
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Scroll
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
api.editor.scrollToCursor(margin?: number): Promise<void>
|
|
377
|
+
// Scrolls the editor so the cursor is visible.
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Editing commands
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
api.editor.indentMore(): Promise<boolean>
|
|
384
|
+
api.editor.indentLess(): Promise<boolean>
|
|
385
|
+
// Indent / unindent the current line or selection.
|
|
386
|
+
|
|
387
|
+
api.editor.toggleComment(): Promise<boolean>
|
|
388
|
+
// Toggle line comments on the current selection.
|
|
389
|
+
|
|
390
|
+
api.editor.moveLineUp(): Promise<boolean>
|
|
391
|
+
api.editor.moveLineDown(): Promise<boolean>
|
|
392
|
+
// Move the current line (or selected lines) up or down.
|
|
393
|
+
|
|
394
|
+
api.editor.deleteLine(): Promise<boolean>
|
|
395
|
+
// Delete the current line.
|
|
396
|
+
|
|
397
|
+
api.editor.selectLine(): Promise<boolean>
|
|
398
|
+
// Select the entire current line.
|
|
399
|
+
|
|
400
|
+
api.editor.selectParentSyntax(): Promise<boolean>
|
|
401
|
+
// Expand the selection to the enclosing syntax node.
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Autocomplete
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
api.editor.startCompletion(): Promise<void>
|
|
408
|
+
// Explicitly trigger the autocomplete popup.
|
|
409
|
+
|
|
410
|
+
api.editor.setCompletions(items: CompletionItem[]): Promise<void>
|
|
411
|
+
// Replace the static completions list used by the built-in completion source.
|
|
412
|
+
// Each item: { label, type?, detail?, info? }
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Font size
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
api.editor.setFontSize(size: number): Promise<void>
|
|
419
|
+
// Sets the editor font size in pixels.
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Soft keyboard
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
api.editor.setSoftKeyboard(enabled: boolean): Promise<void>
|
|
426
|
+
// Enable or disable the soft keyboard for the WebView editor area.
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Advanced
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
api.editor.loadExtension(moduleName: string): Promise<object>
|
|
433
|
+
// Loads a CM6 module by package name and returns its raw exports.
|
|
434
|
+
// Useful for building custom features on top of bundled modules.
|
|
435
|
+
|
|
436
|
+
api.editor.destroy(): Promise<void>
|
|
437
|
+
// Destroys the CM6 EditorView and removes it from the DOM.
|
|
438
|
+
|
|
439
|
+
api.injectJavaScript(code: string): void
|
|
440
|
+
// Runs arbitrary JavaScript inside the WebView. Synchronous, no return value.
|
|
441
|
+
|
|
442
|
+
api.requestFocus(): void
|
|
443
|
+
// Calls webView.requestFocus() on the native WebView ref (Android keyboard hint).
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Extension specs
|
|
449
|
+
|
|
450
|
+
The `extensions` prop and `api.editor.setExtensions()` accept an array of **extension specs**.
|
|
451
|
+
Each item can be:
|
|
452
|
+
|
|
453
|
+
| Form | Example | Behaviour |
|
|
454
|
+
|---|---|---|
|
|
455
|
+
| Package name string | `'@codemirror/search'` | Loaded and resolved via the built-in registry |
|
|
456
|
+
| `[packageName, exportName]` | `['@uiw/codemirror-theme-nord', 'nord']` | `mod[exportName]` returned directly |
|
|
457
|
+
| `[packageName, options]` | `['@codemirror/search', { top: true }]` | Resolver called with options |
|
|
458
|
+
| Any other value | `myCustomExtension` | Used as-is (a pre-built CM6 `Extension`) |
|
|
459
|
+
|
|
460
|
+
### Built-in extension registry
|
|
461
|
+
|
|
462
|
+
| Package | Resolver |
|
|
463
|
+
|---|---|
|
|
464
|
+
| `@codemirror/autocomplete` | `mod.autocompletion(options)` |
|
|
465
|
+
| `@codemirror/search` | `mod.search(options)` |
|
|
466
|
+
| `@codemirror/lint` | `mod.lintGutter(options)` |
|
|
467
|
+
| `@codemirror/collab` | `mod.collab(options)` |
|
|
468
|
+
| `@codemirror/theme-one-dark` | `mod.oneDark` |
|
|
469
|
+
|
|
470
|
+
### Examples
|
|
471
|
+
|
|
472
|
+
```tsx
|
|
473
|
+
// Search panel at the top + autocomplete
|
|
474
|
+
<CodeEditor
|
|
475
|
+
extensions={[
|
|
476
|
+
['@codemirror/search', { top: true }],
|
|
477
|
+
'@codemirror/autocomplete',
|
|
478
|
+
]}
|
|
479
|
+
...
|
|
480
|
+
/>
|
|
481
|
+
|
|
482
|
+
// One Dark theme (from @codemirror, not @uiw)
|
|
483
|
+
<CodeEditor
|
|
484
|
+
extensions={[['@codemirror/theme-one-dark', 'oneDark']]}
|
|
485
|
+
...
|
|
486
|
+
/>
|
|
487
|
+
|
|
488
|
+
// @uiw theme via per-package spec (NOT the meta-package @uiw/codemirror-themes)
|
|
489
|
+
<CodeEditor
|
|
490
|
+
extensions={[['@uiw/codemirror-theme-monokai', 'monokai']]}
|
|
491
|
+
...
|
|
492
|
+
/>
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Themes
|
|
498
|
+
|
|
499
|
+
Pass a theme name string to the `theme` prop:
|
|
500
|
+
|
|
501
|
+
| | | | |
|
|
502
|
+
|---|---|---|---|
|
|
503
|
+
| `androidstudio` | `andromeda` | `atomone` | `aura` |
|
|
504
|
+
| `basic` | `bbedit` | `copilot` | `darcula` |
|
|
505
|
+
| `dracula` | `duotone` | `eclipse` | `github` |
|
|
506
|
+
| `material` | `monokai` | `nord` | `okaidia` |
|
|
507
|
+
| `solarized` | `sublime` | `vscode` | `xcode` |
|
|
508
|
+
|
|
509
|
+
```tsx
|
|
510
|
+
<CodeEditor theme="nord" ... />
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Switch theme at runtime:
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
await api.editor.setTheme('monokai');
|
|
517
|
+
await api.editor.setTheme(undefined); // remove theme, use CM6 default
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## Supported languages
|
|
523
|
+
|
|
524
|
+
Pass any of these to the `language` prop or `api.editor.setLanguage()`:
|
|
525
|
+
|
|
526
|
+
`angular` `cpp` `css` `go` `html` `java` `javascript` `jinja` `json`
|
|
527
|
+
`less` `lezer` `liquid` `markdown` `php` `python` `rust` `sass` `sql`
|
|
528
|
+
`vue` `wast` `xml` `yaml`
|
|
529
|
+
|
|
530
|
+
Legacy modes (100+ additional languages via `@codemirror/legacy-modes`) can be loaded
|
|
531
|
+
manually with `api.editor.loadExtension()`:
|
|
532
|
+
|
|
533
|
+
```ts
|
|
534
|
+
const { swift } = await api.editor.loadExtension('@codemirror/legacy-modes/mode/swift');
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Viewport settings
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
interface ViewportSettings {
|
|
543
|
+
intialScale?: number; // note: matches viewport meta attribute spelling
|
|
544
|
+
maximumScale?: number;
|
|
545
|
+
minimumScale?: number;
|
|
546
|
+
userScalable?: boolean;
|
|
547
|
+
viewportWidth?: string | number; // e.g. 'device-width'
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
<CodeEditor
|
|
553
|
+
viewport={{
|
|
554
|
+
intialScale: 1,
|
|
555
|
+
minimumScale: 0.5,
|
|
556
|
+
maximumScale: 3,
|
|
557
|
+
userScalable: true,
|
|
558
|
+
viewportWidth: 'device-width',
|
|
559
|
+
}}
|
|
560
|
+
...
|
|
561
|
+
/>
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## Full example
|
|
567
|
+
|
|
568
|
+
```tsx
|
|
569
|
+
import { useCallback, useRef, useState } from 'react';
|
|
570
|
+
import { KeyboardAvoidingView, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
571
|
+
import CodeEditor from '@actualwave/react-native-codeditor';
|
|
572
|
+
import type { WebViewAPI, HistorySize } from '@actualwave/react-native-codeditor';
|
|
573
|
+
|
|
574
|
+
const INITIAL_CODE = `function greet(name) {
|
|
575
|
+
return 'Hello, ' + name + '!';
|
|
576
|
+
}
|
|
577
|
+
`;
|
|
578
|
+
|
|
579
|
+
const VIEWPORT = {
|
|
580
|
+
intialScale: 1,
|
|
581
|
+
minimumScale: 0.5,
|
|
582
|
+
maximumScale: 3,
|
|
583
|
+
userScalable: true,
|
|
584
|
+
viewportWidth: 'device-width',
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
export default function EditorScreen() {
|
|
588
|
+
const apiRef = useRef<WebViewAPI | null>(null);
|
|
589
|
+
const [status, setStatus] = useState('Loading…');
|
|
590
|
+
const [historySize, setHistorySize] = useState<HistorySize | null>(null);
|
|
591
|
+
|
|
592
|
+
const handleInitialized = useCallback((api: WebViewAPI) => {
|
|
593
|
+
apiRef.current = api;
|
|
594
|
+
setStatus('Ready');
|
|
595
|
+
void api.focus();
|
|
596
|
+
}, []);
|
|
597
|
+
|
|
598
|
+
const handleUndo = useCallback(async () => { await apiRef.current?.editor?.historyUndo(); }, []);
|
|
599
|
+
const handleRedo = useCallback(async () => { await apiRef.current?.editor?.historyRedo(); }, []);
|
|
600
|
+
|
|
601
|
+
const handleSwitchLanguage = useCallback(async () => {
|
|
602
|
+
await apiRef.current?.editor.setLanguage('python');
|
|
603
|
+
await apiRef.current?.editor.setTheme('github');
|
|
604
|
+
}, []);
|
|
605
|
+
|
|
606
|
+
return (
|
|
607
|
+
// KeyboardAvoidingView shrinks the editor when the soft keyboard appears.
|
|
608
|
+
// Requires android:windowSoftInputMode="adjustResize" in AndroidManifest.xml.
|
|
609
|
+
<KeyboardAvoidingView
|
|
610
|
+
style={styles.container}
|
|
611
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
612
|
+
>
|
|
613
|
+
<View style={styles.toolbar}>
|
|
614
|
+
<TouchableOpacity style={styles.btn} onPress={handleUndo}>
|
|
615
|
+
<Text style={styles.btnText}>Undo</Text>
|
|
616
|
+
</TouchableOpacity>
|
|
617
|
+
<TouchableOpacity style={styles.btn} onPress={handleRedo}>
|
|
618
|
+
<Text style={styles.btnText}>Redo</Text>
|
|
619
|
+
</TouchableOpacity>
|
|
620
|
+
<TouchableOpacity style={styles.btn} onPress={handleSwitchLanguage}>
|
|
621
|
+
<Text style={styles.btnText}>→ Python</Text>
|
|
622
|
+
</TouchableOpacity>
|
|
623
|
+
<Text style={styles.status}>
|
|
624
|
+
{status}{historySize ? ` ↩${historySize.undo} ↪${historySize.redo}` : ''}
|
|
625
|
+
</Text>
|
|
626
|
+
</View>
|
|
627
|
+
|
|
628
|
+
<CodeEditor
|
|
629
|
+
content={INITIAL_CODE}
|
|
630
|
+
language="javascript"
|
|
631
|
+
theme="darcula"
|
|
632
|
+
viewport={VIEWPORT}
|
|
633
|
+
onInitialized={handleInitialized}
|
|
634
|
+
onContentUpdate={(text) => setStatus(`${text.length} chars`)}
|
|
635
|
+
onHistorySizeUpdate={setHistorySize}
|
|
636
|
+
onLog={(...args) => console.log('[editor]', ...args)}
|
|
637
|
+
onError={(err) => console.error('[editor]', err)}
|
|
638
|
+
/>
|
|
639
|
+
</KeyboardAvoidingView>
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const styles = StyleSheet.create({
|
|
644
|
+
container: { flex: 1, backgroundColor: '#1e1e1e' },
|
|
645
|
+
toolbar: {
|
|
646
|
+
flexDirection: 'row',
|
|
647
|
+
alignItems: 'center',
|
|
648
|
+
padding: 8,
|
|
649
|
+
gap: 8,
|
|
650
|
+
backgroundColor: '#2d2d2d',
|
|
651
|
+
},
|
|
652
|
+
btn: {
|
|
653
|
+
backgroundColor: '#0e639c',
|
|
654
|
+
paddingHorizontal: 12,
|
|
655
|
+
paddingVertical: 6,
|
|
656
|
+
borderRadius: 4,
|
|
657
|
+
},
|
|
658
|
+
btnText: { color: '#fff', fontSize: 13 },
|
|
659
|
+
status: { color: '#aaa', fontSize: 12, flex: 1, textAlign: 'right' },
|
|
660
|
+
});
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## Contributing
|
|
666
|
+
|
|
667
|
+
### Initial setup
|
|
668
|
+
|
|
669
|
+
```bash
|
|
670
|
+
git clone https://github.com/burdiuz/react-native-codeditor.git
|
|
671
|
+
cd react-native-codeditor
|
|
672
|
+
npm install
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Regenerate WebView assets
|
|
676
|
+
|
|
677
|
+
The `src/assets/` files are pre-built and committed. Run this when `@actualwave/codemirror-package`
|
|
678
|
+
is updated to a new version:
|
|
679
|
+
|
|
680
|
+
```bash
|
|
681
|
+
npm run copy-assets
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
`copy-assets` reads from `node_modules/@actualwave/codemirror-package/dist/` and writes:
|
|
685
|
+
- `src/assets/codemirror-editor.umd.js` — plain IIFE bundle (no `import`/`export`)
|
|
686
|
+
- `src/assets/codemirror/` — individual CM6 module files
|
|
687
|
+
|
|
688
|
+
### Build the library
|
|
689
|
+
|
|
690
|
+
```bash
|
|
691
|
+
# Compile TypeScript → lib/ (ESM + type declarations):
|
|
692
|
+
npm run prepare
|
|
693
|
+
|
|
694
|
+
# Type-check only:
|
|
695
|
+
npm run typecheck
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Run the example app
|
|
699
|
+
|
|
700
|
+
```bash
|
|
701
|
+
# 1. Build library
|
|
702
|
+
npm run prepare
|
|
703
|
+
|
|
704
|
+
# 2. Copy assets to the example's Android project
|
|
705
|
+
cp -r src/assets/* example/android/app/src/main/assets/codeditor/
|
|
706
|
+
|
|
707
|
+
# 3. Copy assets to the example's iOS project
|
|
708
|
+
cp -r src/assets/* example/ios/CodeditorExample/assets/codeditor/
|
|
709
|
+
|
|
710
|
+
# 4. Launch
|
|
711
|
+
cd example && npx expo run:android # or run:ios
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
For iOS, also ensure the `assets` folder reference exists in Xcode (`project.pbxproj`).
|
|
715
|
+
See `COPY_ASSETS.md` for the full manual and automated steps.
|
|
716
|
+
|
|
717
|
+
Re-run steps 2–4 after any change to `src/assets/`. Re-run step 1 after any TypeScript
|
|
718
|
+
source change.
|
|
719
|
+
|
|
720
|
+
Metro resolves `@actualwave/react-native-codeditor` from the workspace root via `lib/module/index.js`.
|
|
721
|
+
|
|
722
|
+
Do NOT use `./gradlew clean` — it fails because `react-native-webview` codegen JNI
|
|
723
|
+
directories don't exist until after the first successful build. Use `npx expo run:android` directly.
|
|
724
|
+
|
|
725
|
+
### Type-check
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
npx tsc --noEmit
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
---
|
|
732
|
+
|
|
733
|
+
## Updating dependencies (contributors)
|
|
734
|
+
|
|
735
|
+
### Updating CodeMirror assets
|
|
736
|
+
|
|
737
|
+
`src/assets/codemirror/` and `src/assets/codemirror-editor.umd.js` are generated from
|
|
738
|
+
`@actualwave/js-codemirror-package` (a devDependency, published to npm). To update:
|
|
739
|
+
|
|
740
|
+
```bash
|
|
741
|
+
# 1. Update the version in package.json, then:
|
|
742
|
+
npm install
|
|
743
|
+
|
|
744
|
+
# 2. Regenerate the assets:
|
|
745
|
+
npm run copy-assets
|
|
746
|
+
|
|
747
|
+
# 3. Copy to the example app:
|
|
748
|
+
cp -r src/assets/* example/android/app/src/main/assets/codeditor/
|
|
749
|
+
cp -r src/assets/* example/ios/CodeditorExample/assets/codeditor/
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
Library consumers copy from `node_modules/@actualwave/react-native-codeditor/src/assets/`
|
|
753
|
+
into their own project (see [Copy native assets](#2-copy-native-assets)).
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## Known issues
|
|
758
|
+
|
|
759
|
+
### Android: characters appearing after the cursor when typing fast
|
|
760
|
+
|
|
761
|
+
Two separate issues can cause this, both fixed in the library:
|
|
762
|
+
|
|
763
|
+
**1. `drawSelection()` hiding the native cursor**
|
|
764
|
+
|
|
765
|
+
CM6's `drawSelection()` extension replaces the native browser cursor with a custom overlay.
|
|
766
|
+
Android's IME tracks the native cursor to know where to insert text — hiding it causes characters
|
|
767
|
+
to appear to the right of the cursor instead of advancing it.
|
|
768
|
+
|
|
769
|
+
**Fixed**: The library uses a custom `mobileSetup` that omits `drawSelection()`, keeping the
|
|
770
|
+
native cursor visible. If you add `drawSelection()` via a custom extension, this issue returns.
|
|
771
|
+
|
|
772
|
+
**2. EditContext API race condition (Chrome 126+ WebView)**
|
|
773
|
+
|
|
774
|
+
Chrome 126 introduced the [EditContext API](https://developer.chrome.com/docs/web-platform/editcontext/), which CM6 v6.42+ activates automatically on Android Chrome. In Chrome 147 there is a race condition where successive IME `textupdate` events arrive faster than CM6 can sync back via `editContext.updateText/updateSelection`, again placing characters after the cursor during fast typing.
|
|
775
|
+
|
|
776
|
+
**Fixed**: The library sets `EditorView.EDIT_CONTEXT = false` in `createEditor` (after the CM6
|
|
777
|
+
modules load but before the `EditorView` is constructed), falling back to the `contenteditable` +
|
|
778
|
+
MutationObserver path which is stable at any typing speed when `drawSelection()` is omitted.
|
|
779
|
+
|
|
780
|
+
### iOS: editor URI must be provided manually
|
|
781
|
+
|
|
782
|
+
The default `editorUri` points to `file:///android_asset/codeditor/editor.html` (Android
|
|
783
|
+
only). On iOS you must compute the `.app` bundle path at runtime and pass it via the
|
|
784
|
+
`editorUri` prop (see [iOS: provide the editor URI](#4-ios-provide-the-editor-uri)).
|
|
785
|
+
|
|
786
|
+
---
|
|
787
|
+
|
|
788
|
+
## License
|
|
789
|
+
|
|
790
|
+
MIT
|