@codenotch/codenotch.react 1.0.82 → 2.0.1

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.
@@ -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}.
@@ -13,7 +15,6 @@ declare const env: ICodenotchEnv;
13
15
  * any other source). It must be called before `useCodenotch()` is used.
14
16
  *
15
17
  * Recognized key formats:
16
- * - `i18n.<key>.<language>` — a translation, stored in `env.i18n[language][key]`.
17
18
  * The first language found becomes the current language if none is set.
18
19
  * - `GLOBAL.<name>` — JSON value assigned to `window[name]` (or `globalThis`).
19
20
  * - `appManifest` / `projectManifest` — parsed as JSON into the environment.
@@ -28,31 +29,80 @@ declare const env: ICodenotchEnv;
28
29
  * init({
29
30
  * clusterUrl: 'https://cluster.example.com',
30
31
  * serviceName: 'myproject',
31
- * tenantName: 'acme',
32
- * 'i18n.welcome.en': 'Welcome',
33
- * 'i18n.welcome.fr': 'Bienvenue'
32
+ * tenantName: 'acme'
34
33
  * });
35
34
  */
36
35
  declare function init(envVariables: any): void;
37
36
  /**
38
- * Return the Codenotch client API bound to the current environment.
37
+ * Return the Codenotch client API bound to the current environment, from
38
+ * anywhere: event handlers, plain modules, class components, services…
39
39
  *
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.
40
+ * This is a plain function (not a hook). The returned object is never stale
41
+ * its methods always read the live environment but it does not trigger any
42
+ * re-render when the language or theme changes: inside React components prefer
43
+ * {@link useCodenotch} (function components) or {@link withCodenotch}
44
+ * (class components), which do.
45
+ *
46
+ * `init()` must have been called first, which the Codenotch runtime does
47
+ * automatically when serving the application.
44
48
  *
45
- * @returns The Codenotch API: BPMN processes, CNQL queries, i18n, signals, theme…
46
49
  * @example
47
- * import { useCodenotch } from 'codenotch-react';
50
+ * import { getCodenotch } from 'codenotch-react';
48
51
  *
49
- * const MyApp: React.FC = () => {
50
- * const cn = useCodenotch();
51
- * return <h1>{cn.i18n('welcome')}</h1>;
52
- * };
52
+ * export async function loadTodos(userId: string) {
53
+ * const result = await getCodenotch().startProcess('getTodos', 'start', { UserId: userId });
54
+ * return result.output.todos;
55
+ * }
56
+ */
57
+ declare function getCodenotch(): ICodenotchApi;
58
+ /**
59
+ * React hook returning the Codenotch client API.
60
+ *
61
+ * The reference is stable across renders and only changes when the environment
62
+ * changes (`setLanguage`, `setTheme`, `init`), in which case every component
63
+ * using the hook re-renders output follows the current
64
+ * language automatically, and the object is safe to use in `useMemo` /
65
+ * `useEffect` dependency arrays.
66
+ *
67
+ * Regular hook rules apply (call it unconditionally at the top of a function
68
+ * component or a custom hook). Outside of components — handlers defined in
69
+ * plain modules, services, class components — use {@link getCodenotch} or
70
+ * {@link withCodenotch} instead.
53
71
  */
54
72
  declare function useCodenotch(): ICodenotchApi;
