@actualwave/react-native-codeditor 1.0.0 → 1.1.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/README.md +40 -0
- package/lib/module/CodeEditor.js +4 -0
- package/lib/module/WebViewAPI.js +4 -0
- package/lib/typescript/src/CodeEditor.d.ts +2 -1
- package/lib/typescript/src/EditorAPI.d.ts +2 -0
- package/lib/typescript/src/WebViewAPI.d.ts +1 -0
- package/package.json +1 -1
- package/src/assets/editor.html +24 -1
package/README.md
CHANGED
|
@@ -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
|
|
|
@@ -426,6 +427,45 @@ api.editor.setSoftKeyboard(enabled: boolean): Promise<void>
|
|
|
426
427
|
// Enable or disable the soft keyboard for the WebView editor area.
|
|
427
428
|
```
|
|
428
429
|
|
|
430
|
+
### Keyboard shortcuts
|
|
431
|
+
|
|
432
|
+
Register keyboard shortcuts from the React Native side. When triggered inside the editor,
|
|
433
|
+
the shortcut fires `onShortcut` on the `<CodeEditor>` component with the registered name.
|
|
434
|
+
|
|
435
|
+
Key strings follow [CodeMirror's key binding format](https://codemirror.net/docs/ref/#view.KeyBinding):
|
|
436
|
+
`Mod` resolves to `Cmd` on macOS / iOS and `Ctrl` on Android / Windows.
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
api.editor.registerShortcut(key: string, name: string): Promise<void>
|
|
440
|
+
// Register a keyboard shortcut. When the key combination is pressed, onShortcut(name)
|
|
441
|
+
// fires on the React Native side.
|
|
442
|
+
// e.g. await api.editor.registerShortcut('Mod-s', 'save')
|
|
443
|
+
// await api.editor.registerShortcut('Mod-Enter', 'run')
|
|
444
|
+
// await api.editor.registerShortcut('Mod-Shift-Enter', 'fullRun')
|
|
445
|
+
|
|
446
|
+
api.editor.unregisterShortcut(key: string): Promise<void>
|
|
447
|
+
// Remove a previously registered shortcut.
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Example:**
|
|
451
|
+
|
|
452
|
+
```tsx
|
|
453
|
+
<CodeEditor
|
|
454
|
+
onInitialized={(api) => {
|
|
455
|
+
void api.editor.registerShortcut('Mod-s', 'save');
|
|
456
|
+
void api.editor.registerShortcut('Mod-Enter', 'run');
|
|
457
|
+
}}
|
|
458
|
+
onShortcut={(name) => {
|
|
459
|
+
if (name === 'save') handleSave();
|
|
460
|
+
if (name === 'run') handleRun();
|
|
461
|
+
}}
|
|
462
|
+
...
|
|
463
|
+
/>
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Shortcuts are registered with `Prec.highest` so they take priority over CodeMirror's
|
|
467
|
+
built-in key bindings.
|
|
468
|
+
|
|
429
469
|
### Advanced
|
|
430
470
|
|
|
431
471
|
```ts
|
package/lib/module/CodeEditor.js
CHANGED
|
@@ -38,6 +38,7 @@ const CodeEditor = ({
|
|
|
38
38
|
onError,
|
|
39
39
|
onContentUpdate,
|
|
40
40
|
onSelectionChange,
|
|
41
|
+
onShortcut,
|
|
41
42
|
onWebViewRefUpdated,
|
|
42
43
|
onLoad,
|
|
43
44
|
onLoadStart,
|
|
@@ -63,6 +64,7 @@ const CodeEditor = ({
|
|
|
63
64
|
onInitialized,
|
|
64
65
|
onContentUpdate,
|
|
65
66
|
onSelectionChange,
|
|
67
|
+
onShortcut,
|
|
66
68
|
onHistorySizeUpdate,
|
|
67
69
|
onLog,
|
|
68
70
|
onError,
|
|
@@ -72,6 +74,7 @@ const CodeEditor = ({
|
|
|
72
74
|
onInitialized,
|
|
73
75
|
onContentUpdate,
|
|
74
76
|
onSelectionChange,
|
|
77
|
+
onShortcut,
|
|
75
78
|
onHistorySizeUpdate,
|
|
76
79
|
onLog,
|
|
77
80
|
onError,
|
|
@@ -103,6 +106,7 @@ const CodeEditor = ({
|
|
|
103
106
|
callbacksRef.current.onContentUpdate(value);
|
|
104
107
|
},
|
|
105
108
|
onSelectionChange: text => callbacksRef.current.onSelectionChange?.(text),
|
|
109
|
+
onShortcut: name => callbacksRef.current.onShortcut?.(name),
|
|
106
110
|
onHistorySizeUpdate: size => callbacksRef.current.onHistorySizeUpdate(size),
|
|
107
111
|
onLog: (...args) => callbacksRef.current.onLog(...args),
|
|
108
112
|
onError: error => callbacksRef.current.onError(error)
|
package/lib/module/WebViewAPI.js
CHANGED
|
@@ -99,6 +99,10 @@ class WebViewAPI {
|
|
|
99
99
|
this.handlers.onSelectionChange?.(typeof parsed.data === 'string' ? parsed.data : '');
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
+
if (parsed?.type === '__shortcut__') {
|
|
103
|
+
this.handlers.onShortcut?.(typeof parsed.data === 'string' ? parsed.data : '');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
102
106
|
} catch {
|
|
103
107
|
// not JSON — fall through to DDA
|
|
104
108
|
}
|
|
@@ -9,6 +9,7 @@ export interface CodeEditorProps {
|
|
|
9
9
|
onError: (error: unknown) => void;
|
|
10
10
|
onContentUpdate: (content: string) => void;
|
|
11
11
|
onSelectionChange?: (text: string) => void;
|
|
12
|
+
onShortcut?: (name: string) => void;
|
|
12
13
|
onWebViewRefUpdated?: (webView: WebView | null) => void;
|
|
13
14
|
onLoad?: (event: unknown) => void;
|
|
14
15
|
onLoadStart?: (event: unknown) => void;
|
|
@@ -28,6 +29,6 @@ export interface CodeEditorProps {
|
|
|
28
29
|
/** Override the default editor HTML URI (useful on iOS where the bundle path varies). */
|
|
29
30
|
editorUri?: string;
|
|
30
31
|
}
|
|
31
|
-
declare const CodeEditor: ({ onInitialized, onHistorySizeUpdate, onLog, onError, onContentUpdate, onSelectionChange, onWebViewRefUpdated, onLoad, onLoadStart, onLoadProgress, onLoadEnd, onNavigationStateChange, renderBlockingView, language, extensions, theme, content, viewport, allowFileAccess, editorUri, }: CodeEditorProps) => import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
declare const CodeEditor: ({ onInitialized, onHistorySizeUpdate, onLog, onError, onContentUpdate, onSelectionChange, onShortcut, onWebViewRefUpdated, onLoad, onLoadStart, onLoadProgress, onLoadEnd, onNavigationStateChange, renderBlockingView, language, extensions, theme, content, viewport, allowFileAccess, editorUri, }: CodeEditorProps) => import("react/jsx-runtime").JSX.Element;
|
|
32
33
|
export default CodeEditor;
|
|
33
34
|
//# sourceMappingURL=CodeEditor.d.ts.map
|
|
@@ -60,6 +60,8 @@ export interface EditorAPI {
|
|
|
60
60
|
startCompletion(): Promise<void>;
|
|
61
61
|
setCompletions(items: CompletionItem[]): Promise<void>;
|
|
62
62
|
setSoftKeyboard(enabled: boolean): Promise<void>;
|
|
63
|
+
registerShortcut(key: string, name: string): Promise<void>;
|
|
64
|
+
unregisterShortcut(key: string): Promise<void>;
|
|
63
65
|
loadExtension(moduleName: string): Promise<object>;
|
|
64
66
|
destroy(): Promise<void>;
|
|
65
67
|
}
|
|
@@ -4,6 +4,7 @@ export interface WebViewAPIHandlers {
|
|
|
4
4
|
onHistorySizeUpdate: (size: HistorySize) => void;
|
|
5
5
|
onContentUpdate: (content: string) => void;
|
|
6
6
|
onSelectionChange?: (text: string) => void;
|
|
7
|
+
onShortcut?: (name: string) => void;
|
|
7
8
|
onLog: (...args: unknown[]) => void;
|
|
8
9
|
onError: (error: unknown) => void;
|
|
9
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actualwave/react-native-codeditor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CodeMirror 6 code editor for React Native, embedded in a WebView with a full bidirectional RPC bridge. Offline, no CDN required.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/assets/editor.html
CHANGED
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
|
|
144
144
|
// Separate compartment for the theme so setTheme and setExtensions never clobber each other.
|
|
145
145
|
// appendConfig adds it to the live state; themeCompartment.reconfigure() updates it in-place.
|
|
146
|
-
const { Compartment, StateEffect, Transaction } = await requireAsyncModule('@codemirror/state');
|
|
146
|
+
const { Compartment, StateEffect, Transaction, Prec } = await requireAsyncModule('@codemirror/state');
|
|
147
147
|
const themeCompartment = new Compartment();
|
|
148
148
|
editor.view.dispatch({ effects: StateEffect.appendConfig.of(themeCompartment.of([])) });
|
|
149
149
|
|
|
@@ -189,6 +189,19 @@
|
|
|
189
189
|
]),
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
+
// Compartment for shortcuts registered from the React Native side at runtime.
|
|
193
|
+
const shortcutCompartment = new Compartment();
|
|
194
|
+
editor.view.dispatch({ effects: StateEffect.appendConfig.of(Prec.highest(shortcutCompartment.of([]))) });
|
|
195
|
+
|
|
196
|
+
const _registeredShortcuts = new Map();
|
|
197
|
+
const _updateShortcutKeymap = () => {
|
|
198
|
+
const entries = Array.from(_registeredShortcuts.entries()).map(([key, name]) => ({
|
|
199
|
+
key,
|
|
200
|
+
run: () => { _rnPost('__shortcut__', name); return true; },
|
|
201
|
+
}));
|
|
202
|
+
editor.view.dispatch({ effects: shortcutCompartment.reconfigure(keymap.of(entries)) });
|
|
203
|
+
};
|
|
204
|
+
|
|
192
205
|
// Spread EditorController methods directly — getValue, setValue, setLanguage,
|
|
193
206
|
// setExtensions, loadExtension, destroy — then add methods not in EditorController.
|
|
194
207
|
const { getValue, setValue, setLanguage, setExtensions, loadExtension, destroy } = editor;
|
|
@@ -344,6 +357,16 @@
|
|
|
344
357
|
effects: EditorView.scrollIntoView(head, margin !== undefined ? { y: 'center' } : {}),
|
|
345
358
|
});
|
|
346
359
|
},
|
|
360
|
+
|
|
361
|
+
registerShortcut: (key, name) => {
|
|
362
|
+
_registeredShortcuts.set(key, name);
|
|
363
|
+
_updateShortcutKeymap();
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
unregisterShortcut: (key) => {
|
|
367
|
+
_registeredShortcuts.delete(key);
|
|
368
|
+
_updateShortcutKeymap();
|
|
369
|
+
},
|
|
347
370
|
});
|
|
348
371
|
|
|
349
372
|
if (config.viewport) {
|