@codenotch/codenotch.react 1.0.81 → 2.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/README.md CHANGED
@@ -2,17 +2,16 @@
2
2
 
3
3
  React bindings for [Codenotch](https://codenotch.com) applications.
4
4
 
5
- Codenotch is a full-stack development platform: a project combines server-side **BPMN processes**, **SQL tables** / **NoSQL documents**, **i18n** translations and **React** apps. This package is the client-side bridge between those React apps and the Codenotch runtime: it lets a component start BPMN processes, run SioQL queries, translate i18n keys, listen to real-time signals, and manage theme/language.
5
+ Codenotch is a full-stack development platform: a project combines server-side **BPMN processes**, **SQL tables** / **NoSQL documents**, **i18n** translations and **React** apps. This package is the client-side bridge between those React apps and the Codenotch runtime: it lets a component start BPMN processes, run CNQL queries, translate i18n keys, listen to real-time signals, and manage theme/language.
6
6
 
7
- > **Requirements** — Codenotch apps run on **React 16** (`react@^16.14.0`). Do not use React 17/18 APIs (`createRoot`, `useId`, automatic JSX runtime…); hooks work fine.
7
+ > **Requirements** — Codenotch apps run on **React 19** (`react@^19`, `react-dom@^19`). The package uses the automatic JSX runtime and `createRoot`; all modern React APIs (hooks, `useId`, `use`, Actions…) are available.
8
8
 
9
9
  ## Quick start
10
10
 
11
11
  ```tsx
12
- import React from 'react';
13
12
  import { useCodenotch } from 'codenotch-react';
14
13
 
15
- const MyApp: React.FC = () => {
14
+ const MyApp = () => {
16
15
  const cn = useCodenotch();
17
16
 
18
17
  return <div className="p-4">
@@ -23,7 +22,41 @@ const MyApp: React.FC = () => {
23
22
  export default MyApp;
24
23
  ```
25
24
 
26
- `useCodenotch()` is **not a React hook** despite its name: it is a plain function that returns the API object bound to the current environment. It can be called anywhere components, event handlers, plain modules.
25
+ `useCodenotch()` is a real React hook: the returned object is stable across renders and is replaced re-rendering the component — whenever the environment changes (`setLanguage`, `setTheme`, `init`). Regular hook rules apply. The same API is available everywhere else through two non-hook entry points:
26
+
27
+ | Where | Use |
28
+ |-------|-----|
29
+ | Function component / custom hook | `const cn = useCodenotch();` — re-renders on language/theme change. |
30
+ | Class component | `export default withCodenotch(MyClass);` — injects `this.props.cn`, re-renders on change, forwards `ref`. |
31
+ | Event handler, plain module, service, anywhere | `getCodenotch()` — plain function, never stale, no re-render. |
32
+ | Manual subscription (rare) | `onCodenotchChange(listener)` → `IDisposable`. |
33
+
34
+ ```tsx
35
+ import React from 'react';
36
+ import { withCodenotch, WithCodenotchProps } from 'codenotch-react';
37
+
38
+ interface Props extends WithCodenotchProps {
39
+ userId: string;
40
+ }
41
+
42
+ class TodoList extends React.Component<Props> {
43
+ render() {
44
+ return <h1>{this.props.cn.i18n('todos.title')}</h1>;
45
+ }
46
+ }
47
+
48
+ export default withCodenotch(TodoList); // <TodoList userId="42" /> — `cn` is injected
49
+ ```
50
+
51
+ ```ts
52
+ // services/todos.ts — outside React
53
+ import { getCodenotch } from 'codenotch-react';
54
+
55
+ export async function loadTodos(userId: string) {
56
+ const result = await getCodenotch().startProcess('getTodos', 'start', { UserId: userId });
57
+ return result.output.todos;
58
+ }
59
+ ```
27
60
 
28
61
  The environment (cluster URL, service name, language, translations…) is set up by `init(envVariables)`, which the Codenotch runtime calls automatically from the page hosting the application. You only call `init` yourself in custom hosting scenarios.
29
62
 
@@ -34,9 +67,9 @@ The environment (cluster URL, service name, language, translations…) is set up
34
67
  | `cn.env` | Readonly environment: `clusterUrl`, `serviceName`, `tenantName`, `accessToken`, `language`, `theme`, `i18n`, `appManifest`, `projectManifest`. |
35
68
  | `cn.i18n(key, ...args)` | Translate a key for the current language, filling `{0}`, `{1}`, … placeholders. |
36
69
  | `cn.startProcess(name, startNodeId, inputs)` | Start a server-side BPMN process and await its result. |
37
- | `cn.requestSioql(sioql, verbose?)` | Run a SioQL query (XML, SELECT-only) against the project's tables. |
70
+ | `cn.requestCnql(cnql, verbose?)` | Run a CNQL query (XML, SELECT-only) against the project's tables. |
38
71
  | `cn.listenSignal(signalId, callback)` | Subscribe to real-time signals emitted by BPMN processes. |
39
- | `cn.showDialog(jsx)` | Render a JSX element in a fullscreen modal dialog. |
72
+ | `cn.showDialog(node)` | Render a React node in a fullscreen modal dialog (own React root, unmounted on `close()`). |
40
73
  | `cn.setTheme(t)` / `cn.getTheme()` | `'light' \| 'dark'`; `setTheme` also toggles the class on `<html>` (Tailwind `dark:`). |
41
74
  | `cn.setLanguage(l)` / `cn.getLanguage()` / `cn.getLanguages()` | Current language and the languages declared in `manifest.json`. |
42
75
  | `cn.getProjectFile(path)` / `cn.getProjectFileUrl(path)` | Read/link a file of the deployed project. |
@@ -60,19 +93,19 @@ if (!result.isError) {
60
93
  }
61
94
  ```
62
95
 
63
- ## Querying tables with SioQL
96
+ ## Querying tables with CNQL
64
97
 
65
- SioQL is Codenotch's XML query language over the project's SQL tables (SELECT only — writes go through BPMN processes). The root `xmlns` must be the project's `serviceName`; each queried table's `Ref` attribute names its result set:
98
+ CNQL is Codenotch's XML query language over the project's SQL tables (SELECT only — writes go through BPMN processes). The root `xmlns` must be the project's `serviceName`; each queried table's `Ref` attribute names its result set:
66
99
 
67
100
  ```tsx
68
- const data = await cn.requestSioql(`
69
- <SioQL xmlns="myproject" PageSize="10" PageIndex="0">
101
+ const data = await cn.requestCnql(`
102
+ <CNQL xmlns="myproject" PageSize="10" PageIndex="0">
70
103
  <Users Ref="results">
71
104
  <Id />
72
105
  <Email />
73
106
  <IsAdmin Equal="true" />
74
107
  </Users>
75
- </SioQL>`);
108
+ </CNQL>`);
76
109
 
77
110
  console.log(data.results); // [{ Id: '...', Email: '...' }, ...]
78
111
  ```
@@ -84,7 +117,8 @@ Translations come from the project's `.i18n.csv` files and are injected into `cn
84
117
  ```tsx
85
118
  cn.i18n('welcome'); // "Bienvenue"
86
119
  cn.i18n('greeting', 'Ada', 3); // "Bonjour Ada, 3 messages" (from "Bonjour {0}, {1} messages")
87
- cn.setLanguage('en'); // switch language at runtime
120
+ cn.setLanguage('en'); // switch language at runtime — every component using
121
+ // useCodenotch() / withCodenotch() re-renders
88
122
  ```
89
123
 
90
124
  ## Real-time signals
@@ -94,12 +128,15 @@ BPMN processes can broadcast signals; subscribe from the UI with `listenSignal`:
94
128
  ```tsx
95
129
  useEffect(() => {
96
130
  let sub: IDisposable | undefined;
97
- cn.listenSignal('todosChanged', (signal) => refresh(signal.data))
98
- .then(s => { sub = s; });
99
- return () => sub?.dispose();
131
+ let cancelled = false;
132
+ getCodenotch().listenSignal('todosChanged', (signal) => refresh(signal.data))
133
+ .then(s => { if (cancelled) s.dispose(); else sub = s; });
134
+ return () => { cancelled = true; sub?.dispose(); };
100
135
  }, []);
101
136
  ```
102
137
 
138
+ Two details: the `cancelled` flag matters under React 19's StrictMode, which mounts / unmounts / remounts effects in development — without it the first subscription would leak when the cleanup runs before the promise resolves. And the effect uses `getCodenotch()` rather than the `cn` from `useCodenotch()`: the hook's object changes on every language/theme change, so listing it in the dependency array would re-subscribe on each change, while omitting it trips `react-hooks/exhaustive-deps`. `getCodenotch()` sidesteps both.
139
+
103
140
  ## Dialogs
104
141
 
105
142
  ```tsx
@@ -111,7 +148,7 @@ const dialog = cn.showDialog(
111
148
  );
112
149
  ```
113
150
 
114
- The dialog stays open until you call `dialog.close()`.
151
+ The dialog stays open until you call `dialog.close()`, which unmounts the React tree and removes the `<dialog>` element. The content is rendered in its own React root, so it does not share context (providers) with the calling component.
115
152
 
116
153
  ## Type-safe processes and translation keys
117
154
 
@@ -140,6 +177,7 @@ With these in the compilation, keys, inputs and outputs are strictly checked and
140
177
  ## Notes for AI assistants
141
178
 
142
179
  - The complete typed API surface is in `dist/index.d.ts`; the readable implementation ships in `src/` (entry point `src/index.ts`).
143
- - `useCodenotch()` is a plain function, not a hook no hook rules apply.
180
+ - `useCodenotch()` is a real hook (hook rules apply; re-renders on language/theme change). In class components use `withCodenotch(Component)` and read `this.props.cn`; in event handlers, services, plain modules or effects use `getCodenotch()`. Never call `useCodenotch()` outside a function component / custom hook.
181
+ - The object returned by `useCodenotch()` changes when the environment changes: fine in `useMemo` deps, but in `useEffect` prefer `getCodenotch()` unless the effect should re-run on language/theme change.
144
182
  - Never hand-edit a project's `typings/i18n.d.ts` / `typings/process.d.ts`: they are regenerated by the Codenotch IDE.
145
- - Codenotch apps are React 16 + Tailwind CSS; SioQL is SELECT-only (writes go through BPMN processes via `startProcess`).
183
+ - Codenotch apps are React 19 + Tailwind CSS (automatic JSX runtime — no `import React` needed for JSX); CNQL is SELECT-only (writes go through BPMN processes via `startProcess`).
@@ -24,7 +24,7 @@ export default class CodeEditor extends React.Component<ICodeEditorProps> {
24
24
  private isIframeReady;
25
25
  private onChangeTimeoutHandle;
26
26
  private onMessageHandler;
27
- refFrame: React.RefObject<HTMLIFrameElement>;
27
+ refFrame: React.RefObject<HTMLIFrameElement | null>;
28
28
  constructor(props: ICodeEditorProps);
29
29
  setValue(value: string): void;
30
30
  componentDidMount(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"CodeEditor.d.ts","sourceRoot":"","sources":["../../src/components/CodeEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAEnC,UAAU,gBAAgB;IACtB,qFAAqF;IACrF,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,KAAK,CAAC,SAAS,CAAC,gBAAgB,CAAC;IACrE,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,gBAAgB,CAAM;IAC9B,QAAQ,qCAAwC;gBAEpC,KAAK,EAAE,gBAAgB;IAKnC,QAAQ,CAAC,KAAK,EAAE,MAAM;IAStB,iBAAiB,IAAI,IAAI;IAKzB,oBAAoB,IAAI,IAAI;IAI5B,kBAAkB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAoBrD,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IA4EnB,MAAM;CAeT"}
1
+ {"version":3,"file":"CodeEditor.d.ts","sourceRoot":"","sources":["../../src/components/CodeEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAEnC,UAAU,gBAAgB;IACtB,qFAAqF;IACrF,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,KAAK,CAAC,SAAS,CAAC,gBAAgB,CAAC;IACrE,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,gBAAgB,CAAM;IAC9B,QAAQ,4CAAwC;gBAEpC,KAAK,EAAE,gBAAgB;IAKnC,QAAQ,CAAC,KAAK,EAAE,MAAM;IAStB,iBAAiB,IAAI,IAAI;IAKzB,oBAAoB,IAAI,IAAI;IAI5B,kBAAkB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAoBrD,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IA4EnB,MAAM;CAgBT"}
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
7
  const react_1 = __importDefault(require("react"));
7
8
  /**
8
9
  * Light Monaco code editor embedded in a sandboxed iframe
@@ -92,68 +93,68 @@ class CodeEditor extends react_1.default.Component {
92
93
  if (!iframe)
93
94
  return;
94
95
  const vsBase = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs';
95
- const html = `<!DOCTYPE html>
96
- <html>
97
- <head>
98
- <meta charset="utf-8" />
99
- <meta name="viewport" content="width=device-width, initial-scale=1" />
100
- <style>
101
- html, body, #container { height: 100%; width: 100%; margin: 0; padding: 0; overflow: hidden; }
102
- </style>
103
- <script>
104
- (function(){
105
- var vsBase = ${JSON.stringify(vsBase)};
106
- // AMD loader
107
- var s = document.createElement('script');
108
- s.src = vsBase + '/loader.js';
109
- s.onload = function(){
110
- // Configure and load monaco
111
- window.require.config({ paths: { vs: vsBase } });
112
- window.require(['vs/editor/editor.main'], function(){
113
- var editor;
114
-
115
- function post(msg){ parent.postMessage(Object.assign({__from:'MonacoIframe'}, msg), '*'); }
116
-
117
- function ensureEditor(){
118
- if (editor) return editor;
119
- editor = monaco.editor.create(document.getElementById('container'), {
120
- value: '',
121
- language: 'plaintext',
122
- theme: 'vs',
123
- automaticLayout: true,
124
- minimap: { enabled: false },
125
- scrollBeyondLastLine: true,
126
- wordWrap: "on"
127
- });
128
- editor.onDidChangeModelContent(function(){
129
- var v = editor.getValue();
130
- post({ type: 'change', value: v });
131
- });
132
- return editor;
133
- }
134
-
135
- window.addEventListener('message', function(ev){
136
- var data = ev.data || {}; if (data.__from !== 'MonacoIframe') return;
137
- if (data.type === 'init' || data.type === 'update'){
138
- var ed = ensureEditor();
139
- if (typeof data.value === 'string' && ed.getValue() !== data.value){ ed.setValue(data.value); }
140
- if (typeof data.language === 'string') { monaco.editor.setModelLanguage(ed.getModel(), data.language); }
141
- if (typeof data.theme === 'string') { monaco.editor.setTheme(data.theme); }
142
- if (typeof data.readOnly === 'boolean') { ed.updateOptions({ readOnly: data.readOnly }); }
143
- }
144
- });
145
-
146
- // Signal ready after monaco is loaded
147
- post({ type: 'ready' });
148
- });
149
- };
150
- document.head.appendChild(s);
151
- })();
152
- </script>
153
- </head>
154
- <body>
155
- <div id="container"></div>
156
- </body>
96
+ const html = `<!DOCTYPE html>
97
+ <html>
98
+ <head>
99
+ <meta charset="utf-8" />
100
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
101
+ <style>
102
+ html, body, #container { height: 100%; width: 100%; margin: 0; padding: 0; overflow: hidden; }
103
+ </style>
104
+ <script>
105
+ (function(){
106
+ var vsBase = ${JSON.stringify(vsBase)};
107
+ // AMD loader
108
+ var s = document.createElement('script');
109
+ s.src = vsBase + '/loader.js';
110
+ s.onload = function(){
111
+ // Configure and load monaco
112
+ window.require.config({ paths: { vs: vsBase } });
113
+ window.require(['vs/editor/editor.main'], function(){
114
+ var editor;
115
+
116
+ function post(msg){ parent.postMessage(Object.assign({__from:'MonacoIframe'}, msg), '*'); }
117
+
118
+ function ensureEditor(){
119
+ if (editor) return editor;
120
+ editor = monaco.editor.create(document.getElementById('container'), {
121
+ value: '',
122
+ language: 'plaintext',
123
+ theme: 'vs',
124
+ automaticLayout: true,
125
+ minimap: { enabled: false },
126
+ scrollBeyondLastLine: true,
127
+ wordWrap: "on"
128
+ });
129
+ editor.onDidChangeModelContent(function(){
130
+ var v = editor.getValue();
131
+ post({ type: 'change', value: v });
132
+ });
133
+ return editor;
134
+ }
135
+
136
+ window.addEventListener('message', function(ev){
137
+ var data = ev.data || {}; if (data.__from !== 'MonacoIframe') return;
138
+ if (data.type === 'init' || data.type === 'update'){
139
+ var ed = ensureEditor();
140
+ if (typeof data.value === 'string' && ed.getValue() !== data.value){ ed.setValue(data.value); }
141
+ if (typeof data.language === 'string') { monaco.editor.setModelLanguage(ed.getModel(), data.language); }
142
+ if (typeof data.theme === 'string') { monaco.editor.setTheme(data.theme); }
143
+ if (typeof data.readOnly === 'boolean') { ed.updateOptions({ readOnly: data.readOnly }); }
144
+ }
145
+ });
146
+
147
+ // Signal ready after monaco is loaded
148
+ post({ type: 'ready' });
149
+ });
150
+ };
151
+ document.head.appendChild(s);
152
+ })();
153
+ </script>
154
+ </head>
155
+ <body>
156
+ <div id="container"></div>
157
+ </body>
157
158
  </html>`;
158
159
  const doc = iframe.contentWindow?.document;
159
160
  if (!doc)
@@ -163,12 +164,13 @@ class CodeEditor extends react_1.default.Component {
163
164
  doc.close();
164
165
  }
165
166
  render() {
166
- let style = this.props.style || {};
167
+ // Never mutate props: copy the style object before applying defaults.
168
+ const style = { ...this.props.style };
167
169
  style.width = style.width || '100%';
168
170
  style.height = style.height || '100%';
169
171
  style.border = style.border || '0';
170
172
  style.outline = style.outline || '0';
171
- return react_1.default.createElement("iframe", { style: style, title: "Codenotch", ref: this.refFrame, className: this.props.className, sandbox: "allow-scripts allow-same-origin" });
173
+ return (0, jsx_runtime_1.jsx)("iframe", { style: style, title: "Codenotch", ref: this.refFrame, className: this.props.className, sandbox: "allow-scripts allow-same-origin" });
172
174
  }
173
175
  }
174
176
  exports.default = CodeEditor;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { ICodenotchApi, ICodenotchEnv } from "./models/Codenotch";
1
+ import { type ComponentType, type ComponentProps, type FunctionComponent, type RefAttributes } from "react";
2
+ import { IDisposable } from "./models/Misc";
3
+ import { ICodenotchApi, ICodenotchEnv, WithCodenotchProps } from "./models/Codenotch";
2
4
  import CodeEditor from "./components/CodeEditor";
3
5
  /**
4
6
  * The Codenotch runtime environment, populated by {@link init}.
@@ -35,26 +37,100 @@ declare const env: ICodenotchEnv;
35
37
  */
36
38
  declare function init(envVariables: any): void;
37
39
  /**
38
- * Return the Codenotch client API bound to the current environment.
40
+ * Return the Codenotch client API bound to the current environment, from
41
+ * anywhere: event handlers, plain modules, class components, services…
39
42
  *
40
- * Despite its name this is NOT a React hook it is a plain function with no
41
- * hook rules attached: it can be called anywhere (components, handlers, plain
42
- * modules). `init()` must have been called first, which the Codenotch runtime
43
- * does automatically when serving the application.
43
+ * This is a plain function (not a hook). The returned object is never stale
44
+ * its methods always read the live environment but it does not trigger any
45
+ * re-render when the language or theme changes: inside React components prefer
46
+ * {@link useCodenotch} (function components) or {@link withCodenotch}
47
+ * (class components), which do.
44
48
  *
45
- * @returns The Codenotch API: BPMN processes, SioQL queries, i18n, signals, theme…
49
+ * `init()` must have been called first, which the Codenotch runtime does
50
+ * automatically when serving the application.
51
+ *
52
+ * @example
53
+ * import { getCodenotch } from 'codenotch-react';
54
+ *
55
+ * export async function loadTodos(userId: string) {
56
+ * const result = await getCodenotch().startProcess('getTodos', 'start', { UserId: userId });
57
+ * return result.output.todos;
58
+ * }
59
+ */
60
+ declare function getCodenotch(): ICodenotchApi;
61
+ /**
62
+ * React hook returning the Codenotch client API.
63
+ *
64
+ * The reference is stable across renders and only changes when the environment
65
+ * changes (`setLanguage`, `setTheme`, `init`), in which case every component
66
+ * using the hook re-renders — so `cn.i18n(...)` output follows the current
67
+ * language automatically, and the object is safe to use in `useMemo` /
68
+ * `useEffect` dependency arrays.
69
+ *
70
+ * Regular hook rules apply (call it unconditionally at the top of a function
71
+ * component or a custom hook). Outside of components — handlers defined in
72
+ * plain modules, services, class components — use {@link getCodenotch} or
73
+ * {@link withCodenotch} instead.
74
+ *
75
+ * @returns The Codenotch API: BPMN processes, CNQL queries, i18n, signals, theme…
46
76
  * @example
47
77
  * import { useCodenotch } from 'codenotch-react';
48
78
  *
49
- * const MyApp: React.FC = () => {
79
+ * const MyApp = () => {
50
80
  * const cn = useCodenotch();
51
81
  * return <h1>{cn.i18n('welcome')}</h1>;
52
82
  * };
53
83
  */
54
84
  declare function useCodenotch(): ICodenotchApi;
55
- export { env, useCodenotch, init, CodeEditor };
85
+ /**
86
+ * Register a listener called each time the Codenotch environment changes
87
+ * (`setLanguage`, `setTheme`, `init`). Escape hatch for code that cannot use
88
+ * {@link useCodenotch} or {@link withCodenotch}, e.g. a class component that
89
+ * wants to `forceUpdate()` itself, or a non-React module caching translations.
90
+ *
91
+ * @returns A disposable — call `dispose()` to stop listening.
92
+ * @example
93
+ * componentDidMount() {
94
+ * this.sub = onCodenotchChange(() => this.forceUpdate());
95
+ * }
96
+ * componentWillUnmount() {
97
+ * this.sub.dispose();
98
+ * }
99
+ */
100
+ declare function onCodenotchChange(listener: () => void): IDisposable;
101
+ /** Props of a component wrapped by {@link withCodenotch}, without the injected `cn`. */
102
+ type WithoutCodenotchProps<P> = Omit<P, keyof WithCodenotchProps>;
103
+ /** Instance type of a component (class components only), used to type the forwarded `ref`. */
104
+ type ComponentInstance<C> = C extends new (...args: any[]) => infer I ? I : never;
105
+ /**
106
+ * Higher-order component injecting the Codenotch API as a `cn` prop.
107
+ *
108
+ * Meant for class components, which cannot call {@link useCodenotch}: the
109
+ * wrapped component receives `this.props.cn` and re-renders whenever the
110
+ * environment changes (language, theme…), exactly like the hook. A `ref`
111
+ * passed to the wrapper is forwarded to the wrapped component instance.
112
+ *
113
+ * @param Component A component whose props extend {@link WithCodenotchProps}.
114
+ * @returns A component with the same props minus `cn`.
115
+ * @example
116
+ * import { withCodenotch, WithCodenotchProps } from 'codenotch-react';
117
+ *
118
+ * interface Props extends WithCodenotchProps {
119
+ * userId: string;
120
+ * }
121
+ *
122
+ * class TodoList extends React.Component<Props> {
123
+ * render() {
124
+ * return <h1>{this.props.cn.i18n('todos.title')}</h1>;
125
+ * }
126
+ * }
127
+ *
128
+ * export default withCodenotch(TodoList);
129
+ * // <TodoList userId="42" /> — `cn` is injected
130
+ */
131
+ declare function withCodenotch<C extends ComponentType<any>>(Component: C): FunctionComponent<WithoutCodenotchProps<ComponentProps<C>> & RefAttributes<ComponentInstance<C>>>;
132
+ export { env, useCodenotch, getCodenotch, withCodenotch, onCodenotchChange, init, CodeEditor };
56
133
  export * from "./models/Codenotch";
57
134
  export * from "./models/Misc";
58
- export * from "./models/AppManifestModels";
59
135
  export * from "@codenotch/codenotch.core";
60
136
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAoB,aAAa,EAAkB,MAAM,oBAAoB,CAAC;AAEpG,OAAO,UAAU,MAAM,yBAAyB,CAAC;AAIjD;;;GAGG;AACH,QAAA,MAAM,GAAG,EAAE,aAAkB,CAAC;AAM9B;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,iBAAS,IAAI,CAAC,YAAY,EAAE,GAAG,GAAG,IAAI,CAyFrC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,iBAAS,YAAY,IAAI,aAAa,CAsNrC;AAED,OAAO,EACH,GAAG,EACH,YAAY,EACZ,IAAI,EACJ,UAAU,EACb,CAAC;AAMF,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,iBAAiB,EAAkB,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAKjK,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAoB,aAAa,EAAkB,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExH,OAAO,UAAU,MAAM,yBAAyB,CAAC;AAIjD;;;GAGG;AACH,QAAA,MAAM,GAAG,EAAE,aAAkB,CAAC;AA6B9B;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,iBAAS,IAAI,CAAC,YAAY,EAAE,GAAG,GAAG,IAAI,CA2FrC;AA2OD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,iBAAS,YAAY,IAAI,aAAa,CAErC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,iBAAS,YAAY,IAAI,aAAa,CAErC;AAED;;;;;;;;;;;;;;GAcG;AACH,iBAAS,iBAAiB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,WAAW,CAG5D;AAED,wFAAwF;AACxF,KAAK,qBAAqB,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,kBAAkB,CAAC,CAAC;AAElE,8FAA8F;AAC9F,KAAK,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,iBAAS,aAAa,CAAC,CAAC,SAAS,aAAa,CAAC,GAAG,CAAC,EAC/C,SAAS,EAAE,CAAC,GACb,iBAAiB,CAAC,qBAAqB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAQnG;AAED,OAAO,EACH,GAAG,EACH,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,IAAI,EACJ,UAAU,EACb,CAAC;AAMF,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC"}