55
- export { env, useCodenotch, init, CodeEditor };
73
+ /**
74
+ * Register a listener called each time the Codenotch environment changes
75
+ * (`setLanguage`, `setTheme`, `init`). Escape hatch for code that cannot use
76
+ * {@link useCodenotch} or {@link withCodenotch}, e.g. a class component that
77
+ * wants to `forceUpdate()` itself, or a non-React module caching translations.
78
+ *
79
+ * @returns A disposable — call `dispose()` to stop listening.
80
+ * @example
81
+ * componentDidMount() {
82
+ * this.sub = onCodenotchChange(() => this.forceUpdate());
83
+ * }
84
+ * componentWillUnmount() {
85
+ * this.sub.dispose();
86
+ * }
87
+ */
88
+ declare function onCodenotchChange(listener: () => void): IDisposable;
89
+ /** Props of a component wrapped by {@link withCodenotch}, without the injected `cn`. */
90
+ type WithoutCodenotchProps<P> = Omit<P, keyof WithCodenotchProps>;
91
+ /** Instance type of a component (class components only), used to type the forwarded `ref`. */
92
+ type ComponentInstance<C> = C extends new (...args: any[]) => infer I ? I : never;
93
+ /**
94
+ * Higher-order component injecting the Codenotch API as a `cn` prop.
95
+ *
96
+ * Meant for class components, which cannot call {@link useCodenotch}: the
97
+ * wrapped component receives `this.props.cn` and re-renders whenever the
98
+ * environment changes (language, theme…), exactly like the hook. A `ref`
99
+ * passed to the wrapper is forwarded to the wrapped component instance.
100
+ *
101
+ * @param Component A component whose props extend {@link WithCodenotchProps}.
102
+ * @returns A component with the same props minus `cn`.
103
+ */
104
+ declare function withCodenotch<C extends ComponentType<any>>(Component: C): FunctionComponent<WithoutCodenotchProps<ComponentProps<C>> & RefAttributes<ComponentInstance<C>>>;
105
+ export { env, useCodenotch, getCodenotch, withCodenotch, onCodenotchChange, init, CodeEditor };
56
106
  export * from "./models/Codenotch";
57
107
  export * from "./models/Misc";
58
108
  export * from "@codenotch/codenotch.core";
@@ -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,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;AAGjK,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;AAEjD;;;GAGG;AACH,QAAA,MAAM,GAAG,EAAE,aAAkB,CAAC;AA6B9B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,iBAAS,IAAI,CAAC,YAAY,EAAE,GAAG,GAAG,IAAI,CA0DrC;AA2KD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,iBAAS,YAAY,IAAI,aAAa,CAErC;AAED;;;;;;;;;;;;;GAaG;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;;;;;;;;;;GAUG;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;AAEF,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -19,14 +19,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.CodeEditor = exports.env = void 0;
21
21
  exports.useCodenotch = useCodenotch;
22
+ exports.getCodenotch = getCodenotch;
23
+ exports.withCodenotch = withCodenotch;
24
+ exports.onCodenotchChange = onCodenotchChange;
22
25
  exports.init = init;
23
- const react_dom_1 = __importDefault(require("react-dom"));
24
- const SignalR_1 = require("./core/SignalR");
25
- const ProcessUtils_1 = __importDefault(require("./core/ProcessUtils"));
26
+ const react_1 = require("react");
27
+ const react_dom_1 = require("react-dom");
28
+ const client_1 = require("react-dom/client");
26
29
  const uuid_1 = require("uuid");
27
30
  const CodeEditor_1 = __importDefault(require("./components/CodeEditor"));
28
31
  exports.CodeEditor = CodeEditor_1.default;
