@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 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
@@ -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)
@@ -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.0.0",
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",
@@ -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) {