29
- var signalR = undefined;
30
32
  /**
31
33
  * The Codenotch runtime environment, populated by {@link init}.
32
34
  * Prefer reading it through `useCodenotch().env`.
@@ -36,6 +38,30 @@ exports.env = env;
36
38
  const MISSING_CLUSTER_URL_ERROR = "Codenotch cluster URL is not defined. Please set it in the Codenotch configuration.";
37
39
  const MISSING_SERVICE_NAME_ERROR = "Codenotch service name is not defined. Please set it in the Codenotch configuration.";
38
40
  const MISSING_TENANT_NAME_ERROR = "Codenotch tenant name is not defined. Please set it in the Codenotch configuration.";
41
+ // ---------------------------------------------------------------------------
42
+ // Change tracking. `env` is a plain mutable object; every mutation done through
43
+ // the public API (init, setTheme, setLanguage) goes through notify(), which
44
+ // rotates the API object (so React sees a new reference) and wakes up the
45
+ // subscribers: useCodenotch() via useSyncExternalStore, withCodenotch(),
46
+ // and any listener registered with onCodenotchChange().
47
+ // ---------------------------------------------------------------------------
48
+ const listeners = new Set();
49
+ let currentApi;
50
+ function notify() {
51
+ currentApi = createApi();
52
+ listeners.forEach((listener) => {
53
+ try {
54
+ listener();
55
+ }
56
+ catch (error) {
57
+ console.error("Error in Codenotch change listener:", error);
58
+ }
59
+ });
60
+ }
61
+ function subscribe(listener) {
62
+ listeners.add(listener);
63
+ return () => { listeners.delete(listener); };
64
+ }
39
65
  /**
40
66
  * Initialize the Codenotch environment from the given key-value pairs.
41
67
  *
@@ -44,7 +70,6 @@ const MISSING_TENANT_NAME_ERROR = "Codenotch tenant name is not defined. Please
44
70
  * any other source). It must be called before `useCodenotch()` is used.
45
71
  *
46
72
  * Recognized key formats:
47
- * - `i18n.<key>.<language>` — a translation, stored in `env.i18n[language][key]`.
48
73
  * The first language found becomes the current language if none is set.
49
74
  * - `GLOBAL.<name>` — JSON value assigned to `window[name]` (or `globalThis`).
50
75
  * - `appManifest` / `projectManifest` — parsed as JSON into the environment.
@@ -59,15 +84,12 @@ const MISSING_TENANT_NAME_ERROR = "Codenotch tenant name is not defined. Please
59
84
  * init({
60
85
  * clusterUrl: 'https://cluster.example.com',
61
86
  * serviceName: 'myproject',
62
- * tenantName: 'acme',
63
- * 'i18n.welcome.en': 'Welcome',
64
- * 'i18n.welcome.fr': 'Bienvenue'
87
+ * tenantName: 'acme'
65
88
  * });
66
89
  */
67
90
  function init(envVariables) {
68
91
  // Ensure envVariables is an object
69
92
  if (typeof envVariables === 'object' && envVariables !== null) {
70
- let i18nLanguages = new Set();
71
93
  Object.keys(envVariables).forEach((key) => {
72
94
  try {
73
95
  if (key.startsWith('GLOBAL.')) {
@@ -78,21 +100,6 @@ function init(envVariables) {
78
100
  console.log(`Codenotch global variable ${globalKey} set!`);
79
101
  return;
80
102
  }
81
- else if (key.startsWith("i18n.")) {
82
- // Special case
83
- let parts = key.split("."); // i18n.someKey.fr
84
- let language = parts[parts.length - 1]; // fr
85
- let keyName = parts.slice(1, parts.length - 1).join("."); // someKey
86
- i18nLanguages.add(language);
87
- let value = `${envVariables[key]}`.trim();
88
- if (value.startsWith('"') && value.endsWith('"')) {
89
- // Parse like json
90
- value = JSON.parse(value);
91
- }
92
- env.i18n = env.i18n ?? {};
93
- env.i18n[language] = env.i18n[language] ?? {};
94
- env.i18n[language][keyName] = value;
95
- }
96
103
  else {
97
104
  env[key] = envVariables[key];
98
105
  if (['appManifest', 'projectManifest'].includes(key)) {
@@ -111,17 +118,6 @@ function init(envVariables) {
111
118
  console.error(`Error processing Codenotch environment variable ${key}:`, error);
112
119
  }
113
120
  });
114
- if (env.language === undefined) {
115
- // If current language is not defined, we set it to the first language found in i18n
116
- if (i18nLanguages.size > 0) {
117
- env.language = Array.from(i18nLanguages)[0];
118
- console.log(`Codenotch current language set to ${env.language}`);
119
- }
120
- }
121
- }
122
- // Instantiate SignalR for further use in the API
123
- if (env.clusterUrl && env.serviceName) {
124
- signalR = new SignalR_1.SignalR(env.clusterUrl, env.serviceName, false, env.accessToken);
125
121
  }
126
122
  // Get theme like AEC does
127
123
  try {
@@ -141,26 +137,15 @@ function init(envVariables) {
141
137
  }
142
138
  catch {
143
139
  }
140
+ notify();
144
141
  }
145
142
  /**
146
- * Return the Codenotch client API bound to the current environment.
147
- *
148
- * Despite its name this is NOT a React hook it is a plain function with no
149
- * hook rules attached: it can be called anywhere (components, handlers, plain
150
- * modules). `init()` must have been called first, which the Codenotch runtime
151
- * does automatically when serving the application.
152
- *
153
- * @returns The Codenotch API: BPMN processes, CNQL queries, i18n, signals, theme…
154
- * @example
155
- * import { useCodenotch } from 'codenotch-react';
156
- *
157
- * const MyApp: React.FC = () => {
158
- * const cn = useCodenotch();
159
- * return <h1>{cn.i18n('welcome')}</h1>;
160
- * };
143
+ * Build the API object bound to {@link env}. Every method reads `env` lazily,
144
+ * so an instance never goes stale; a new one is created by {@link notify} only
145
+ * to give React a fresh reference when the environment changes.
161
146
  */
162
- function useCodenotch() {
163
- return {
147
+ function createApi() {
148
+ const api = {
164
149
  env: env,
165
150
  uuid: () => (0, uuid_1.v4)(),
166
151
  getProjectFileUrl: (relativePath) => {
@@ -175,7 +160,7 @@ function useCodenotch() {
175
160
  return url;
176
161
  },
177
162
  getProjectFile: async (relativePath) => {
178
- let url = useCodenotch().getProjectFileUrl(relativePath);
163
+ let url = api.getProjectFileUrl(relativePath);
179
164
  let resp = await fetch(url);
180
165
  if (resp.status.toString().startsWith('2') === false) {
181
166
  throw new Error(`Failed to fetch file content: ${resp.status} ${resp.statusText}`);
@@ -209,48 +194,8 @@ function useCodenotch() {
209
194
  }
210
195
  return await response.json();
211
196
  },
212
- i18n: (key, ...args) => {
213
- try {
214
- if (env.i18n === undefined || Object.keys(env.i18n).length === 0) {
215
- console.warn("Codenotch i18n is not defined. Please set it in the Codenotch configuration.");
216
- return key;
217
- }
218
- let language = env.language ?? "en";
219
- let isLanguageExists = env.i18n[language] !== undefined;
220
- if (isLanguageExists === false) {
221
- let fallbackLanguage = Object.keys(env.i18n)[0];
222
- console.warn(`Codenotch i18n language ${language} is not defined. Falling back to ${fallbackLanguage}. Please set the correct language in the Codenotch configuration.`);
223
- language = fallbackLanguage;
224
- }
225
- let dico = env.i18n[language];
226
- let value = dico[key];
227
- if (typeof value !== "string") {
228
- console.warn(`Codenotch i18n key ${key} is not defined for language ${language}. Please set it in the Codenotch configuration.`);
229
- return key;
230
- }
231
- args = args ?? [];
232
- let result = value.replace(/{([\d ]*)}/g, (match, p1) => {
233
- let argValue = args[p1.trim()];
234
- // Instead of returning 'undefined', returns an empty string
235
- return argValue !== undefined ? argValue : "";
236
- });
237
- return result;
238
- }
239
- catch (error) {
240
- console.error("Error in i18n function:", error);
241
- return key;
242
- }
243
- },
244
- startProcess: async (processName, startNodeId, inputs) => {
245
- if (env.clusterUrl === undefined)
246
- throw new Error(MISSING_CLUSTER_URL_ERROR);
247
- if (env.serviceName === undefined)
248
- throw new Error(MISSING_SERVICE_NAME_ERROR);
249
- if (env.tenantName === undefined)
250
- throw new Error(MISSING_TENANT_NAME_ERROR);
251
- if (signalR === undefined)
252
- throw new Error("SignalR not available. Cannot start process.");
253
- return ProcessUtils_1.default.startProcess((processInstanceId, callbacks) => signalR.subscribeToProcessInstance(processInstanceId, callbacks), env.clusterUrl, env.tenantName, env.serviceName, processName, inputs, undefined, startNodeId ?? 'start', env.accessToken);
197
+ startProcess: async (process, inputs) => {
198
+ throw new Error('Not implemented.');
254
199
  },
255
200
  getUrlParams: () => {
256
201
  let result = {};
@@ -266,9 +211,14 @@ function useCodenotch() {
266
211
  },
267
212
  showDialog: (node) => {
268
213
  let dialogId = "codenotch-dialog-" + Math.random().toString(36).substring(2, 9);
214
+ let root = undefined;
269
215
  let result = {
270
216
  id: dialogId,
271
217
  close: () => {
218
+ // Unmount the React tree first so effects are cleaned up and nothing leaks,
219
+ // then drop the <dialog> element itself.
220
+ root?.unmount();
221
+ root = undefined;
272
222
  let dialogElement = document.getElementById(dialogId);
273
223
  if (dialogElement) {
274
224
  dialogElement.remove();
@@ -311,22 +261,13 @@ function useCodenotch() {
311
261
  dialogElement.style.background = '#78787822';
312
262
  dialogElement.style.backdropFilter = 'blur(2px)';
313
263
  container.appendChild(dialogElement);
314
- react_dom_1.default.render(node, dialogElement, () => {
315
- dialogElement.showModal();
316
- });
264
+ // React 19: ReactDOM.render() is gone. Render synchronously through a dedicated root
265
+ // so the content is in the DOM before showModal() moves the focus into the dialog.
266
+ root = (0, client_1.createRoot)(dialogElement);
267
+ (0, react_dom_1.flushSync)(() => root.render(node));
268
+ dialogElement.showModal();
317
269
  return result;
318
270
  },
319
- listenSignal: async (signalId, callback) => {
320
- if (!signalR) {
321
- throw new Error("SignalR not available. Cannot listen to signal.");
322
- }
323
- let unsubscribeFunc = await signalR.subscribeToSignal(signalId, callback);
324
- return {
325
- dispose: () => {
326
- unsubscribeFunc();
327
- }
328
- };
329
- },
330
271
  setTheme: (theme) => {
331
272
  if (env.theme === theme)
332
273
  return;
@@ -334,11 +275,13 @@ function useCodenotch() {
334
275
  document.documentElement.classList.remove(env.theme);
335
276
  env.theme = theme;
336
277
  document.documentElement.classList.add(theme);
278
+ notify();
337
279
  },
338
280
  setLanguage: (lang) => {
339
281
  if (env.language === lang)
340
282
  return;
341
283
  env.language = lang;
284
+ notify();
342
285
  },
343
286
  getLanguages: () => {
344
287
  return env.projectManifest?.languages ?? [];
@@ -359,11 +302,89 @@ function useCodenotch() {
359
302
  return env.appManifest;
360
303
  }
361
304
  };
305
+ return api;
306
+ }
307
+ currentApi = createApi();
308
+ /**
309
+ * Return the Codenotch client API bound to the current environment, from
310
+ * anywhere: event handlers, plain modules, class components, services…
311
+ *
312
+ * This is a plain function (not a hook). The returned object is never stale —
313
+ * its methods always read the live environment — but it does not trigger any
314
+ * re-render when the language or theme changes: inside React components prefer
315
+ * {@link useCodenotch} (function components) or {@link withCodenotch}
316
+ * (class components), which do.
317
+ *
318
+ * `init()` must have been called first, which the Codenotch runtime does
319
+ * automatically when serving the application.
320
+ *
321
+ * @example
322
+ * import { getCodenotch } from 'codenotch-react';
323
+ *
324
+ * export async function loadTodos(userId: string) {
325
+ * const result = await getCodenotch().startProcess('getTodos', 'start', { UserId: userId });
326
+ * return result.output.todos;
327
+ * }
328
+ */
329
+ function getCodenotch() {
330
+ return currentApi;
331
+ }
332
+ /**
333
+ * React hook returning the Codenotch client API.
334
+ *
335
+ * The reference is stable across renders and only changes when the environment
336
+ * changes (`setLanguage`, `setTheme`, `init`), in which case every component
337
+ * using the hook re-renders output follows the current
338
+ * language automatically, and the object is safe to use in `useMemo` /
339
+ * `useEffect` dependency arrays.
340
+ *
341
+ * Regular hook rules apply (call it unconditionally at the top of a function
342
+ * component or a custom hook). Outside of components — handlers defined in
343
+ * plain modules, services, class components — use {@link getCodenotch} or
344
+ * {@link withCodenotch} instead.
345
+ */
346
+ function useCodenotch() {
347
+ return (0, react_1.useSyncExternalStore)(subscribe, getCodenotch, getCodenotch);
348
+ }
349
+ /**
350
+ * Register a listener called each time the Codenotch environment changes
351
+ * (`setLanguage`, `setTheme`, `init`). Escape hatch for code that cannot use
352
+ * {@link useCodenotch} or {@link withCodenotch}, e.g. a class component that
353
+ * wants to `forceUpdate()` itself, or a non-React module caching translations.
354
+ *
355
+ * @returns A disposable — call `dispose()` to stop listening.
356
+ * @example
357
+ * componentDidMount() {
358
+ * this.sub = onCodenotchChange(() => this.forceUpdate());
359
+ * }
360
+ * componentWillUnmount() {
361
+ * this.sub.dispose();
362
+ * }
363
+ */
364
+ function onCodenotchChange(listener) {
365
+ const unsubscribe = subscribe(listener);
366
+ return { dispose: unsubscribe };
367
+ }
368
+ /**
369
+ * Higher-order component injecting the Codenotch API as a `cn` prop.
370
+ *
371
+ * Meant for class components, which cannot call {@link useCodenotch}: the
372
+ * wrapped component receives `this.props.cn` and re-renders whenever the
373
+ * environment changes (language, theme…), exactly like the hook. A `ref`
374
+ * passed to the wrapper is forwarded to the wrapped component instance.
375
+ *
376
+ * @param Component A component whose props extend {@link WithCodenotchProps}.
377
+ * @returns A component with the same props minus `cn`.
378
+ */
379
+ function withCodenotch(Component) {
380
+ const Wrapped = (props) => {
381
+ const cn = useCodenotch();
382
+ // React 19: `ref` is a regular prop, so spreading `props` forwards it.
383
+ return (0, react_1.createElement)(Component, { ...props, cn });
384
+ };
385
+ Wrapped.displayName = `withCodenotch(${Component.displayName || Component.name || "Component"})`;
386
+ return Wrapped;
362
387
  }
363
- // Public models. Re-exported from the package root so that the typings
364
- // generated by the Codenotch IDE (typings/i18n.d.ts and typings/process.d.ts,
365
- // which augment the 'codenotch-react' module) merge with the real
366
- // TranslationRegistry / ProcessRegistry declarations.
367
388
  __exportStar(require("./models/Codenotch"), exports);
368
389
  __exportStar(require("./models/Misc"), exports);
369
390
  __exportStar(require("@codenotch/codenotch.core"), exports);