@intlayer/editor 8.9.8 → 8.10.0-canary.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.
Files changed (32) hide show
  1. package/README.md +14 -0
  2. package/dist/cjs/core/EditorStateManager.cjs +115 -0
  3. package/dist/cjs/core/EditorStateManager.cjs.map +1 -1
  4. package/dist/cjs/core/editedContentBus.cjs +37 -0
  5. package/dist/cjs/core/editedContentBus.cjs.map +1 -0
  6. package/dist/cjs/core/focusedContentBus.cjs +43 -0
  7. package/dist/cjs/core/focusedContentBus.cjs.map +1 -0
  8. package/dist/cjs/core/index.cjs +9 -1
  9. package/dist/cjs/index.cjs +9 -1
  10. package/dist/cjs/messageKey.cjs +2 -0
  11. package/dist/cjs/messageKey.cjs.map +1 -1
  12. package/dist/esm/core/EditorStateManager.mjs +115 -0
  13. package/dist/esm/core/EditorStateManager.mjs.map +1 -1
  14. package/dist/esm/core/editedContentBus.mjs +33 -0
  15. package/dist/esm/core/editedContentBus.mjs.map +1 -0
  16. package/dist/esm/core/focusedContentBus.mjs +39 -0
  17. package/dist/esm/core/focusedContentBus.mjs.map +1 -0
  18. package/dist/esm/core/index.mjs +3 -1
  19. package/dist/esm/index.mjs +3 -1
  20. package/dist/esm/messageKey.mjs +2 -0
  21. package/dist/esm/messageKey.mjs.map +1 -1
  22. package/dist/types/core/EditorStateManager.d.ts +17 -0
  23. package/dist/types/core/EditorStateManager.d.ts.map +1 -1
  24. package/dist/types/core/editedContentBus.d.ts +9 -0
  25. package/dist/types/core/editedContentBus.d.ts.map +1 -0
  26. package/dist/types/core/focusedContentBus.d.ts +9 -0
  27. package/dist/types/core/focusedContentBus.d.ts.map +1 -0
  28. package/dist/types/core/index.d.ts +3 -1
  29. package/dist/types/index.d.ts +3 -1
  30. package/dist/types/messageKey.d.ts +3 -1
  31. package/dist/types/messageKey.d.ts.map +1 -1
  32. package/package.json +6 -6
package/README.md CHANGED
@@ -67,6 +67,7 @@ With **per-locale content files**, **TypeScript autocompletion**, **tree-shakabl
67
67
  | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/mcp.png?raw=true" alt="Feature" width="700"> | **MCP Server Integration**<br><br>Provides an MCP (Model Context Protocol) server for IDE automation, enabling seamless content management and i18n workflows directly within your development environment. <br><br> - [MCP Server](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/mcp_server.md) |
68
68
  | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/vscode_extension.png?raw=true" alt="Feature" width="700"> | **VSCode Extension**<br><br>Intlayer provides a VSCode extension to help you manage your content and translations, building your dictionaries, translating your content, and more. <br><br> - [VSCode Extension](https://intlayer.org/doc/vs-code-extension) |
69
69
  | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/interoperability.png?raw=true" alt="Feature" width="700"> | **Interoperability**<br><br>Allow interoperability with react-i18next, next-i18next, next-intl, react-intl, vue-i18n. <br><br> - [Intlayer and react-intl](https://intlayer.org/blog/intlayer-with-react-intl) <br> - [Intlayer and next-intl](https://intlayer.org/blog/intlayer-with-next-intl) <br> - [Intlayer and next-i18next](https://intlayer.org/blog/intlayer-with-next-i18next) <br> - [Intlayer and vue-i18n](https://intlayer.org/blog/intlayer-with-vue-i18n) |
70
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/benchmark.png?raw=true" alt="Feature" width="700"> | **Performances & Benchmark**<br><br>Uses advanced tree-shaking and dynamic loading to boost performances and keep the solution as light as possible. <br><br> - [Performances & Benchmark](https://intlayer.org/doc/benchmark) |
70
71
 
71
72
  ---
72
73
 
@@ -249,6 +250,19 @@ Explore our comprehensive documentation to get started with Intlayer and learn h
249
250
  </ul>
250
251
  </details>
251
252
 
253
+ ## Multilingual content management system
254
+
255
+ More than an i18n library, Intlayer is a complete **multilingual content management system**. A full CMS is available for free at [app.intlayer.org](https://app.intlayer.org).
256
+
257
+ Intlayer connects **developers**, **copywriters**, and **AI agents** in one workflow for creating and maintaining multilingual websites effortlessly.Intlayer replaces the following stack in a single solution:
258
+
259
+ - i18n solutions (e.g. `i18next`, `next-intl`, `vue-i18n`)
260
+ - TMSs (Translation Management Systems) (e.g. Crowdin, Phrase, Lokalise)
261
+ - Feature flags
262
+ - Headless CMSs (e.g. Contentful, Strapi, Sanity)
263
+
264
+ ![CMS Preview](https://github.com/aymericzip/intlayer/blob/main/docs/assets/CMS.png?raw=true)
265
+
252
266
  ## 🌐 Readme in other languages
253
267
 
254
268
  <p align="center">
@@ -3,6 +3,8 @@ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
3
3
  const require_messageKey = require('../messageKey.cjs');
4
4
  const require_core_CrossFrameMessenger = require('./CrossFrameMessenger.cjs');
5
5
  const require_core_CrossFrameStateManager = require('./CrossFrameStateManager.cjs');
6
+ const require_core_editedContentBus = require('./editedContentBus.cjs');
7
+ const require_core_focusedContentBus = require('./focusedContentBus.cjs');
6
8
  const require_core_IframeClickInterceptor = require('./IframeClickInterceptor.cjs');
7
9
  const require_core_UrlStateManager = require('./UrlStateManager.cjs');
8
10
  let _intlayer_types_nodeType = require("@intlayer/types/nodeType");
@@ -26,6 +28,7 @@ var EditorStateManager = class {
26
28
  editedContent;
27
29
  configuration;
28
30
  currentLocale;
31
+ displayedDictionaryKeys;
29
32
  _urlManager;
30
33
  _iframeInterceptor;
31
34
  _mode;
@@ -33,6 +36,15 @@ var EditorStateManager = class {
33
36
  _unsubAreYouThere = null;
34
37
  _unsubActivate = null;
35
38
  _unsubClientReady = null;
39
+ _displayedKeysObserver = null;
40
+ _displayedKeysTimer = null;
41
+ _displayedKeysListeners = [];
42
+ _editedContentFromBus = false;
43
+ _unsubGlobalEditedContent = null;
44
+ _editedContentBusHandler = null;
45
+ _focusedContentFromBus = false;
46
+ _unsubGlobalFocusedContent = null;
47
+ _focusedContentBusHandler = null;
36
48
  constructor(config) {
37
49
  this._mode = config.mode;
38
50
  this._configuration = config.configuration;
@@ -58,6 +70,11 @@ var EditorStateManager = class {
58
70
  emit: config.mode === "client",
59
71
  receive: config.mode === "editor"
60
72
  });
73
+ this.displayedDictionaryKeys = new require_core_CrossFrameStateManager.CrossFrameStateManager("INTLAYER_DISPLAYED_DICTIONARY_KEYS", this.messenger, {
74
+ emit: config.mode === "client",
75
+ receive: config.mode === "editor",
76
+ initialValue: []
77
+ });
61
78
  this._urlManager = new require_core_UrlStateManager.UrlStateManager(this.messenger);
62
79
  this._iframeInterceptor = new require_core_IframeClickInterceptor.IframeClickInterceptor(this.messenger);
63
80
  }
@@ -69,10 +86,14 @@ var EditorStateManager = class {
69
86
  this.editedContent.start();
70
87
  this.configuration.start();
71
88
  this.currentLocale.start();
89
+ this.displayedDictionaryKeys.start();
90
+ this._startEditedContentBusSync();
91
+ this._startFocusedContentBusSync();
72
92
  if (this._mode === "client") {
73
93
  this._urlManager.start();
74
94
  this._iframeInterceptor.startInterceptor();
75
95
  this._loadDictionaries();
96
+ this._startDisplayedDictionariesTracking();
76
97
  this.messenger.send(`${"INTLAYER_EDITED_CONTENT_CHANGED"}/get`);
77
98
  if (this._configuration?.editor?.enabled !== false) this._setupActivationHandshake();
78
99
  } else {
@@ -94,6 +115,10 @@ var EditorStateManager = class {
94
115
  this.editedContent.stop();
95
116
  this.configuration.stop();
96
117
  this.currentLocale.stop();
118
+ this.displayedDictionaryKeys.stop();
119
+ this._stopDisplayedDictionariesTracking();
120
+ this._stopEditedContentBusSync();
121
+ this._stopFocusedContentBusSync();
97
122
  this._urlManager.stop();
98
123
  this._iframeInterceptor.stopInterceptor();
99
124
  this._iframeInterceptor.stopMerger();
@@ -224,6 +249,96 @@ var EditorStateManager = class {
224
249
  if (node) return node;
225
250
  }
226
251
  }
252
+ _startEditedContentBusSync() {
253
+ this._editedContentBusHandler = (e) => {
254
+ if (this._editedContentFromBus) return;
255
+ const content = e.detail;
256
+ require_core_editedContentBus.setGlobalEditedContent(content, this.messenger.senderId);
257
+ };
258
+ this.editedContent.addEventListener("change", this._editedContentBusHandler);
259
+ this._unsubGlobalEditedContent = require_core_editedContentBus.subscribeToGlobalEditedContent((content, sourceId) => {
260
+ if (sourceId === this.messenger.senderId) return;
261
+ this._editedContentFromBus = true;
262
+ this.editedContent.set(content);
263
+ this._editedContentFromBus = false;
264
+ });
265
+ const existing = require_core_editedContentBus.getGlobalEditedContent();
266
+ if (Object.keys(existing).length > 0) {
267
+ this._editedContentFromBus = true;
268
+ this.editedContent.set(existing);
269
+ this._editedContentFromBus = false;
270
+ }
271
+ }
272
+ _stopEditedContentBusSync() {
273
+ if (this._editedContentBusHandler) {
274
+ this.editedContent.removeEventListener("change", this._editedContentBusHandler);
275
+ this._editedContentBusHandler = null;
276
+ }
277
+ this._unsubGlobalEditedContent?.();
278
+ this._unsubGlobalEditedContent = null;
279
+ }
280
+ _startFocusedContentBusSync() {
281
+ this._focusedContentBusHandler = (e) => {
282
+ if (this._focusedContentFromBus) return;
283
+ const content = e.detail;
284
+ require_core_focusedContentBus.setGlobalFocusedContent(content, this.messenger.senderId);
285
+ };
286
+ this.focusedContent.addEventListener("change", this._focusedContentBusHandler);
287
+ this._unsubGlobalFocusedContent = require_core_focusedContentBus.subscribeToGlobalFocusedContent((content, sourceId) => {
288
+ if (sourceId === this.messenger.senderId) return;
289
+ this._focusedContentFromBus = true;
290
+ this.focusedContent.set(content);
291
+ this._focusedContentFromBus = false;
292
+ });
293
+ const existing = require_core_focusedContentBus.getGlobalFocusedContent();
294
+ if (existing !== void 0) {
295
+ this._focusedContentFromBus = true;
296
+ this.focusedContent.set(existing);
297
+ this._focusedContentFromBus = false;
298
+ }
299
+ }
300
+ _stopFocusedContentBusSync() {
301
+ if (this._focusedContentBusHandler) {
302
+ this.focusedContent.removeEventListener("change", this._focusedContentBusHandler);
303
+ this._focusedContentBusHandler = null;
304
+ }
305
+ this._unsubGlobalFocusedContent?.();
306
+ this._unsubGlobalFocusedContent = null;
307
+ }
308
+ _scanDisplayedDictionaryKeys() {
309
+ if (typeof document === "undefined") return;
310
+ const elements = document.querySelectorAll("intlayer-content-selector-wrapper[dictionary-key]");
311
+ const keys = Array.from(new Set(Array.from(elements).map((el) => el.getAttribute("dictionary-key") ?? "").filter(Boolean)));
312
+ this.displayedDictionaryKeys.set(keys);
313
+ }
314
+ _startDisplayedDictionariesTracking() {
315
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
316
+ const schedule = () => {
317
+ if (this._displayedKeysTimer) clearTimeout(this._displayedKeysTimer);
318
+ this._displayedKeysTimer = setTimeout(() => this._scanDisplayedDictionaryKeys(), 100);
319
+ };
320
+ this._displayedKeysObserver = new MutationObserver(schedule);
321
+ this._displayedKeysObserver.observe(document.body, {
322
+ childList: true,
323
+ subtree: true
324
+ });
325
+ for (const evt of ["locationchange", "popstate"]) {
326
+ const listener = schedule;
327
+ window.addEventListener(evt, listener);
328
+ this._displayedKeysListeners.push([evt, listener]);
329
+ }
330
+ this._scanDisplayedDictionaryKeys();
331
+ }
332
+ _stopDisplayedDictionariesTracking() {
333
+ this._displayedKeysObserver?.disconnect();
334
+ this._displayedKeysObserver = null;
335
+ if (this._displayedKeysTimer) {
336
+ clearTimeout(this._displayedKeysTimer);
337
+ this._displayedKeysTimer = null;
338
+ }
339
+ for (const [evt, listener] of this._displayedKeysListeners) window.removeEventListener(evt, listener);
340
+ this._displayedKeysListeners = [];
341
+ }
227
342
  /**
228
343
  * EDITOR mode: listen for CLIENT_READY and respond with EDITOR_ACTIVATE.
229
344
  * Also pings the client immediately in case it loaded before the editor.
@@ -1 +1 @@
1
- {"version":3,"file":"EditorStateManager.cjs","names":["CrossFrameMessenger","CrossFrameStateManager","UrlStateManager","IframeClickInterceptor","NodeTypes"],"sources":["../../../src/core/EditorStateManager.ts"],"sourcesContent":["import {\n editDictionaryByKeyPath,\n getContentNodeByKeyPath,\n renameContentNodeByKeyPath,\n} from '@intlayer/core/dictionaryManipulator';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n ContentNode,\n Dictionary,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport { MessageKey } from '../messageKey';\nimport {\n CrossFrameMessenger,\n type MessengerConfig,\n} from './CrossFrameMessenger';\nimport { CrossFrameStateManager } from './CrossFrameStateManager';\nimport { IframeClickInterceptor } from './IframeClickInterceptor';\nimport { UrlStateManager } from './UrlStateManager';\n\nexport type DictionaryContent = Record<LocalDictionaryId, Dictionary>;\n\nexport type FileContent = {\n dictionaryKey: string;\n dictionaryLocalId?: LocalDictionaryId;\n keyPath?: KeyPath[];\n};\n\nexport type EditorStateManagerConfig = {\n /** 'client' = the app running inside the iframe; 'editor' = the editor wrapping the app */\n mode: 'editor' | 'client';\n /** Cross-frame messaging configuration */\n messenger: MessengerConfig;\n /** Optional initial Intlayer configuration to broadcast */\n configuration?: IntlayerConfig;\n};\n\n/**\n * EditorStateManager is the single entry point for all Intlayer editor state.\n * It is framework-agnostic: instantiate one instance at the root of the application\n * and subscribe to its EventTarget-based events from any framework adapter.\n *\n * Replaces all context providers, hooks and store files across React, Preact,\n * Solid, Svelte, and Vue integrations.\n */\nexport class EditorStateManager {\n readonly messenger: CrossFrameMessenger;\n readonly editorEnabled: CrossFrameStateManager<boolean>;\n readonly focusedContent: CrossFrameStateManager<FileContent | null>;\n readonly localeDictionaries: CrossFrameStateManager<DictionaryContent>;\n readonly editedContent: CrossFrameStateManager<DictionaryContent>;\n readonly configuration: CrossFrameStateManager<IntlayerConfig>;\n readonly currentLocale: CrossFrameStateManager<Locale | undefined>;\n\n private readonly _urlManager: UrlStateManager;\n private readonly _iframeInterceptor: IframeClickInterceptor;\n private readonly _mode: 'editor' | 'client';\n private readonly _configuration: IntlayerConfig | undefined;\n\n // Client-mode handshake subscribers\n private _unsubAreYouThere: (() => void) | null = null;\n private _unsubActivate: (() => void) | null = null;\n // Editor-mode handshake subscriber\n private _unsubClientReady: (() => void) | null = null;\n\n constructor(config: EditorStateManagerConfig) {\n this._mode = config.mode;\n this._configuration = config.configuration;\n\n this.messenger = new CrossFrameMessenger(config.messenger);\n\n this.editorEnabled = new CrossFrameStateManager<boolean>(\n MessageKey.INTLAYER_EDITOR_ENABLED,\n this.messenger,\n { emit: false, receive: true, initialValue: false }\n );\n\n this.focusedContent = new CrossFrameStateManager<FileContent | null>(\n MessageKey.INTLAYER_FOCUSED_CONTENT_CHANGED,\n this.messenger,\n { emit: true, receive: true, initialValue: null }\n );\n\n this.localeDictionaries = new CrossFrameStateManager<DictionaryContent>(\n MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED,\n this.messenger\n );\n\n this.editedContent = new CrossFrameStateManager<DictionaryContent>(\n MessageKey.INTLAYER_EDITED_CONTENT_CHANGED,\n this.messenger\n );\n\n this.configuration = new CrossFrameStateManager<IntlayerConfig>(\n MessageKey.INTLAYER_CONFIGURATION,\n this.messenger,\n {\n emit: true,\n receive: false,\n ...(config.configuration ? { initialValue: config.configuration } : {}),\n }\n );\n\n // Client emits its locale to the editor; editor receives it.\n this.currentLocale = new CrossFrameStateManager<Locale>(\n MessageKey.INTLAYER_CURRENT_LOCALE,\n this.messenger,\n {\n emit: config.mode === 'client',\n receive: config.mode === 'editor',\n }\n );\n\n this._urlManager = new UrlStateManager(this.messenger);\n this._iframeInterceptor = new IframeClickInterceptor(this.messenger);\n }\n\n start(): void {\n this.messenger.start();\n this.editorEnabled.start();\n this.focusedContent.start();\n this.localeDictionaries.start();\n this.editedContent.start();\n this.configuration.start();\n this.currentLocale.start();\n\n if (this._mode === 'client') {\n this._urlManager.start();\n this._iframeInterceptor.startInterceptor();\n this._loadDictionaries();\n // Request current edited content from the editor\n this.messenger.send(`${MessageKey.INTLAYER_EDITED_CONTENT_CHANGED}/get`);\n // Activation handshake: only participate if editor.enabled !== false\n if (this._configuration?.editor?.enabled !== false) {\n this._setupActivationHandshake();\n }\n } else {\n this._iframeInterceptor.startMerger();\n this._setupEditorHandshake();\n }\n }\n\n stop(): void {\n this._unsubAreYouThere?.();\n this._unsubActivate?.();\n this._unsubClientReady?.();\n this._unsubAreYouThere = null;\n this._unsubActivate = null;\n this._unsubClientReady = null;\n this.messenger.stop();\n this.editorEnabled.stop();\n this.focusedContent.stop();\n this.localeDictionaries.stop();\n this.editedContent.stop();\n this.configuration.stop();\n this.currentLocale.stop();\n this._urlManager.stop();\n this._iframeInterceptor.stopInterceptor();\n this._iframeInterceptor.stopMerger();\n }\n\n // ─── Handshake helpers ───────────────────────────────────────────────────────\n\n /**\n * EDITOR mode: re-send ARE_YOU_THERE to attempt re-connection with the client.\n * Call this when the user clicks \"Enable Editor\" or when the iframe reloads.\n */\n pingClient(): void {\n if (this._mode !== 'editor') return;\n\n this.messenger.send(MessageKey.INTLAYER_ARE_YOU_THERE);\n }\n\n // ─── Focus helpers ──────────────────────────────────────────────────────────\n\n setFocusedContentKeyPath(keyPath: KeyPath[]): void {\n const filtered = keyPath.filter(\n (key) => key.type !== NodeTypes.TRANSLATION\n );\n const prev = this.focusedContent.value;\n\n if (!prev) return;\n\n this.focusedContent.set({ ...prev, keyPath: filtered });\n }\n\n // ─── Dictionary record helpers ───────────────────────────────────────────\n\n setLocaleDictionary(dictionary: Dictionary): void {\n if (!dictionary.localId) return;\n const current = this.localeDictionaries.value ?? {};\n\n this.localeDictionaries.set({\n ...current,\n [dictionary.localId as LocalDictionaryId]: dictionary,\n });\n }\n\n // ─── Edited content helpers ───────────────────────────────────────────────\n\n setEditedDictionary(newDict: Dictionary): void {\n if (!newDict.localId) {\n console.error('setEditedDictionary: missing localId', newDict);\n return;\n }\n const current = this.editedContent.value ?? {};\n\n this.editedContent.set({\n ...current,\n [newDict.localId as LocalDictionaryId]: newDict,\n });\n }\n\n setEditedContent(\n localDictionaryId: LocalDictionaryId,\n newValue: Dictionary['content']\n ): void {\n const current = this.editedContent.value ?? {};\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: newValue,\n },\n });\n }\n\n addContent(\n localDictionaryId: LocalDictionaryId,\n newValue: ContentNode,\n keyPath: KeyPath[] = [],\n overwrite = true\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n\n let newKeyPath = keyPath;\n if (!overwrite) {\n let index = 0;\n\n const otherKeyPath = keyPath.slice(0, -1);\n const lastKeyPath = keyPath[keyPath.length - 1];\n\n let finalKey = lastKeyPath.key;\n\n while (\n typeof getContentNodeByKeyPath(currentContent, newKeyPath) !==\n 'undefined'\n ) {\n index++;\n finalKey =\n index === 0 ? lastKeyPath.key : `${lastKeyPath.key} (${index})`;\n newKeyPath = [\n ...otherKeyPath,\n { ...lastKeyPath, key: finalKey } as KeyPath,\n ];\n }\n }\n\n const updatedContent = editDictionaryByKeyPath(\n currentContent,\n newKeyPath,\n newValue\n );\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: updatedContent as Dictionary['content'],\n },\n });\n }\n\n renameContent(\n localDictionaryId: LocalDictionaryId,\n newKey: KeyPath['key'],\n keyPath: KeyPath[] = []\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n const updated = renameContentNodeByKeyPath(currentContent, newKey, keyPath);\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: updated as Dictionary['content'],\n },\n });\n }\n\n removeContent(\n localDictionaryId: LocalDictionaryId,\n keyPath: KeyPath[]\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n const initialContent = getContentNodeByKeyPath(originalContent, keyPath);\n const restored = editDictionaryByKeyPath(\n currentContent,\n keyPath,\n initialContent\n );\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: restored as Dictionary['content'],\n },\n });\n }\n\n restoreContent(localDictionaryId: LocalDictionaryId): void {\n const current = this.editedContent.value ?? {};\n const updated = { ...current };\n\n delete updated[localDictionaryId];\n\n this.editedContent.set(updated);\n }\n\n clearContent(localDictionaryId: LocalDictionaryId): void {\n const current = this.editedContent.value ?? {};\n const filtered = { ...current };\n\n delete filtered[localDictionaryId];\n\n this.editedContent.set(filtered);\n }\n\n clearAllContent(): void {\n this.editedContent.set({});\n }\n\n getContentValue(\n localDictionaryIdOrKey: LocalDictionaryId | string,\n keyPath: KeyPath[]\n ): ContentNode | undefined {\n const edited = this.editedContent.value;\n if (!edited) return undefined;\n\n const filteredKeyPath = keyPath.filter(\n (key) => key.type !== NodeTypes.TRANSLATION\n );\n\n // Only use edited content entries whose localId is known to this client.\n // This prevents stale edits from other apps (different framework demos) from\n // being applied when the editor sends back its stored editedContent.\n const localeDicts = this.localeDictionaries.value;\n\n const isDictionaryId =\n localDictionaryIdOrKey.includes(':local:') ||\n localDictionaryIdOrKey.includes(':remote:');\n\n if (isDictionaryId) {\n // If localeDictionaries is loaded, verify this localId belongs to us\n if (localeDicts && !(localDictionaryIdOrKey in localeDicts)) {\n return undefined;\n }\n const content =\n edited[localDictionaryIdOrKey as LocalDictionaryId]?.content ?? {};\n\n return getContentNodeByKeyPath(\n content,\n filteredKeyPath,\n this.currentLocale.value\n );\n }\n\n const matchingIds = Object.keys(edited).filter(\n (key) =>\n key.startsWith(`${localDictionaryIdOrKey}:`) &&\n // If localeDictionaries is loaded, only include known localIds\n (!localeDicts || key in localeDicts)\n );\n\n for (const localId of matchingIds) {\n const content = edited[localId as LocalDictionaryId]?.content ?? {};\n const node = getContentNodeByKeyPath(\n content,\n filteredKeyPath,\n this.currentLocale.value\n );\n if (node) return node;\n }\n\n return undefined;\n }\n\n /**\n * EDITOR mode: listen for CLIENT_READY and respond with EDITOR_ACTIVATE.\n * Also pings the client immediately in case it loaded before the editor.\n */\n private _setupEditorHandshake(): void {\n // When the client announces it is ready, activate it\n this._unsubClientReady = this.messenger.subscribe(\n MessageKey.INTLAYER_CLIENT_READY,\n () => {\n this.editorEnabled.set(true);\n this.messenger.send(MessageKey.INTLAYER_EDITOR_ACTIVATE);\n }\n );\n\n // Ping any already-running client (covers editor-opens-after-client scenario)\n this.messenger.send(MessageKey.INTLAYER_ARE_YOU_THERE);\n }\n\n private _setupActivationHandshake(): void {\n // Announce to the editor that the client is ready\n this.messenger.send(MessageKey.INTLAYER_CLIENT_READY);\n\n // Respond to \"are you there?\" pings from the editor\n this._unsubAreYouThere = this.messenger.subscribe(\n MessageKey.INTLAYER_ARE_YOU_THERE,\n () => {\n this.messenger.send(MessageKey.INTLAYER_CLIENT_READY);\n }\n );\n\n // When the editor activates us, enable the selector and broadcast state\n this._unsubActivate = this.messenger.subscribe(\n MessageKey.INTLAYER_EDITOR_ACTIVATE,\n () => {\n this.editorEnabled.set(true);\n this._broadcastData();\n }\n );\n }\n\n private _broadcastData(): void {\n const configVal = this.configuration.value;\n\n if (configVal) {\n this.messenger.send(\n `${MessageKey.INTLAYER_CONFIGURATION}/post`,\n configVal\n );\n }\n const localeVal = this.currentLocale.value;\n\n if (localeVal) {\n this.messenger.send(\n `${MessageKey.INTLAYER_CURRENT_LOCALE}/post`,\n localeVal\n );\n }\n const dicts = this.localeDictionaries.value;\n\n if (dicts) {\n this.messenger.send(\n `${MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED}/post`,\n dicts\n );\n }\n }\n\n private async _loadDictionaries(): Promise<void> {\n try {\n const mod = await import('@intlayer/unmerged-dictionaries-entry');\n const unmergedDictionaries = mod.getUnmergedDictionaries();\n const dictionariesList = Object.fromEntries(\n Object.values(unmergedDictionaries)\n .flat()\n .map((dictionary) => [dictionary.localId, dictionary])\n ) as DictionaryContent;\n\n this.localeDictionaries.set(dictionariesList);\n\n // If the editor already activated us before dictionaries finished loading,\n // re-broadcast now so the editor receives the dictionaries.\n if (this.editorEnabled.value) {\n this._broadcastData();\n }\n } catch (e) {\n // Dynamic entry not available (expected in editor mode or when not configured)\n console.warn('[intlayer] Failed to load unmerged dictionaries:', e);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditorStateManager.cjs","names":["CrossFrameMessenger","CrossFrameStateManager","UrlStateManager","IframeClickInterceptor","NodeTypes","subscribeToGlobalEditedContent","getGlobalEditedContent","subscribeToGlobalFocusedContent","getGlobalFocusedContent"],"sources":["../../../src/core/EditorStateManager.ts"],"sourcesContent":["import {\n editDictionaryByKeyPath,\n getContentNodeByKeyPath,\n renameContentNodeByKeyPath,\n} from '@intlayer/core/dictionaryManipulator';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n ContentNode,\n Dictionary,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport { MessageKey } from '../messageKey';\nimport {\n CrossFrameMessenger,\n type MessengerConfig,\n} from './CrossFrameMessenger';\nimport { CrossFrameStateManager } from './CrossFrameStateManager';\nimport {\n getGlobalEditedContent,\n setGlobalEditedContent,\n subscribeToGlobalEditedContent,\n} from './editedContentBus';\nimport {\n getGlobalFocusedContent,\n setGlobalFocusedContent,\n subscribeToGlobalFocusedContent,\n} from './focusedContentBus';\nimport { IframeClickInterceptor } from './IframeClickInterceptor';\nimport { UrlStateManager } from './UrlStateManager';\n\nexport type DictionaryContent = Record<LocalDictionaryId, Dictionary>;\n\nexport type FileContent = {\n dictionaryKey: string;\n dictionaryLocalId?: LocalDictionaryId;\n keyPath?: KeyPath[];\n};\n\nexport type EditorStateManagerConfig = {\n /** 'client' = the app running inside the iframe; 'editor' = the editor wrapping the app */\n mode: 'editor' | 'client';\n /** Cross-frame messaging configuration */\n messenger: MessengerConfig;\n /** Optional initial Intlayer configuration to broadcast */\n configuration?: IntlayerConfig;\n};\n\n/**\n * EditorStateManager is the single entry point for all Intlayer editor state.\n * It is framework-agnostic: instantiate one instance at the root of the application\n * and subscribe to its EventTarget-based events from any framework adapter.\n *\n * Replaces all context providers, hooks and store files across React, Preact,\n * Solid, Svelte, and Vue integrations.\n */\nexport class EditorStateManager {\n readonly messenger: CrossFrameMessenger;\n readonly editorEnabled: CrossFrameStateManager<boolean>;\n readonly focusedContent: CrossFrameStateManager<FileContent | null>;\n readonly localeDictionaries: CrossFrameStateManager<DictionaryContent>;\n readonly editedContent: CrossFrameStateManager<DictionaryContent>;\n readonly configuration: CrossFrameStateManager<IntlayerConfig>;\n readonly currentLocale: CrossFrameStateManager<Locale | undefined>;\n readonly displayedDictionaryKeys: CrossFrameStateManager<string[]>;\n\n private readonly _urlManager: UrlStateManager;\n private readonly _iframeInterceptor: IframeClickInterceptor;\n private readonly _mode: 'editor' | 'client';\n private readonly _configuration: IntlayerConfig | undefined;\n\n // Client-mode handshake subscribers\n private _unsubAreYouThere: (() => void) | null = null;\n private _unsubActivate: (() => void) | null = null;\n // Editor-mode handshake subscriber\n private _unsubClientReady: (() => void) | null = null;\n\n // Client-mode displayed-keys tracking\n private _displayedKeysObserver: MutationObserver | null = null;\n private _displayedKeysTimer: ReturnType<typeof setTimeout> | null = null;\n private _displayedKeysListeners: Array<[string, EventListener]> = [];\n\n // Global editedContent bus sync\n private _editedContentFromBus = false;\n private _unsubGlobalEditedContent: (() => void) | null = null;\n private _editedContentBusHandler: ((e: Event) => void) | null = null;\n\n // Global focusedContent bus sync\n private _focusedContentFromBus = false;\n private _unsubGlobalFocusedContent: (() => void) | null = null;\n private _focusedContentBusHandler: ((e: Event) => void) | null = null;\n\n constructor(config: EditorStateManagerConfig) {\n this._mode = config.mode;\n this._configuration = config.configuration;\n\n this.messenger = new CrossFrameMessenger(config.messenger);\n\n this.editorEnabled = new CrossFrameStateManager<boolean>(\n MessageKey.INTLAYER_EDITOR_ENABLED,\n this.messenger,\n { emit: false, receive: true, initialValue: false }\n );\n\n this.focusedContent = new CrossFrameStateManager<FileContent | null>(\n MessageKey.INTLAYER_FOCUSED_CONTENT_CHANGED,\n this.messenger,\n { emit: true, receive: true, initialValue: null }\n );\n\n this.localeDictionaries = new CrossFrameStateManager<DictionaryContent>(\n MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED,\n this.messenger\n );\n\n this.editedContent = new CrossFrameStateManager<DictionaryContent>(\n MessageKey.INTLAYER_EDITED_CONTENT_CHANGED,\n this.messenger\n );\n\n this.configuration = new CrossFrameStateManager<IntlayerConfig>(\n MessageKey.INTLAYER_CONFIGURATION,\n this.messenger,\n {\n emit: true,\n receive: false,\n ...(config.configuration ? { initialValue: config.configuration } : {}),\n }\n );\n\n // Client emits its locale to the editor; editor receives it.\n this.currentLocale = new CrossFrameStateManager<Locale>(\n MessageKey.INTLAYER_CURRENT_LOCALE,\n this.messenger,\n {\n emit: config.mode === 'client',\n receive: config.mode === 'editor',\n }\n );\n\n // Client emits displayed dictionary keys; editor receives them.\n this.displayedDictionaryKeys = new CrossFrameStateManager<string[]>(\n MessageKey.INTLAYER_DISPLAYED_DICTIONARY_KEYS,\n this.messenger,\n {\n emit: config.mode === 'client',\n receive: config.mode === 'editor',\n initialValue: [],\n }\n );\n\n this._urlManager = new UrlStateManager(this.messenger);\n this._iframeInterceptor = new IframeClickInterceptor(this.messenger);\n }\n\n start(): void {\n this.messenger.start();\n this.editorEnabled.start();\n this.focusedContent.start();\n this.localeDictionaries.start();\n this.editedContent.start();\n this.configuration.start();\n this.currentLocale.start();\n this.displayedDictionaryKeys.start();\n this._startEditedContentBusSync();\n this._startFocusedContentBusSync();\n\n if (this._mode === 'client') {\n this._urlManager.start();\n this._iframeInterceptor.startInterceptor();\n this._loadDictionaries();\n this._startDisplayedDictionariesTracking();\n // Request current edited content from the editor\n this.messenger.send(`${MessageKey.INTLAYER_EDITED_CONTENT_CHANGED}/get`);\n // Activation handshake: only participate if editor.enabled !== false\n if (this._configuration?.editor?.enabled !== false) {\n this._setupActivationHandshake();\n }\n } else {\n this._iframeInterceptor.startMerger();\n this._setupEditorHandshake();\n }\n }\n\n stop(): void {\n this._unsubAreYouThere?.();\n this._unsubActivate?.();\n this._unsubClientReady?.();\n this._unsubAreYouThere = null;\n this._unsubActivate = null;\n this._unsubClientReady = null;\n this.messenger.stop();\n this.editorEnabled.stop();\n this.focusedContent.stop();\n this.localeDictionaries.stop();\n this.editedContent.stop();\n this.configuration.stop();\n this.currentLocale.stop();\n this.displayedDictionaryKeys.stop();\n this._stopDisplayedDictionariesTracking();\n this._stopEditedContentBusSync();\n this._stopFocusedContentBusSync();\n this._urlManager.stop();\n this._iframeInterceptor.stopInterceptor();\n this._iframeInterceptor.stopMerger();\n }\n\n // ─── Handshake helpers ───────────────────────────────────────────────────────\n\n /**\n * EDITOR mode: re-send ARE_YOU_THERE to attempt re-connection with the client.\n * Call this when the user clicks \"Enable Editor\" or when the iframe reloads.\n */\n pingClient(): void {\n if (this._mode !== 'editor') return;\n\n this.messenger.send(MessageKey.INTLAYER_ARE_YOU_THERE);\n }\n\n // ─── Focus helpers ──────────────────────────────────────────────────────────\n\n setFocusedContentKeyPath(keyPath: KeyPath[]): void {\n const filtered = keyPath.filter(\n (key) => key.type !== NodeTypes.TRANSLATION\n );\n const prev = this.focusedContent.value;\n\n if (!prev) return;\n\n this.focusedContent.set({ ...prev, keyPath: filtered });\n }\n\n // ─── Dictionary record helpers ───────────────────────────────────────────\n\n setLocaleDictionary(dictionary: Dictionary): void {\n if (!dictionary.localId) return;\n const current = this.localeDictionaries.value ?? {};\n\n this.localeDictionaries.set({\n ...current,\n [dictionary.localId as LocalDictionaryId]: dictionary,\n });\n }\n\n // ─── Edited content helpers ───────────────────────────────────────────────\n\n setEditedDictionary(newDict: Dictionary): void {\n if (!newDict.localId) {\n console.error('setEditedDictionary: missing localId', newDict);\n return;\n }\n const current = this.editedContent.value ?? {};\n\n this.editedContent.set({\n ...current,\n [newDict.localId as LocalDictionaryId]: newDict,\n });\n }\n\n setEditedContent(\n localDictionaryId: LocalDictionaryId,\n newValue: Dictionary['content']\n ): void {\n const current = this.editedContent.value ?? {};\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: newValue,\n },\n });\n }\n\n addContent(\n localDictionaryId: LocalDictionaryId,\n newValue: ContentNode,\n keyPath: KeyPath[] = [],\n overwrite = true\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n\n let newKeyPath = keyPath;\n if (!overwrite) {\n let index = 0;\n\n const otherKeyPath = keyPath.slice(0, -1);\n const lastKeyPath = keyPath[keyPath.length - 1];\n\n let finalKey = lastKeyPath.key;\n\n while (\n typeof getContentNodeByKeyPath(currentContent, newKeyPath) !==\n 'undefined'\n ) {\n index++;\n finalKey =\n index === 0 ? lastKeyPath.key : `${lastKeyPath.key} (${index})`;\n newKeyPath = [\n ...otherKeyPath,\n { ...lastKeyPath, key: finalKey } as KeyPath,\n ];\n }\n }\n\n const updatedContent = editDictionaryByKeyPath(\n currentContent,\n newKeyPath,\n newValue\n );\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: updatedContent as Dictionary['content'],\n },\n });\n }\n\n renameContent(\n localDictionaryId: LocalDictionaryId,\n newKey: KeyPath['key'],\n keyPath: KeyPath[] = []\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n const updated = renameContentNodeByKeyPath(currentContent, newKey, keyPath);\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: updated as Dictionary['content'],\n },\n });\n }\n\n removeContent(\n localDictionaryId: LocalDictionaryId,\n keyPath: KeyPath[]\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n const initialContent = getContentNodeByKeyPath(originalContent, keyPath);\n const restored = editDictionaryByKeyPath(\n currentContent,\n keyPath,\n initialContent\n );\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: restored as Dictionary['content'],\n },\n });\n }\n\n restoreContent(localDictionaryId: LocalDictionaryId): void {\n const current = this.editedContent.value ?? {};\n const updated = { ...current };\n\n delete updated[localDictionaryId];\n\n this.editedContent.set(updated);\n }\n\n clearContent(localDictionaryId: LocalDictionaryId): void {\n const current = this.editedContent.value ?? {};\n const filtered = { ...current };\n\n delete filtered[localDictionaryId];\n\n this.editedContent.set(filtered);\n }\n\n clearAllContent(): void {\n this.editedContent.set({});\n }\n\n getContentValue(\n localDictionaryIdOrKey: LocalDictionaryId | string,\n keyPath: KeyPath[]\n ): ContentNode | undefined {\n const edited = this.editedContent.value;\n if (!edited) return undefined;\n\n const filteredKeyPath = keyPath.filter(\n (key) => key.type !== NodeTypes.TRANSLATION\n );\n\n // Only use edited content entries whose localId is known to this client.\n // This prevents stale edits from other apps (different framework demos) from\n // being applied when the editor sends back its stored editedContent.\n const localeDicts = this.localeDictionaries.value;\n\n const isDictionaryId =\n localDictionaryIdOrKey.includes(':local:') ||\n localDictionaryIdOrKey.includes(':remote:');\n\n if (isDictionaryId) {\n // If localeDictionaries is loaded, verify this localId belongs to us\n if (localeDicts && !(localDictionaryIdOrKey in localeDicts)) {\n return undefined;\n }\n const content =\n edited[localDictionaryIdOrKey as LocalDictionaryId]?.content ?? {};\n\n return getContentNodeByKeyPath(\n content,\n filteredKeyPath,\n this.currentLocale.value\n );\n }\n\n const matchingIds = Object.keys(edited).filter(\n (key) =>\n key.startsWith(`${localDictionaryIdOrKey}:`) &&\n // If localeDictionaries is loaded, only include known localIds\n (!localeDicts || key in localeDicts)\n );\n\n for (const localId of matchingIds) {\n const content = edited[localId as LocalDictionaryId]?.content ?? {};\n const node = getContentNodeByKeyPath(\n content,\n filteredKeyPath,\n this.currentLocale.value\n );\n if (node) return node;\n }\n\n return undefined;\n }\n\n // ─── Global editedContent bus sync ───────────────────────────────────────\n\n private _startEditedContentBusSync(): void {\n // Push local changes to the global bus (loop-guarded)\n this._editedContentBusHandler = (e: Event) => {\n if (this._editedContentFromBus) return;\n const content = (e as CustomEvent<DictionaryContent>).detail;\n setGlobalEditedContent(content, this.messenger.senderId);\n };\n this.editedContent.addEventListener(\n 'change',\n this._editedContentBusHandler\n );\n\n // Receive bus changes from other managers\n this._unsubGlobalEditedContent = subscribeToGlobalEditedContent(\n (content, sourceId) => {\n if (sourceId === this.messenger.senderId) return;\n this._editedContentFromBus = true;\n this.editedContent.set(content);\n this._editedContentFromBus = false;\n }\n );\n\n // Seed local value from the bus if bus already has content\n const existing = getGlobalEditedContent();\n if (Object.keys(existing).length > 0) {\n this._editedContentFromBus = true;\n this.editedContent.set(existing);\n this._editedContentFromBus = false;\n }\n }\n\n private _stopEditedContentBusSync(): void {\n if (this._editedContentBusHandler) {\n this.editedContent.removeEventListener(\n 'change',\n this._editedContentBusHandler\n );\n this._editedContentBusHandler = null;\n }\n this._unsubGlobalEditedContent?.();\n this._unsubGlobalEditedContent = null;\n }\n\n // ─── Global focusedContent bus sync ──────────────────────────────────────\n\n private _startFocusedContentBusSync(): void {\n this._focusedContentBusHandler = (e: Event) => {\n if (this._focusedContentFromBus) return;\n const content = (e as CustomEvent<FileContent | null>).detail;\n setGlobalFocusedContent(content, this.messenger.senderId);\n };\n this.focusedContent.addEventListener(\n 'change',\n this._focusedContentBusHandler\n );\n\n this._unsubGlobalFocusedContent = subscribeToGlobalFocusedContent(\n (content, sourceId) => {\n if (sourceId === this.messenger.senderId) return;\n this._focusedContentFromBus = true;\n this.focusedContent.set(content);\n this._focusedContentFromBus = false;\n }\n );\n\n const existing = getGlobalFocusedContent();\n if (existing !== undefined) {\n this._focusedContentFromBus = true;\n this.focusedContent.set(existing);\n this._focusedContentFromBus = false;\n }\n }\n\n private _stopFocusedContentBusSync(): void {\n if (this._focusedContentBusHandler) {\n this.focusedContent.removeEventListener(\n 'change',\n this._focusedContentBusHandler\n );\n this._focusedContentBusHandler = null;\n }\n this._unsubGlobalFocusedContent?.();\n this._unsubGlobalFocusedContent = null;\n }\n\n // ─── Displayed dictionaries tracking (client mode only) ──────────────────\n\n private _scanDisplayedDictionaryKeys(): void {\n if (typeof document === 'undefined') return;\n const elements = document.querySelectorAll(\n 'intlayer-content-selector-wrapper[dictionary-key]'\n );\n const keys = Array.from(\n new Set(\n Array.from(elements)\n .map((el) => el.getAttribute('dictionary-key') ?? '')\n .filter(Boolean)\n )\n );\n this.displayedDictionaryKeys.set(keys);\n }\n\n private _startDisplayedDictionariesTracking(): void {\n if (\n typeof document === 'undefined' ||\n typeof MutationObserver === 'undefined'\n )\n return;\n\n const schedule = () => {\n if (this._displayedKeysTimer) clearTimeout(this._displayedKeysTimer);\n this._displayedKeysTimer = setTimeout(\n () => this._scanDisplayedDictionaryKeys(),\n 100\n );\n };\n\n this._displayedKeysObserver = new MutationObserver(schedule);\n this._displayedKeysObserver.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n for (const evt of ['locationchange', 'popstate'] as const) {\n const listener = schedule as EventListener;\n window.addEventListener(evt, listener);\n this._displayedKeysListeners.push([evt, listener]);\n }\n\n this._scanDisplayedDictionaryKeys();\n }\n\n private _stopDisplayedDictionariesTracking(): void {\n this._displayedKeysObserver?.disconnect();\n this._displayedKeysObserver = null;\n if (this._displayedKeysTimer) {\n clearTimeout(this._displayedKeysTimer);\n this._displayedKeysTimer = null;\n }\n for (const [evt, listener] of this._displayedKeysListeners) {\n window.removeEventListener(evt, listener);\n }\n this._displayedKeysListeners = [];\n }\n\n // ─── Handshake helpers ───────────────────────────────────────────────────────\n\n /**\n * EDITOR mode: listen for CLIENT_READY and respond with EDITOR_ACTIVATE.\n * Also pings the client immediately in case it loaded before the editor.\n */\n private _setupEditorHandshake(): void {\n // When the client announces it is ready, activate it\n this._unsubClientReady = this.messenger.subscribe(\n MessageKey.INTLAYER_CLIENT_READY,\n () => {\n this.editorEnabled.set(true);\n this.messenger.send(MessageKey.INTLAYER_EDITOR_ACTIVATE);\n }\n );\n\n // Ping any already-running client (covers editor-opens-after-client scenario)\n this.messenger.send(MessageKey.INTLAYER_ARE_YOU_THERE);\n }\n\n private _setupActivationHandshake(): void {\n // Announce to the editor that the client is ready\n this.messenger.send(MessageKey.INTLAYER_CLIENT_READY);\n\n // Respond to \"are you there?\" pings from the editor\n this._unsubAreYouThere = this.messenger.subscribe(\n MessageKey.INTLAYER_ARE_YOU_THERE,\n () => {\n this.messenger.send(MessageKey.INTLAYER_CLIENT_READY);\n }\n );\n\n // When the editor activates us, enable the selector and broadcast state\n this._unsubActivate = this.messenger.subscribe(\n MessageKey.INTLAYER_EDITOR_ACTIVATE,\n () => {\n this.editorEnabled.set(true);\n this._broadcastData();\n }\n );\n }\n\n private _broadcastData(): void {\n const configVal = this.configuration.value;\n\n if (configVal) {\n this.messenger.send(\n `${MessageKey.INTLAYER_CONFIGURATION}/post`,\n configVal\n );\n }\n const localeVal = this.currentLocale.value;\n\n if (localeVal) {\n this.messenger.send(\n `${MessageKey.INTLAYER_CURRENT_LOCALE}/post`,\n localeVal\n );\n }\n const dicts = this.localeDictionaries.value;\n\n if (dicts) {\n this.messenger.send(\n `${MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED}/post`,\n dicts\n );\n }\n }\n\n private async _loadDictionaries(): Promise<void> {\n try {\n const mod = await import('@intlayer/unmerged-dictionaries-entry');\n const unmergedDictionaries = mod.getUnmergedDictionaries();\n const dictionariesList = Object.fromEntries(\n Object.values(unmergedDictionaries)\n .flat()\n .map((dictionary) => [dictionary.localId, dictionary])\n ) as DictionaryContent;\n\n this.localeDictionaries.set(dictionariesList);\n\n // If the editor already activated us before dictionaries finished loading,\n // re-broadcast now so the editor receives the dictionaries.\n if (this.editorEnabled.value) {\n this._broadcastData();\n }\n } catch (e) {\n // Dynamic entry not available (expected in editor mode or when not configured)\n console.warn('[intlayer] Failed to load unmerged dictionaries:', e);\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ //#region src/core/editedContentBus.ts
4
+ const BUS_KEY = "__intlayer_edited_content_bus__";
5
+ const BUS_EVENTS_KEY = "__intlayer_edited_content_bus_events__";
6
+ const getBusTarget = () => {
7
+ if (typeof window === "undefined") return new EventTarget();
8
+ const w = window;
9
+ if (!w[BUS_EVENTS_KEY]) w[BUS_EVENTS_KEY] = new EventTarget();
10
+ return w[BUS_EVENTS_KEY];
11
+ };
12
+ const getGlobalEditedContent = () => {
13
+ if (typeof window === "undefined") return {};
14
+ return window[BUS_KEY] ?? {};
15
+ };
16
+ const setGlobalEditedContent = (content, sourceId) => {
17
+ if (typeof window !== "undefined") window[BUS_KEY] = content;
18
+ getBusTarget().dispatchEvent(new CustomEvent("change", { detail: {
19
+ content,
20
+ sourceId
21
+ } }));
22
+ };
23
+ const subscribeToGlobalEditedContent = (cb) => {
24
+ const handler = (e) => {
25
+ const { content, sourceId } = e.detail;
26
+ cb(content, sourceId);
27
+ };
28
+ const target = getBusTarget();
29
+ target.addEventListener("change", handler);
30
+ return () => target.removeEventListener("change", handler);
31
+ };
32
+
33
+ //#endregion
34
+ exports.getGlobalEditedContent = getGlobalEditedContent;
35
+ exports.setGlobalEditedContent = setGlobalEditedContent;
36
+ exports.subscribeToGlobalEditedContent = subscribeToGlobalEditedContent;
37
+ //# sourceMappingURL=editedContentBus.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editedContentBus.cjs","names":[],"sources":["../../../src/core/editedContentBus.ts"],"sourcesContent":["import type { DictionaryContent } from './EditorStateManager';\n\nconst BUS_KEY = '__intlayer_edited_content_bus__';\nconst BUS_EVENTS_KEY = '__intlayer_edited_content_bus_events__';\n\ntype WindowWithBus = typeof window & {\n [BUS_KEY]?: DictionaryContent;\n [BUS_EVENTS_KEY]?: EventTarget;\n};\n\ntype BusEvent = { content: DictionaryContent; sourceId?: string };\n\nconst getBusTarget = (): EventTarget => {\n if (typeof window === 'undefined') return new EventTarget();\n const w = window as WindowWithBus;\n if (!w[BUS_EVENTS_KEY]) w[BUS_EVENTS_KEY] = new EventTarget();\n return w[BUS_EVENTS_KEY]!;\n};\n\nexport const getGlobalEditedContent = (): DictionaryContent => {\n if (typeof window === 'undefined') return {};\n return (window as WindowWithBus)[BUS_KEY] ?? {};\n};\n\nexport const setGlobalEditedContent = (\n content: DictionaryContent,\n sourceId?: string\n): void => {\n if (typeof window !== 'undefined') {\n (window as WindowWithBus)[BUS_KEY] = content;\n }\n getBusTarget().dispatchEvent(\n new CustomEvent<BusEvent>('change', { detail: { content, sourceId } })\n );\n};\n\nexport const subscribeToGlobalEditedContent = (\n cb: (content: DictionaryContent, sourceId?: string) => void\n): (() => void) => {\n const handler = (e: Event) => {\n const { content, sourceId } = (e as CustomEvent<BusEvent>).detail;\n cb(content, sourceId);\n };\n const target = getBusTarget();\n target.addEventListener('change', handler);\n return () => target.removeEventListener('change', handler);\n};\n"],"mappings":";;;AAEA,MAAM,UAAU;AAChB,MAAM,iBAAiB;AASvB,MAAM,qBAAkC;CACtC,IAAI,OAAO,WAAW,aAAa,OAAO,IAAI,YAAY;CAC1D,MAAM,IAAI;CACV,IAAI,CAAC,EAAE,iBAAiB,EAAE,kBAAkB,IAAI,YAAY;CAC5D,OAAO,EAAE;AACX;AAEA,MAAa,+BAAkD;CAC7D,IAAI,OAAO,WAAW,aAAa,OAAO,CAAC;CAC3C,OAAQ,OAAyB,YAAY,CAAC;AAChD;AAEA,MAAa,0BACX,SACA,aACS;CACT,IAAI,OAAO,WAAW,aACpB,AAAC,OAAyB,WAAW;CAEvC,aAAa,EAAE,cACb,IAAI,YAAsB,UAAU,EAAE,QAAQ;EAAE;EAAS;CAAS,EAAE,CAAC,CACvE;AACF;AAEA,MAAa,kCACX,OACiB;CACjB,MAAM,WAAW,MAAa;EAC5B,MAAM,EAAE,SAAS,aAAc,EAA4B;EAC3D,GAAG,SAAS,QAAQ;CACtB;CACA,MAAM,SAAS,aAAa;CAC5B,OAAO,iBAAiB,UAAU,OAAO;CACzC,aAAa,OAAO,oBAAoB,UAAU,OAAO;AAC3D"}
@@ -0,0 +1,43 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ //#region src/core/focusedContentBus.ts
4
+ const BUS_KEY = "__intlayer_focused_content_bus__";
5
+ const BUS_EVENTS_KEY = "__intlayer_focused_content_bus_events__";
6
+ const getBusTarget = () => {
7
+ if (typeof window === "undefined") return new EventTarget();
8
+ const w = window;
9
+ if (!w[BUS_EVENTS_KEY]) w[BUS_EVENTS_KEY] = new EventTarget();
10
+ return w[BUS_EVENTS_KEY];
11
+ };
12
+ const getGlobalFocusedContent = () => {
13
+ if (typeof window === "undefined") return void 0;
14
+ const w = window;
15
+ if (!w.__intlayer_focused_content_bus_set__) return void 0;
16
+ return w[BUS_KEY] ?? null;
17
+ };
18
+ const setGlobalFocusedContent = (content, sourceId) => {
19
+ if (typeof window !== "undefined") {
20
+ const w = window;
21
+ w[BUS_KEY] = content;
22
+ w.__intlayer_focused_content_bus_set__ = true;
23
+ }
24
+ getBusTarget().dispatchEvent(new CustomEvent("change", { detail: {
25
+ content,
26
+ sourceId
27
+ } }));
28
+ };
29
+ const subscribeToGlobalFocusedContent = (cb) => {
30
+ const handler = (e) => {
31
+ const { content, sourceId } = e.detail;
32
+ cb(content, sourceId);
33
+ };
34
+ const target = getBusTarget();
35
+ target.addEventListener("change", handler);
36
+ return () => target.removeEventListener("change", handler);
37
+ };
38
+
39
+ //#endregion
40
+ exports.getGlobalFocusedContent = getGlobalFocusedContent;
41
+ exports.setGlobalFocusedContent = setGlobalFocusedContent;
42
+ exports.subscribeToGlobalFocusedContent = subscribeToGlobalFocusedContent;
43
+ //# sourceMappingURL=focusedContentBus.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focusedContentBus.cjs","names":[],"sources":["../../../src/core/focusedContentBus.ts"],"sourcesContent":["import type { FileContent } from './EditorStateManager';\n\nconst BUS_KEY = '__intlayer_focused_content_bus__';\nconst BUS_EVENTS_KEY = '__intlayer_focused_content_bus_events__';\n\ntype WindowWithBus = typeof window & {\n [BUS_KEY]?: FileContent | null;\n [BUS_EVENTS_KEY]?: EventTarget;\n __intlayer_focused_content_bus_set__?: boolean;\n};\n\ntype BusEvent = { content: FileContent | null; sourceId?: string };\n\nconst getBusTarget = (): EventTarget => {\n if (typeof window === 'undefined') return new EventTarget();\n const w = window as WindowWithBus;\n if (!w[BUS_EVENTS_KEY]) w[BUS_EVENTS_KEY] = new EventTarget();\n return w[BUS_EVENTS_KEY]!;\n};\n\nexport const getGlobalFocusedContent = (): FileContent | null | undefined => {\n if (typeof window === 'undefined') return undefined;\n const w = window as WindowWithBus;\n if (!w.__intlayer_focused_content_bus_set__) return undefined;\n return w[BUS_KEY] ?? null;\n};\n\nexport const setGlobalFocusedContent = (\n content: FileContent | null,\n sourceId?: string\n): void => {\n if (typeof window !== 'undefined') {\n const w = window as WindowWithBus;\n w[BUS_KEY] = content;\n w.__intlayer_focused_content_bus_set__ = true;\n }\n getBusTarget().dispatchEvent(\n new CustomEvent<BusEvent>('change', { detail: { content, sourceId } })\n );\n};\n\nexport const subscribeToGlobalFocusedContent = (\n cb: (content: FileContent | null, sourceId?: string) => void\n): (() => void) => {\n const handler = (e: Event) => {\n const { content, sourceId } = (e as CustomEvent<BusEvent>).detail;\n cb(content, sourceId);\n };\n const target = getBusTarget();\n target.addEventListener('change', handler);\n return () => target.removeEventListener('change', handler);\n};\n"],"mappings":";;;AAEA,MAAM,UAAU;AAChB,MAAM,iBAAiB;AAUvB,MAAM,qBAAkC;CACtC,IAAI,OAAO,WAAW,aAAa,OAAO,IAAI,YAAY;CAC1D,MAAM,IAAI;CACV,IAAI,CAAC,EAAE,iBAAiB,EAAE,kBAAkB,IAAI,YAAY;CAC5D,OAAO,EAAE;AACX;AAEA,MAAa,gCAAgE;CAC3E,IAAI,OAAO,WAAW,aAAa,OAAO;CAC1C,MAAM,IAAI;CACV,IAAI,CAAC,EAAE,sCAAsC,OAAO;CACpD,OAAO,EAAE,YAAY;AACvB;AAEA,MAAa,2BACX,SACA,aACS;CACT,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,IAAI;EACV,EAAE,WAAW;EACb,EAAE,uCAAuC;CAC3C;CACA,aAAa,EAAE,cACb,IAAI,YAAsB,UAAU,EAAE,QAAQ;EAAE;EAAS;CAAS,EAAE,CAAC,CACvE;AACF;AAEA,MAAa,mCACX,OACiB;CACjB,MAAM,WAAW,MAAa;EAC5B,MAAM,EAAE,SAAS,aAAc,EAA4B;EAC3D,GAAG,SAAS,QAAQ;CACtB;CACA,MAAM,SAAS,aAAa;CAC5B,OAAO,iBAAiB,UAAU,OAAO;CACzC,aAAa,OAAO,oBAAoB,UAAU,OAAO;AAC3D"}
@@ -2,6 +2,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_core_globalManager = require('./globalManager.cjs');
3
3
  const require_core_CrossFrameMessenger = require('./CrossFrameMessenger.cjs');
4
4
  const require_core_CrossFrameStateManager = require('./CrossFrameStateManager.cjs');
5
+ const require_core_editedContentBus = require('./editedContentBus.cjs');
6
+ const require_core_focusedContentBus = require('./focusedContentBus.cjs');
5
7
  const require_core_IframeClickInterceptor = require('./IframeClickInterceptor.cjs');
6
8
  const require_core_UrlStateManager = require('./UrlStateManager.cjs');
7
9
  const require_core_EditorStateManager = require('./EditorStateManager.cjs');
@@ -13,8 +15,14 @@ exports.EditorStateManager = require_core_EditorStateManager.EditorStateManager;
13
15
  exports.IframeClickInterceptor = require_core_IframeClickInterceptor.IframeClickInterceptor;
14
16
  exports.UrlStateManager = require_core_UrlStateManager.UrlStateManager;
15
17
  exports.buildClientMessengerConfig = require_core_initEditorClient.buildClientMessengerConfig;
18
+ exports.getGlobalEditedContent = require_core_editedContentBus.getGlobalEditedContent;
16
19
  exports.getGlobalEditorManager = require_core_globalManager.getGlobalEditorManager;
20
+ exports.getGlobalFocusedContent = require_core_focusedContentBus.getGlobalFocusedContent;
17
21
  exports.initEditorClient = require_core_initEditorClient.initEditorClient;
18
22
  exports.onGlobalEditorManagerChange = require_core_globalManager.onGlobalEditorManagerChange;
23
+ exports.setGlobalEditedContent = require_core_editedContentBus.setGlobalEditedContent;
19
24
  exports.setGlobalEditorManager = require_core_globalManager.setGlobalEditorManager;
20
- exports.stopEditorClient = require_core_initEditorClient.stopEditorClient;
25
+ exports.setGlobalFocusedContent = require_core_focusedContentBus.setGlobalFocusedContent;
26
+ exports.stopEditorClient = require_core_initEditorClient.stopEditorClient;
27
+ exports.subscribeToGlobalEditedContent = require_core_editedContentBus.subscribeToGlobalEditedContent;
28
+ exports.subscribeToGlobalFocusedContent = require_core_focusedContentBus.subscribeToGlobalFocusedContent;
@@ -7,6 +7,8 @@ const require_components_ContentSelectorWrapper = require('./components/ContentS
7
7
  const require_components_EditedContent = require('./components/EditedContent.cjs');
8
8
  const require_core_CrossFrameMessenger = require('./core/CrossFrameMessenger.cjs');
9
9
  const require_core_CrossFrameStateManager = require('./core/CrossFrameStateManager.cjs');
10
+ const require_core_editedContentBus = require('./core/editedContentBus.cjs');
11
+ const require_core_focusedContentBus = require('./core/focusedContentBus.cjs');
10
12
  const require_core_IframeClickInterceptor = require('./core/IframeClickInterceptor.cjs');
11
13
  const require_core_UrlStateManager = require('./core/UrlStateManager.cjs');
12
14
  const require_core_EditorStateManager = require('./core/EditorStateManager.cjs');
@@ -30,9 +32,15 @@ exports.defineIntlayerContentSelectorWrapper = require_components_ContentSelecto
30
32
  exports.defineIntlayerEditedContent = require_components_EditedContent.defineIntlayerEditedContent;
31
33
  exports.defineIntlayerEditorElement = require_components_IntlayerEditor.defineIntlayerEditorElement;
32
34
  exports.defineIntlayerElements = require_components_ContentSelector.defineIntlayerElements;
35
+ exports.getGlobalEditedContent = require_core_editedContentBus.getGlobalEditedContent;
33
36
  exports.getGlobalEditorManager = require_core_globalManager.getGlobalEditorManager;
37
+ exports.getGlobalFocusedContent = require_core_focusedContentBus.getGlobalFocusedContent;
34
38
  exports.initEditorClient = require_core_initEditorClient.initEditorClient;
35
39
  exports.mergeIframeClick = require_mergeIframeClick.mergeIframeClick;
36
40
  exports.onGlobalEditorManagerChange = require_core_globalManager.onGlobalEditorManagerChange;
41
+ exports.setGlobalEditedContent = require_core_editedContentBus.setGlobalEditedContent;
37
42
  exports.setGlobalEditorManager = require_core_globalManager.setGlobalEditorManager;
38
- exports.stopEditorClient = require_core_initEditorClient.stopEditorClient;
43
+ exports.setGlobalFocusedContent = require_core_focusedContentBus.setGlobalFocusedContent;
44
+ exports.stopEditorClient = require_core_initEditorClient.stopEditorClient;
45
+ exports.subscribeToGlobalEditedContent = require_core_editedContentBus.subscribeToGlobalEditedContent;
46
+ exports.subscribeToGlobalFocusedContent = require_core_focusedContentBus.subscribeToGlobalFocusedContent;
@@ -23,6 +23,8 @@ let MessageKey = /* @__PURE__ */ function(MessageKey) {
23
23
  MessageKey["INTLAYER_EDITED_CONTENT_CHANGED"] = "INTLAYER_EDITED_CONTENT_CHANGED";
24
24
  /** Client → editor: Helper to sync iframe client reactivity on click client */
25
25
  MessageKey["INTLAYER_IFRAME_CLICKED"] = "INTLAYER_IFRAME_CLICKED";
26
+ /** Client → editor: list of dictionary keys currently rendered in the iframe */
27
+ MessageKey["INTLAYER_DISPLAYED_DICTIONARY_KEYS"] = "INTLAYER_DISPLAYED_DICTIONARY_KEYS";
26
28
  return MessageKey;
27
29
  }({});
28
30
 
@@ -1 +1 @@
1
- {"version":3,"file":"messageKey.cjs","names":[],"sources":["../../src/messageKey.ts"],"sourcesContent":["export enum MessageKey {\n /** Client → editor: announces the client is ready (also used as response to ARE_YOU_THERE) */\n INTLAYER_CLIENT_READY = 'INTLAYER_CLIENT_READY',\n /** Editor → client: asks if the client is still alive */\n INTLAYER_ARE_YOU_THERE = 'INTLAYER_ARE_YOU_THERE',\n /** Editor → client: instructs the client to activate the editor selector */\n INTLAYER_EDITOR_ACTIVATE = 'INTLAYER_EDITOR_ACTIVATE',\n /** Client → editor: instructs the editor that the client is enabled */\n INTLAYER_EDITOR_ENABLED = 'INTLAYER_EDITOR_ENABLED',\n\n /** Client → editor: send information related the app */\n INTLAYER_CONFIGURATION = 'INTLAYER_CONFIGURATION',\n INTLAYER_CURRENT_LOCALE = 'INTLAYER_CURRENT_LOCALE',\n INTLAYER_URL_CHANGE = 'INTLAYER_URL_CHANGE',\n\n /** Client → editor: load and update content */\n INTLAYER_LOCALE_DICTIONARIES_CHANGED = 'INTLAYER_LOCALE_DICTIONARIES_CHANGED',\n INTLAYER_DISTANT_DICTIONARIES_CHANGED = 'INTLAYER_DISTANT_DICTIONARIES_CHANGED',\n\n /** Client → editor & Editor → client: Update focus and changed content */\n INTLAYER_HOVERED_CONTENT_CHANGED = 'INTLAYER_HOVERED_CONTENT_CHANGED',\n INTLAYER_FOCUSED_CONTENT_CHANGED = 'INTLAYER_FOCUSED_CONTENT_CHANGED',\n INTLAYER_EDITED_CONTENT_CHANGED = 'INTLAYER_EDITED_CONTENT_CHANGED',\n\n /** Client → editor: Helper to sync iframe client reactivity on click client */\n INTLAYER_IFRAME_CLICKED = 'INTLAYER_IFRAME_CLICKED',\n}\n"],"mappings":";;;AAAA,IAAY,aAAL;;CAEL;;CAEA;;CAEA;;CAEA;;CAGA;CACA;CACA;;CAGA;CACA;;CAGA;CACA;CACA;;CAGA;;AACF"}
1
+ {"version":3,"file":"messageKey.cjs","names":[],"sources":["../../src/messageKey.ts"],"sourcesContent":["export enum MessageKey {\n /** Client → editor: announces the client is ready (also used as response to ARE_YOU_THERE) */\n INTLAYER_CLIENT_READY = 'INTLAYER_CLIENT_READY',\n /** Editor → client: asks if the client is still alive */\n INTLAYER_ARE_YOU_THERE = 'INTLAYER_ARE_YOU_THERE',\n /** Editor → client: instructs the client to activate the editor selector */\n INTLAYER_EDITOR_ACTIVATE = 'INTLAYER_EDITOR_ACTIVATE',\n /** Client → editor: instructs the editor that the client is enabled */\n INTLAYER_EDITOR_ENABLED = 'INTLAYER_EDITOR_ENABLED',\n\n /** Client → editor: send information related the app */\n INTLAYER_CONFIGURATION = 'INTLAYER_CONFIGURATION',\n INTLAYER_CURRENT_LOCALE = 'INTLAYER_CURRENT_LOCALE',\n INTLAYER_URL_CHANGE = 'INTLAYER_URL_CHANGE',\n\n /** Client → editor: load and update content */\n INTLAYER_LOCALE_DICTIONARIES_CHANGED = 'INTLAYER_LOCALE_DICTIONARIES_CHANGED',\n INTLAYER_DISTANT_DICTIONARIES_CHANGED = 'INTLAYER_DISTANT_DICTIONARIES_CHANGED',\n\n /** Client → editor & Editor → client: Update focus and changed content */\n INTLAYER_HOVERED_CONTENT_CHANGED = 'INTLAYER_HOVERED_CONTENT_CHANGED',\n INTLAYER_FOCUSED_CONTENT_CHANGED = 'INTLAYER_FOCUSED_CONTENT_CHANGED',\n INTLAYER_EDITED_CONTENT_CHANGED = 'INTLAYER_EDITED_CONTENT_CHANGED',\n\n /** Client → editor: Helper to sync iframe client reactivity on click client */\n INTLAYER_IFRAME_CLICKED = 'INTLAYER_IFRAME_CLICKED',\n\n /** Client → editor: list of dictionary keys currently rendered in the iframe */\n INTLAYER_DISPLAYED_DICTIONARY_KEYS = 'INTLAYER_DISPLAYED_DICTIONARY_KEYS',\n}\n"],"mappings":";;;AAAA,IAAY,aAAL;;CAEL;;CAEA;;CAEA;;CAEA;;CAGA;CACA;CACA;;CAGA;CACA;;CAGA;CACA;CACA;;CAGA;;CAGA;;AACF"}
@@ -1,6 +1,8 @@
1
1
  import { MessageKey } from "../messageKey.mjs";
2
2
  import { CrossFrameMessenger } from "./CrossFrameMessenger.mjs";
3
3
  import { CrossFrameStateManager } from "./CrossFrameStateManager.mjs";
4
+ import { getGlobalEditedContent, setGlobalEditedContent, subscribeToGlobalEditedContent } from "./editedContentBus.mjs";
5
+ import { getGlobalFocusedContent, setGlobalFocusedContent, subscribeToGlobalFocusedContent } from "./focusedContentBus.mjs";
4
6
  import { IframeClickInterceptor } from "./IframeClickInterceptor.mjs";
5
7
  import { UrlStateManager } from "./UrlStateManager.mjs";
6
8
  import * as NodeTypes from "@intlayer/types/nodeType";
@@ -23,6 +25,7 @@ var EditorStateManager = class {
23
25
  editedContent;
24
26
  configuration;
25
27
  currentLocale;
28
+ displayedDictionaryKeys;
26
29
  _urlManager;
27
30
  _iframeInterceptor;
28
31
  _mode;
@@ -30,6 +33,15 @@ var EditorStateManager = class {
30
33
  _unsubAreYouThere = null;
31
34
  _unsubActivate = null;
32
35
  _unsubClientReady = null;
36
+ _displayedKeysObserver = null;
37
+ _displayedKeysTimer = null;
38
+ _displayedKeysListeners = [];
39
+ _editedContentFromBus = false;
40
+ _unsubGlobalEditedContent = null;
41
+ _editedContentBusHandler = null;
42
+ _focusedContentFromBus = false;
43
+ _unsubGlobalFocusedContent = null;
44
+ _focusedContentBusHandler = null;
33
45
  constructor(config) {
34
46
  this._mode = config.mode;
35
47
  this._configuration = config.configuration;
@@ -55,6 +67,11 @@ var EditorStateManager = class {
55
67
  emit: config.mode === "client",
56
68
  receive: config.mode === "editor"
57
69
  });
70
+ this.displayedDictionaryKeys = new CrossFrameStateManager("INTLAYER_DISPLAYED_DICTIONARY_KEYS", this.messenger, {
71
+ emit: config.mode === "client",
72
+ receive: config.mode === "editor",
73
+ initialValue: []
74
+ });
58
75
  this._urlManager = new UrlStateManager(this.messenger);
59
76
  this._iframeInterceptor = new IframeClickInterceptor(this.messenger);
60
77
  }
@@ -66,10 +83,14 @@ var EditorStateManager = class {
66
83
  this.editedContent.start();
67
84
  this.configuration.start();
68
85
  this.currentLocale.start();
86
+ this.displayedDictionaryKeys.start();
87
+ this._startEditedContentBusSync();
88
+ this._startFocusedContentBusSync();
69
89
  if (this._mode === "client") {
70
90
  this._urlManager.start();
71
91
  this._iframeInterceptor.startInterceptor();
72
92
  this._loadDictionaries();
93
+ this._startDisplayedDictionariesTracking();
73
94
  this.messenger.send(`${"INTLAYER_EDITED_CONTENT_CHANGED"}/get`);
74
95
  if (this._configuration?.editor?.enabled !== false) this._setupActivationHandshake();
75
96
  } else {
@@ -91,6 +112,10 @@ var EditorStateManager = class {
91
112
  this.editedContent.stop();
92
113
  this.configuration.stop();
93
114
  this.currentLocale.stop();
115
+ this.displayedDictionaryKeys.stop();
116
+ this._stopDisplayedDictionariesTracking();
117
+ this._stopEditedContentBusSync();
118
+ this._stopFocusedContentBusSync();
94
119
  this._urlManager.stop();
95
120
  this._iframeInterceptor.stopInterceptor();
96
121
  this._iframeInterceptor.stopMerger();
@@ -221,6 +246,96 @@ var EditorStateManager = class {
221
246
  if (node) return node;
222
247
  }
223
248
  }
249
+ _startEditedContentBusSync() {
250
+ this._editedContentBusHandler = (e) => {
251
+ if (this._editedContentFromBus) return;
252
+ const content = e.detail;
253
+ setGlobalEditedContent(content, this.messenger.senderId);
254
+ };
255
+ this.editedContent.addEventListener("change", this._editedContentBusHandler);
256
+ this._unsubGlobalEditedContent = subscribeToGlobalEditedContent((content, sourceId) => {
257
+ if (sourceId === this.messenger.senderId) return;
258
+ this._editedContentFromBus = true;
259
+ this.editedContent.set(content);
260
+ this._editedContentFromBus = false;
261
+ });
262
+ const existing = getGlobalEditedContent();
263
+ if (Object.keys(existing).length > 0) {
264
+ this._editedContentFromBus = true;
265
+ this.editedContent.set(existing);
266
+ this._editedContentFromBus = false;
267
+ }
268
+ }
269
+ _stopEditedContentBusSync() {
270
+ if (this._editedContentBusHandler) {
271
+ this.editedContent.removeEventListener("change", this._editedContentBusHandler);
272
+ this._editedContentBusHandler = null;
273
+ }
274
+ this._unsubGlobalEditedContent?.();
275
+ this._unsubGlobalEditedContent = null;
276
+ }
277
+ _startFocusedContentBusSync() {
278
+ this._focusedContentBusHandler = (e) => {
279
+ if (this._focusedContentFromBus) return;
280
+ const content = e.detail;
281
+ setGlobalFocusedContent(content, this.messenger.senderId);
282
+ };
283
+ this.focusedContent.addEventListener("change", this._focusedContentBusHandler);
284
+ this._unsubGlobalFocusedContent = subscribeToGlobalFocusedContent((content, sourceId) => {
285
+ if (sourceId === this.messenger.senderId) return;
286
+ this._focusedContentFromBus = true;
287
+ this.focusedContent.set(content);
288
+ this._focusedContentFromBus = false;
289
+ });
290
+ const existing = getGlobalFocusedContent();
291
+ if (existing !== void 0) {
292
+ this._focusedContentFromBus = true;
293
+ this.focusedContent.set(existing);
294
+ this._focusedContentFromBus = false;
295
+ }
296
+ }
297
+ _stopFocusedContentBusSync() {
298
+ if (this._focusedContentBusHandler) {
299
+ this.focusedContent.removeEventListener("change", this._focusedContentBusHandler);
300
+ this._focusedContentBusHandler = null;
301
+ }
302
+ this._unsubGlobalFocusedContent?.();
303
+ this._unsubGlobalFocusedContent = null;
304
+ }
305
+ _scanDisplayedDictionaryKeys() {
306
+ if (typeof document === "undefined") return;
307
+ const elements = document.querySelectorAll("intlayer-content-selector-wrapper[dictionary-key]");
308
+ const keys = Array.from(new Set(Array.from(elements).map((el) => el.getAttribute("dictionary-key") ?? "").filter(Boolean)));
309
+ this.displayedDictionaryKeys.set(keys);
310
+ }
311
+ _startDisplayedDictionariesTracking() {
312
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
313
+ const schedule = () => {
314
+ if (this._displayedKeysTimer) clearTimeout(this._displayedKeysTimer);
315
+ this._displayedKeysTimer = setTimeout(() => this._scanDisplayedDictionaryKeys(), 100);
316
+ };
317
+ this._displayedKeysObserver = new MutationObserver(schedule);
318
+ this._displayedKeysObserver.observe(document.body, {
319
+ childList: true,
320
+ subtree: true
321
+ });
322
+ for (const evt of ["locationchange", "popstate"]) {
323
+ const listener = schedule;
324
+ window.addEventListener(evt, listener);
325
+ this._displayedKeysListeners.push([evt, listener]);
326
+ }
327
+ this._scanDisplayedDictionaryKeys();
328
+ }
329
+ _stopDisplayedDictionariesTracking() {
330
+ this._displayedKeysObserver?.disconnect();
331
+ this._displayedKeysObserver = null;
332
+ if (this._displayedKeysTimer) {
333
+ clearTimeout(this._displayedKeysTimer);
334
+ this._displayedKeysTimer = null;
335
+ }
336
+ for (const [evt, listener] of this._displayedKeysListeners) window.removeEventListener(evt, listener);
337
+ this._displayedKeysListeners = [];
338
+ }
224
339
  /**
225
340
  * EDITOR mode: listen for CLIENT_READY and respond with EDITOR_ACTIVATE.
226
341
  * Also pings the client immediately in case it loaded before the editor.
@@ -1 +1 @@
1
- {"version":3,"file":"EditorStateManager.mjs","names":[],"sources":["../../../src/core/EditorStateManager.ts"],"sourcesContent":["import {\n editDictionaryByKeyPath,\n getContentNodeByKeyPath,\n renameContentNodeByKeyPath,\n} from '@intlayer/core/dictionaryManipulator';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n ContentNode,\n Dictionary,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport { MessageKey } from '../messageKey';\nimport {\n CrossFrameMessenger,\n type MessengerConfig,\n} from './CrossFrameMessenger';\nimport { CrossFrameStateManager } from './CrossFrameStateManager';\nimport { IframeClickInterceptor } from './IframeClickInterceptor';\nimport { UrlStateManager } from './UrlStateManager';\n\nexport type DictionaryContent = Record<LocalDictionaryId, Dictionary>;\n\nexport type FileContent = {\n dictionaryKey: string;\n dictionaryLocalId?: LocalDictionaryId;\n keyPath?: KeyPath[];\n};\n\nexport type EditorStateManagerConfig = {\n /** 'client' = the app running inside the iframe; 'editor' = the editor wrapping the app */\n mode: 'editor' | 'client';\n /** Cross-frame messaging configuration */\n messenger: MessengerConfig;\n /** Optional initial Intlayer configuration to broadcast */\n configuration?: IntlayerConfig;\n};\n\n/**\n * EditorStateManager is the single entry point for all Intlayer editor state.\n * It is framework-agnostic: instantiate one instance at the root of the application\n * and subscribe to its EventTarget-based events from any framework adapter.\n *\n * Replaces all context providers, hooks and store files across React, Preact,\n * Solid, Svelte, and Vue integrations.\n */\nexport class EditorStateManager {\n readonly messenger: CrossFrameMessenger;\n readonly editorEnabled: CrossFrameStateManager<boolean>;\n readonly focusedContent: CrossFrameStateManager<FileContent | null>;\n readonly localeDictionaries: CrossFrameStateManager<DictionaryContent>;\n readonly editedContent: CrossFrameStateManager<DictionaryContent>;\n readonly configuration: CrossFrameStateManager<IntlayerConfig>;\n readonly currentLocale: CrossFrameStateManager<Locale | undefined>;\n\n private readonly _urlManager: UrlStateManager;\n private readonly _iframeInterceptor: IframeClickInterceptor;\n private readonly _mode: 'editor' | 'client';\n private readonly _configuration: IntlayerConfig | undefined;\n\n // Client-mode handshake subscribers\n private _unsubAreYouThere: (() => void) | null = null;\n private _unsubActivate: (() => void) | null = null;\n // Editor-mode handshake subscriber\n private _unsubClientReady: (() => void) | null = null;\n\n constructor(config: EditorStateManagerConfig) {\n this._mode = config.mode;\n this._configuration = config.configuration;\n\n this.messenger = new CrossFrameMessenger(config.messenger);\n\n this.editorEnabled = new CrossFrameStateManager<boolean>(\n MessageKey.INTLAYER_EDITOR_ENABLED,\n this.messenger,\n { emit: false, receive: true, initialValue: false }\n );\n\n this.focusedContent = new CrossFrameStateManager<FileContent | null>(\n MessageKey.INTLAYER_FOCUSED_CONTENT_CHANGED,\n this.messenger,\n { emit: true, receive: true, initialValue: null }\n );\n\n this.localeDictionaries = new CrossFrameStateManager<DictionaryContent>(\n MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED,\n this.messenger\n );\n\n this.editedContent = new CrossFrameStateManager<DictionaryContent>(\n MessageKey.INTLAYER_EDITED_CONTENT_CHANGED,\n this.messenger\n );\n\n this.configuration = new CrossFrameStateManager<IntlayerConfig>(\n MessageKey.INTLAYER_CONFIGURATION,\n this.messenger,\n {\n emit: true,\n receive: false,\n ...(config.configuration ? { initialValue: config.configuration } : {}),\n }\n );\n\n // Client emits its locale to the editor; editor receives it.\n this.currentLocale = new CrossFrameStateManager<Locale>(\n MessageKey.INTLAYER_CURRENT_LOCALE,\n this.messenger,\n {\n emit: config.mode === 'client',\n receive: config.mode === 'editor',\n }\n );\n\n this._urlManager = new UrlStateManager(this.messenger);\n this._iframeInterceptor = new IframeClickInterceptor(this.messenger);\n }\n\n start(): void {\n this.messenger.start();\n this.editorEnabled.start();\n this.focusedContent.start();\n this.localeDictionaries.start();\n this.editedContent.start();\n this.configuration.start();\n this.currentLocale.start();\n\n if (this._mode === 'client') {\n this._urlManager.start();\n this._iframeInterceptor.startInterceptor();\n this._loadDictionaries();\n // Request current edited content from the editor\n this.messenger.send(`${MessageKey.INTLAYER_EDITED_CONTENT_CHANGED}/get`);\n // Activation handshake: only participate if editor.enabled !== false\n if (this._configuration?.editor?.enabled !== false) {\n this._setupActivationHandshake();\n }\n } else {\n this._iframeInterceptor.startMerger();\n this._setupEditorHandshake();\n }\n }\n\n stop(): void {\n this._unsubAreYouThere?.();\n this._unsubActivate?.();\n this._unsubClientReady?.();\n this._unsubAreYouThere = null;\n this._unsubActivate = null;\n this._unsubClientReady = null;\n this.messenger.stop();\n this.editorEnabled.stop();\n this.focusedContent.stop();\n this.localeDictionaries.stop();\n this.editedContent.stop();\n this.configuration.stop();\n this.currentLocale.stop();\n this._urlManager.stop();\n this._iframeInterceptor.stopInterceptor();\n this._iframeInterceptor.stopMerger();\n }\n\n // ─── Handshake helpers ───────────────────────────────────────────────────────\n\n /**\n * EDITOR mode: re-send ARE_YOU_THERE to attempt re-connection with the client.\n * Call this when the user clicks \"Enable Editor\" or when the iframe reloads.\n */\n pingClient(): void {\n if (this._mode !== 'editor') return;\n\n this.messenger.send(MessageKey.INTLAYER_ARE_YOU_THERE);\n }\n\n // ─── Focus helpers ──────────────────────────────────────────────────────────\n\n setFocusedContentKeyPath(keyPath: KeyPath[]): void {\n const filtered = keyPath.filter(\n (key) => key.type !== NodeTypes.TRANSLATION\n );\n const prev = this.focusedContent.value;\n\n if (!prev) return;\n\n this.focusedContent.set({ ...prev, keyPath: filtered });\n }\n\n // ─── Dictionary record helpers ───────────────────────────────────────────\n\n setLocaleDictionary(dictionary: Dictionary): void {\n if (!dictionary.localId) return;\n const current = this.localeDictionaries.value ?? {};\n\n this.localeDictionaries.set({\n ...current,\n [dictionary.localId as LocalDictionaryId]: dictionary,\n });\n }\n\n // ─── Edited content helpers ───────────────────────────────────────────────\n\n setEditedDictionary(newDict: Dictionary): void {\n if (!newDict.localId) {\n console.error('setEditedDictionary: missing localId', newDict);\n return;\n }\n const current = this.editedContent.value ?? {};\n\n this.editedContent.set({\n ...current,\n [newDict.localId as LocalDictionaryId]: newDict,\n });\n }\n\n setEditedContent(\n localDictionaryId: LocalDictionaryId,\n newValue: Dictionary['content']\n ): void {\n const current = this.editedContent.value ?? {};\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: newValue,\n },\n });\n }\n\n addContent(\n localDictionaryId: LocalDictionaryId,\n newValue: ContentNode,\n keyPath: KeyPath[] = [],\n overwrite = true\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n\n let newKeyPath = keyPath;\n if (!overwrite) {\n let index = 0;\n\n const otherKeyPath = keyPath.slice(0, -1);\n const lastKeyPath = keyPath[keyPath.length - 1];\n\n let finalKey = lastKeyPath.key;\n\n while (\n typeof getContentNodeByKeyPath(currentContent, newKeyPath) !==\n 'undefined'\n ) {\n index++;\n finalKey =\n index === 0 ? lastKeyPath.key : `${lastKeyPath.key} (${index})`;\n newKeyPath = [\n ...otherKeyPath,\n { ...lastKeyPath, key: finalKey } as KeyPath,\n ];\n }\n }\n\n const updatedContent = editDictionaryByKeyPath(\n currentContent,\n newKeyPath,\n newValue\n );\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: updatedContent as Dictionary['content'],\n },\n });\n }\n\n renameContent(\n localDictionaryId: LocalDictionaryId,\n newKey: KeyPath['key'],\n keyPath: KeyPath[] = []\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n const updated = renameContentNodeByKeyPath(currentContent, newKey, keyPath);\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: updated as Dictionary['content'],\n },\n });\n }\n\n removeContent(\n localDictionaryId: LocalDictionaryId,\n keyPath: KeyPath[]\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n const initialContent = getContentNodeByKeyPath(originalContent, keyPath);\n const restored = editDictionaryByKeyPath(\n currentContent,\n keyPath,\n initialContent\n );\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: restored as Dictionary['content'],\n },\n });\n }\n\n restoreContent(localDictionaryId: LocalDictionaryId): void {\n const current = this.editedContent.value ?? {};\n const updated = { ...current };\n\n delete updated[localDictionaryId];\n\n this.editedContent.set(updated);\n }\n\n clearContent(localDictionaryId: LocalDictionaryId): void {\n const current = this.editedContent.value ?? {};\n const filtered = { ...current };\n\n delete filtered[localDictionaryId];\n\n this.editedContent.set(filtered);\n }\n\n clearAllContent(): void {\n this.editedContent.set({});\n }\n\n getContentValue(\n localDictionaryIdOrKey: LocalDictionaryId | string,\n keyPath: KeyPath[]\n ): ContentNode | undefined {\n const edited = this.editedContent.value;\n if (!edited) return undefined;\n\n const filteredKeyPath = keyPath.filter(\n (key) => key.type !== NodeTypes.TRANSLATION\n );\n\n // Only use edited content entries whose localId is known to this client.\n // This prevents stale edits from other apps (different framework demos) from\n // being applied when the editor sends back its stored editedContent.\n const localeDicts = this.localeDictionaries.value;\n\n const isDictionaryId =\n localDictionaryIdOrKey.includes(':local:') ||\n localDictionaryIdOrKey.includes(':remote:');\n\n if (isDictionaryId) {\n // If localeDictionaries is loaded, verify this localId belongs to us\n if (localeDicts && !(localDictionaryIdOrKey in localeDicts)) {\n return undefined;\n }\n const content =\n edited[localDictionaryIdOrKey as LocalDictionaryId]?.content ?? {};\n\n return getContentNodeByKeyPath(\n content,\n filteredKeyPath,\n this.currentLocale.value\n );\n }\n\n const matchingIds = Object.keys(edited).filter(\n (key) =>\n key.startsWith(`${localDictionaryIdOrKey}:`) &&\n // If localeDictionaries is loaded, only include known localIds\n (!localeDicts || key in localeDicts)\n );\n\n for (const localId of matchingIds) {\n const content = edited[localId as LocalDictionaryId]?.content ?? {};\n const node = getContentNodeByKeyPath(\n content,\n filteredKeyPath,\n this.currentLocale.value\n );\n if (node) return node;\n }\n\n return undefined;\n }\n\n /**\n * EDITOR mode: listen for CLIENT_READY and respond with EDITOR_ACTIVATE.\n * Also pings the client immediately in case it loaded before the editor.\n */\n private _setupEditorHandshake(): void {\n // When the client announces it is ready, activate it\n this._unsubClientReady = this.messenger.subscribe(\n MessageKey.INTLAYER_CLIENT_READY,\n () => {\n this.editorEnabled.set(true);\n this.messenger.send(MessageKey.INTLAYER_EDITOR_ACTIVATE);\n }\n );\n\n // Ping any already-running client (covers editor-opens-after-client scenario)\n this.messenger.send(MessageKey.INTLAYER_ARE_YOU_THERE);\n }\n\n private _setupActivationHandshake(): void {\n // Announce to the editor that the client is ready\n this.messenger.send(MessageKey.INTLAYER_CLIENT_READY);\n\n // Respond to \"are you there?\" pings from the editor\n this._unsubAreYouThere = this.messenger.subscribe(\n MessageKey.INTLAYER_ARE_YOU_THERE,\n () => {\n this.messenger.send(MessageKey.INTLAYER_CLIENT_READY);\n }\n );\n\n // When the editor activates us, enable the selector and broadcast state\n this._unsubActivate = this.messenger.subscribe(\n MessageKey.INTLAYER_EDITOR_ACTIVATE,\n () => {\n this.editorEnabled.set(true);\n this._broadcastData();\n }\n );\n }\n\n private _broadcastData(): void {\n const configVal = this.configuration.value;\n\n if (configVal) {\n this.messenger.send(\n `${MessageKey.INTLAYER_CONFIGURATION}/post`,\n configVal\n );\n }\n const localeVal = this.currentLocale.value;\n\n if (localeVal) {\n this.messenger.send(\n `${MessageKey.INTLAYER_CURRENT_LOCALE}/post`,\n localeVal\n );\n }\n const dicts = this.localeDictionaries.value;\n\n if (dicts) {\n this.messenger.send(\n `${MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED}/post`,\n dicts\n );\n }\n }\n\n private async _loadDictionaries(): Promise<void> {\n try {\n const mod = await import('@intlayer/unmerged-dictionaries-entry');\n const unmergedDictionaries = mod.getUnmergedDictionaries();\n const dictionariesList = Object.fromEntries(\n Object.values(unmergedDictionaries)\n .flat()\n .map((dictionary) => [dictionary.localId, dictionary])\n ) as DictionaryContent;\n\n this.localeDictionaries.set(dictionariesList);\n\n // If the editor already activated us before dictionaries finished loading,\n // re-broadcast now so the editor receives the dictionaries.\n if (this.editorEnabled.value) {\n this._broadcastData();\n }\n } catch (e) {\n // Dynamic entry not available (expected in editor mode or when not configured)\n console.warn('[intlayer] Failed to load unmerged dictionaries:', e);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditorStateManager.mjs","names":[],"sources":["../../../src/core/EditorStateManager.ts"],"sourcesContent":["import {\n editDictionaryByKeyPath,\n getContentNodeByKeyPath,\n renameContentNodeByKeyPath,\n} from '@intlayer/core/dictionaryManipulator';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n ContentNode,\n Dictionary,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport { MessageKey } from '../messageKey';\nimport {\n CrossFrameMessenger,\n type MessengerConfig,\n} from './CrossFrameMessenger';\nimport { CrossFrameStateManager } from './CrossFrameStateManager';\nimport {\n getGlobalEditedContent,\n setGlobalEditedContent,\n subscribeToGlobalEditedContent,\n} from './editedContentBus';\nimport {\n getGlobalFocusedContent,\n setGlobalFocusedContent,\n subscribeToGlobalFocusedContent,\n} from './focusedContentBus';\nimport { IframeClickInterceptor } from './IframeClickInterceptor';\nimport { UrlStateManager } from './UrlStateManager';\n\nexport type DictionaryContent = Record<LocalDictionaryId, Dictionary>;\n\nexport type FileContent = {\n dictionaryKey: string;\n dictionaryLocalId?: LocalDictionaryId;\n keyPath?: KeyPath[];\n};\n\nexport type EditorStateManagerConfig = {\n /** 'client' = the app running inside the iframe; 'editor' = the editor wrapping the app */\n mode: 'editor' | 'client';\n /** Cross-frame messaging configuration */\n messenger: MessengerConfig;\n /** Optional initial Intlayer configuration to broadcast */\n configuration?: IntlayerConfig;\n};\n\n/**\n * EditorStateManager is the single entry point for all Intlayer editor state.\n * It is framework-agnostic: instantiate one instance at the root of the application\n * and subscribe to its EventTarget-based events from any framework adapter.\n *\n * Replaces all context providers, hooks and store files across React, Preact,\n * Solid, Svelte, and Vue integrations.\n */\nexport class EditorStateManager {\n readonly messenger: CrossFrameMessenger;\n readonly editorEnabled: CrossFrameStateManager<boolean>;\n readonly focusedContent: CrossFrameStateManager<FileContent | null>;\n readonly localeDictionaries: CrossFrameStateManager<DictionaryContent>;\n readonly editedContent: CrossFrameStateManager<DictionaryContent>;\n readonly configuration: CrossFrameStateManager<IntlayerConfig>;\n readonly currentLocale: CrossFrameStateManager<Locale | undefined>;\n readonly displayedDictionaryKeys: CrossFrameStateManager<string[]>;\n\n private readonly _urlManager: UrlStateManager;\n private readonly _iframeInterceptor: IframeClickInterceptor;\n private readonly _mode: 'editor' | 'client';\n private readonly _configuration: IntlayerConfig | undefined;\n\n // Client-mode handshake subscribers\n private _unsubAreYouThere: (() => void) | null = null;\n private _unsubActivate: (() => void) | null = null;\n // Editor-mode handshake subscriber\n private _unsubClientReady: (() => void) | null = null;\n\n // Client-mode displayed-keys tracking\n private _displayedKeysObserver: MutationObserver | null = null;\n private _displayedKeysTimer: ReturnType<typeof setTimeout> | null = null;\n private _displayedKeysListeners: Array<[string, EventListener]> = [];\n\n // Global editedContent bus sync\n private _editedContentFromBus = false;\n private _unsubGlobalEditedContent: (() => void) | null = null;\n private _editedContentBusHandler: ((e: Event) => void) | null = null;\n\n // Global focusedContent bus sync\n private _focusedContentFromBus = false;\n private _unsubGlobalFocusedContent: (() => void) | null = null;\n private _focusedContentBusHandler: ((e: Event) => void) | null = null;\n\n constructor(config: EditorStateManagerConfig) {\n this._mode = config.mode;\n this._configuration = config.configuration;\n\n this.messenger = new CrossFrameMessenger(config.messenger);\n\n this.editorEnabled = new CrossFrameStateManager<boolean>(\n MessageKey.INTLAYER_EDITOR_ENABLED,\n this.messenger,\n { emit: false, receive: true, initialValue: false }\n );\n\n this.focusedContent = new CrossFrameStateManager<FileContent | null>(\n MessageKey.INTLAYER_FOCUSED_CONTENT_CHANGED,\n this.messenger,\n { emit: true, receive: true, initialValue: null }\n );\n\n this.localeDictionaries = new CrossFrameStateManager<DictionaryContent>(\n MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED,\n this.messenger\n );\n\n this.editedContent = new CrossFrameStateManager<DictionaryContent>(\n MessageKey.INTLAYER_EDITED_CONTENT_CHANGED,\n this.messenger\n );\n\n this.configuration = new CrossFrameStateManager<IntlayerConfig>(\n MessageKey.INTLAYER_CONFIGURATION,\n this.messenger,\n {\n emit: true,\n receive: false,\n ...(config.configuration ? { initialValue: config.configuration } : {}),\n }\n );\n\n // Client emits its locale to the editor; editor receives it.\n this.currentLocale = new CrossFrameStateManager<Locale>(\n MessageKey.INTLAYER_CURRENT_LOCALE,\n this.messenger,\n {\n emit: config.mode === 'client',\n receive: config.mode === 'editor',\n }\n );\n\n // Client emits displayed dictionary keys; editor receives them.\n this.displayedDictionaryKeys = new CrossFrameStateManager<string[]>(\n MessageKey.INTLAYER_DISPLAYED_DICTIONARY_KEYS,\n this.messenger,\n {\n emit: config.mode === 'client',\n receive: config.mode === 'editor',\n initialValue: [],\n }\n );\n\n this._urlManager = new UrlStateManager(this.messenger);\n this._iframeInterceptor = new IframeClickInterceptor(this.messenger);\n }\n\n start(): void {\n this.messenger.start();\n this.editorEnabled.start();\n this.focusedContent.start();\n this.localeDictionaries.start();\n this.editedContent.start();\n this.configuration.start();\n this.currentLocale.start();\n this.displayedDictionaryKeys.start();\n this._startEditedContentBusSync();\n this._startFocusedContentBusSync();\n\n if (this._mode === 'client') {\n this._urlManager.start();\n this._iframeInterceptor.startInterceptor();\n this._loadDictionaries();\n this._startDisplayedDictionariesTracking();\n // Request current edited content from the editor\n this.messenger.send(`${MessageKey.INTLAYER_EDITED_CONTENT_CHANGED}/get`);\n // Activation handshake: only participate if editor.enabled !== false\n if (this._configuration?.editor?.enabled !== false) {\n this._setupActivationHandshake();\n }\n } else {\n this._iframeInterceptor.startMerger();\n this._setupEditorHandshake();\n }\n }\n\n stop(): void {\n this._unsubAreYouThere?.();\n this._unsubActivate?.();\n this._unsubClientReady?.();\n this._unsubAreYouThere = null;\n this._unsubActivate = null;\n this._unsubClientReady = null;\n this.messenger.stop();\n this.editorEnabled.stop();\n this.focusedContent.stop();\n this.localeDictionaries.stop();\n this.editedContent.stop();\n this.configuration.stop();\n this.currentLocale.stop();\n this.displayedDictionaryKeys.stop();\n this._stopDisplayedDictionariesTracking();\n this._stopEditedContentBusSync();\n this._stopFocusedContentBusSync();\n this._urlManager.stop();\n this._iframeInterceptor.stopInterceptor();\n this._iframeInterceptor.stopMerger();\n }\n\n // ─── Handshake helpers ───────────────────────────────────────────────────────\n\n /**\n * EDITOR mode: re-send ARE_YOU_THERE to attempt re-connection with the client.\n * Call this when the user clicks \"Enable Editor\" or when the iframe reloads.\n */\n pingClient(): void {\n if (this._mode !== 'editor') return;\n\n this.messenger.send(MessageKey.INTLAYER_ARE_YOU_THERE);\n }\n\n // ─── Focus helpers ──────────────────────────────────────────────────────────\n\n setFocusedContentKeyPath(keyPath: KeyPath[]): void {\n const filtered = keyPath.filter(\n (key) => key.type !== NodeTypes.TRANSLATION\n );\n const prev = this.focusedContent.value;\n\n if (!prev) return;\n\n this.focusedContent.set({ ...prev, keyPath: filtered });\n }\n\n // ─── Dictionary record helpers ───────────────────────────────────────────\n\n setLocaleDictionary(dictionary: Dictionary): void {\n if (!dictionary.localId) return;\n const current = this.localeDictionaries.value ?? {};\n\n this.localeDictionaries.set({\n ...current,\n [dictionary.localId as LocalDictionaryId]: dictionary,\n });\n }\n\n // ─── Edited content helpers ───────────────────────────────────────────────\n\n setEditedDictionary(newDict: Dictionary): void {\n if (!newDict.localId) {\n console.error('setEditedDictionary: missing localId', newDict);\n return;\n }\n const current = this.editedContent.value ?? {};\n\n this.editedContent.set({\n ...current,\n [newDict.localId as LocalDictionaryId]: newDict,\n });\n }\n\n setEditedContent(\n localDictionaryId: LocalDictionaryId,\n newValue: Dictionary['content']\n ): void {\n const current = this.editedContent.value ?? {};\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: newValue,\n },\n });\n }\n\n addContent(\n localDictionaryId: LocalDictionaryId,\n newValue: ContentNode,\n keyPath: KeyPath[] = [],\n overwrite = true\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n\n let newKeyPath = keyPath;\n if (!overwrite) {\n let index = 0;\n\n const otherKeyPath = keyPath.slice(0, -1);\n const lastKeyPath = keyPath[keyPath.length - 1];\n\n let finalKey = lastKeyPath.key;\n\n while (\n typeof getContentNodeByKeyPath(currentContent, newKeyPath) !==\n 'undefined'\n ) {\n index++;\n finalKey =\n index === 0 ? lastKeyPath.key : `${lastKeyPath.key} (${index})`;\n newKeyPath = [\n ...otherKeyPath,\n { ...lastKeyPath, key: finalKey } as KeyPath,\n ];\n }\n }\n\n const updatedContent = editDictionaryByKeyPath(\n currentContent,\n newKeyPath,\n newValue\n );\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: updatedContent as Dictionary['content'],\n },\n });\n }\n\n renameContent(\n localDictionaryId: LocalDictionaryId,\n newKey: KeyPath['key'],\n keyPath: KeyPath[] = []\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n const updated = renameContentNodeByKeyPath(currentContent, newKey, keyPath);\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: updated as Dictionary['content'],\n },\n });\n }\n\n removeContent(\n localDictionaryId: LocalDictionaryId,\n keyPath: KeyPath[]\n ): void {\n const current = this.editedContent.value ?? {};\n const localeDicts = this.localeDictionaries.value ?? {};\n const originalContent = localeDicts[localDictionaryId]?.content;\n const currentContent = structuredClone(\n current[localDictionaryId]?.content ?? originalContent\n );\n const initialContent = getContentNodeByKeyPath(originalContent, keyPath);\n const restored = editDictionaryByKeyPath(\n currentContent,\n keyPath,\n initialContent\n );\n\n this.editedContent.set({\n ...current,\n [localDictionaryId]: {\n ...current[localDictionaryId],\n content: restored as Dictionary['content'],\n },\n });\n }\n\n restoreContent(localDictionaryId: LocalDictionaryId): void {\n const current = this.editedContent.value ?? {};\n const updated = { ...current };\n\n delete updated[localDictionaryId];\n\n this.editedContent.set(updated);\n }\n\n clearContent(localDictionaryId: LocalDictionaryId): void {\n const current = this.editedContent.value ?? {};\n const filtered = { ...current };\n\n delete filtered[localDictionaryId];\n\n this.editedContent.set(filtered);\n }\n\n clearAllContent(): void {\n this.editedContent.set({});\n }\n\n getContentValue(\n localDictionaryIdOrKey: LocalDictionaryId | string,\n keyPath: KeyPath[]\n ): ContentNode | undefined {\n const edited = this.editedContent.value;\n if (!edited) return undefined;\n\n const filteredKeyPath = keyPath.filter(\n (key) => key.type !== NodeTypes.TRANSLATION\n );\n\n // Only use edited content entries whose localId is known to this client.\n // This prevents stale edits from other apps (different framework demos) from\n // being applied when the editor sends back its stored editedContent.\n const localeDicts = this.localeDictionaries.value;\n\n const isDictionaryId =\n localDictionaryIdOrKey.includes(':local:') ||\n localDictionaryIdOrKey.includes(':remote:');\n\n if (isDictionaryId) {\n // If localeDictionaries is loaded, verify this localId belongs to us\n if (localeDicts && !(localDictionaryIdOrKey in localeDicts)) {\n return undefined;\n }\n const content =\n edited[localDictionaryIdOrKey as LocalDictionaryId]?.content ?? {};\n\n return getContentNodeByKeyPath(\n content,\n filteredKeyPath,\n this.currentLocale.value\n );\n }\n\n const matchingIds = Object.keys(edited).filter(\n (key) =>\n key.startsWith(`${localDictionaryIdOrKey}:`) &&\n // If localeDictionaries is loaded, only include known localIds\n (!localeDicts || key in localeDicts)\n );\n\n for (const localId of matchingIds) {\n const content = edited[localId as LocalDictionaryId]?.content ?? {};\n const node = getContentNodeByKeyPath(\n content,\n filteredKeyPath,\n this.currentLocale.value\n );\n if (node) return node;\n }\n\n return undefined;\n }\n\n // ─── Global editedContent bus sync ───────────────────────────────────────\n\n private _startEditedContentBusSync(): void {\n // Push local changes to the global bus (loop-guarded)\n this._editedContentBusHandler = (e: Event) => {\n if (this._editedContentFromBus) return;\n const content = (e as CustomEvent<DictionaryContent>).detail;\n setGlobalEditedContent(content, this.messenger.senderId);\n };\n this.editedContent.addEventListener(\n 'change',\n this._editedContentBusHandler\n );\n\n // Receive bus changes from other managers\n this._unsubGlobalEditedContent = subscribeToGlobalEditedContent(\n (content, sourceId) => {\n if (sourceId === this.messenger.senderId) return;\n this._editedContentFromBus = true;\n this.editedContent.set(content);\n this._editedContentFromBus = false;\n }\n );\n\n // Seed local value from the bus if bus already has content\n const existing = getGlobalEditedContent();\n if (Object.keys(existing).length > 0) {\n this._editedContentFromBus = true;\n this.editedContent.set(existing);\n this._editedContentFromBus = false;\n }\n }\n\n private _stopEditedContentBusSync(): void {\n if (this._editedContentBusHandler) {\n this.editedContent.removeEventListener(\n 'change',\n this._editedContentBusHandler\n );\n this._editedContentBusHandler = null;\n }\n this._unsubGlobalEditedContent?.();\n this._unsubGlobalEditedContent = null;\n }\n\n // ─── Global focusedContent bus sync ──────────────────────────────────────\n\n private _startFocusedContentBusSync(): void {\n this._focusedContentBusHandler = (e: Event) => {\n if (this._focusedContentFromBus) return;\n const content = (e as CustomEvent<FileContent | null>).detail;\n setGlobalFocusedContent(content, this.messenger.senderId);\n };\n this.focusedContent.addEventListener(\n 'change',\n this._focusedContentBusHandler\n );\n\n this._unsubGlobalFocusedContent = subscribeToGlobalFocusedContent(\n (content, sourceId) => {\n if (sourceId === this.messenger.senderId) return;\n this._focusedContentFromBus = true;\n this.focusedContent.set(content);\n this._focusedContentFromBus = false;\n }\n );\n\n const existing = getGlobalFocusedContent();\n if (existing !== undefined) {\n this._focusedContentFromBus = true;\n this.focusedContent.set(existing);\n this._focusedContentFromBus = false;\n }\n }\n\n private _stopFocusedContentBusSync(): void {\n if (this._focusedContentBusHandler) {\n this.focusedContent.removeEventListener(\n 'change',\n this._focusedContentBusHandler\n );\n this._focusedContentBusHandler = null;\n }\n this._unsubGlobalFocusedContent?.();\n this._unsubGlobalFocusedContent = null;\n }\n\n // ─── Displayed dictionaries tracking (client mode only) ──────────────────\n\n private _scanDisplayedDictionaryKeys(): void {\n if (typeof document === 'undefined') return;\n const elements = document.querySelectorAll(\n 'intlayer-content-selector-wrapper[dictionary-key]'\n );\n const keys = Array.from(\n new Set(\n Array.from(elements)\n .map((el) => el.getAttribute('dictionary-key') ?? '')\n .filter(Boolean)\n )\n );\n this.displayedDictionaryKeys.set(keys);\n }\n\n private _startDisplayedDictionariesTracking(): void {\n if (\n typeof document === 'undefined' ||\n typeof MutationObserver === 'undefined'\n )\n return;\n\n const schedule = () => {\n if (this._displayedKeysTimer) clearTimeout(this._displayedKeysTimer);\n this._displayedKeysTimer = setTimeout(\n () => this._scanDisplayedDictionaryKeys(),\n 100\n );\n };\n\n this._displayedKeysObserver = new MutationObserver(schedule);\n this._displayedKeysObserver.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n for (const evt of ['locationchange', 'popstate'] as const) {\n const listener = schedule as EventListener;\n window.addEventListener(evt, listener);\n this._displayedKeysListeners.push([evt, listener]);\n }\n\n this._scanDisplayedDictionaryKeys();\n }\n\n private _stopDisplayedDictionariesTracking(): void {\n this._displayedKeysObserver?.disconnect();\n this._displayedKeysObserver = null;\n if (this._displayedKeysTimer) {\n clearTimeout(this._displayedKeysTimer);\n this._displayedKeysTimer = null;\n }\n for (const [evt, listener] of this._displayedKeysListeners) {\n window.removeEventListener(evt, listener);\n }\n this._displayedKeysListeners = [];\n }\n\n // ─── Handshake helpers ───────────────────────────────────────────────────────\n\n /**\n * EDITOR mode: listen for CLIENT_READY and respond with EDITOR_ACTIVATE.\n * Also pings the client immediately in case it loaded before the editor.\n */\n private _setupEditorHandshake(): void {\n // When the client announces it is ready, activate it\n this._unsubClientReady = this.messenger.subscribe(\n MessageKey.INTLAYER_CLIENT_READY,\n () => {\n this.editorEnabled.set(true);\n this.messenger.send(MessageKey.INTLAYER_EDITOR_ACTIVATE);\n }\n );\n\n // Ping any already-running client (covers editor-opens-after-client scenario)\n this.messenger.send(MessageKey.INTLAYER_ARE_YOU_THERE);\n }\n\n private _setupActivationHandshake(): void {\n // Announce to the editor that the client is ready\n this.messenger.send(MessageKey.INTLAYER_CLIENT_READY);\n\n // Respond to \"are you there?\" pings from the editor\n this._unsubAreYouThere = this.messenger.subscribe(\n MessageKey.INTLAYER_ARE_YOU_THERE,\n () => {\n this.messenger.send(MessageKey.INTLAYER_CLIENT_READY);\n }\n );\n\n // When the editor activates us, enable the selector and broadcast state\n this._unsubActivate = this.messenger.subscribe(\n MessageKey.INTLAYER_EDITOR_ACTIVATE,\n () => {\n this.editorEnabled.set(true);\n this._broadcastData();\n }\n );\n }\n\n private _broadcastData(): void {\n const configVal = this.configuration.value;\n\n if (configVal) {\n this.messenger.send(\n `${MessageKey.INTLAYER_CONFIGURATION}/post`,\n configVal\n );\n }\n const localeVal = this.currentLocale.value;\n\n if (localeVal) {\n this.messenger.send(\n `${MessageKey.INTLAYER_CURRENT_LOCALE}/post`,\n localeVal\n );\n }\n const dicts = this.localeDictionaries.value;\n\n if (dicts) {\n this.messenger.send(\n `${MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED}/post`,\n dicts\n );\n }\n }\n\n private async _loadDictionaries(): Promise<void> {\n try {\n const mod = await import('@intlayer/unmerged-dictionaries-entry');\n const unmergedDictionaries = mod.getUnmergedDictionaries();\n const dictionariesList = Object.fromEntries(\n Object.values(unmergedDictionaries)\n .flat()\n .map((dictionary) => [dictionary.localId, dictionary])\n ) as DictionaryContent;\n\n this.localeDictionaries.set(dictionariesList);\n\n // If the editor already activated us before dictionaries finished loading,\n // re-broadcast now so the editor receives the dictionaries.\n if (this.editorEnabled.value) {\n this._broadcastData();\n }\n } catch (e) {\n // Dynamic entry not available (expected in editor mode or when not configured)\n console.warn('[intlayer] Failed to load unmerged dictionaries:', e);\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ //#region src/core/editedContentBus.ts
2
+ const BUS_KEY = "__intlayer_edited_content_bus__";
3
+ const BUS_EVENTS_KEY = "__intlayer_edited_content_bus_events__";
4
+ const getBusTarget = () => {
5
+ if (typeof window === "undefined") return new EventTarget();
6
+ const w = window;
7
+ if (!w[BUS_EVENTS_KEY]) w[BUS_EVENTS_KEY] = new EventTarget();
8
+ return w[BUS_EVENTS_KEY];
9
+ };
10
+ const getGlobalEditedContent = () => {
11
+ if (typeof window === "undefined") return {};
12
+ return window[BUS_KEY] ?? {};
13
+ };
14
+ const setGlobalEditedContent = (content, sourceId) => {
15
+ if (typeof window !== "undefined") window[BUS_KEY] = content;
16
+ getBusTarget().dispatchEvent(new CustomEvent("change", { detail: {
17
+ content,
18
+ sourceId
19
+ } }));
20
+ };
21
+ const subscribeToGlobalEditedContent = (cb) => {
22
+ const handler = (e) => {
23
+ const { content, sourceId } = e.detail;
24
+ cb(content, sourceId);
25
+ };
26
+ const target = getBusTarget();
27
+ target.addEventListener("change", handler);
28
+ return () => target.removeEventListener("change", handler);
29
+ };
30
+
31
+ //#endregion
32
+ export { getGlobalEditedContent, setGlobalEditedContent, subscribeToGlobalEditedContent };
33
+ //# sourceMappingURL=editedContentBus.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editedContentBus.mjs","names":[],"sources":["../../../src/core/editedContentBus.ts"],"sourcesContent":["import type { DictionaryContent } from './EditorStateManager';\n\nconst BUS_KEY = '__intlayer_edited_content_bus__';\nconst BUS_EVENTS_KEY = '__intlayer_edited_content_bus_events__';\n\ntype WindowWithBus = typeof window & {\n [BUS_KEY]?: DictionaryContent;\n [BUS_EVENTS_KEY]?: EventTarget;\n};\n\ntype BusEvent = { content: DictionaryContent; sourceId?: string };\n\nconst getBusTarget = (): EventTarget => {\n if (typeof window === 'undefined') return new EventTarget();\n const w = window as WindowWithBus;\n if (!w[BUS_EVENTS_KEY]) w[BUS_EVENTS_KEY] = new EventTarget();\n return w[BUS_EVENTS_KEY]!;\n};\n\nexport const getGlobalEditedContent = (): DictionaryContent => {\n if (typeof window === 'undefined') return {};\n return (window as WindowWithBus)[BUS_KEY] ?? {};\n};\n\nexport const setGlobalEditedContent = (\n content: DictionaryContent,\n sourceId?: string\n): void => {\n if (typeof window !== 'undefined') {\n (window as WindowWithBus)[BUS_KEY] = content;\n }\n getBusTarget().dispatchEvent(\n new CustomEvent<BusEvent>('change', { detail: { content, sourceId } })\n );\n};\n\nexport const subscribeToGlobalEditedContent = (\n cb: (content: DictionaryContent, sourceId?: string) => void\n): (() => void) => {\n const handler = (e: Event) => {\n const { content, sourceId } = (e as CustomEvent<BusEvent>).detail;\n cb(content, sourceId);\n };\n const target = getBusTarget();\n target.addEventListener('change', handler);\n return () => target.removeEventListener('change', handler);\n};\n"],"mappings":";AAEA,MAAM,UAAU;AAChB,MAAM,iBAAiB;AASvB,MAAM,qBAAkC;CACtC,IAAI,OAAO,WAAW,aAAa,OAAO,IAAI,YAAY;CAC1D,MAAM,IAAI;CACV,IAAI,CAAC,EAAE,iBAAiB,EAAE,kBAAkB,IAAI,YAAY;CAC5D,OAAO,EAAE;AACX;AAEA,MAAa,+BAAkD;CAC7D,IAAI,OAAO,WAAW,aAAa,OAAO,CAAC;CAC3C,OAAQ,OAAyB,YAAY,CAAC;AAChD;AAEA,MAAa,0BACX,SACA,aACS;CACT,IAAI,OAAO,WAAW,aACpB,AAAC,OAAyB,WAAW;CAEvC,aAAa,EAAE,cACb,IAAI,YAAsB,UAAU,EAAE,QAAQ;EAAE;EAAS;CAAS,EAAE,CAAC,CACvE;AACF;AAEA,MAAa,kCACX,OACiB;CACjB,MAAM,WAAW,MAAa;EAC5B,MAAM,EAAE,SAAS,aAAc,EAA4B;EAC3D,GAAG,SAAS,QAAQ;CACtB;CACA,MAAM,SAAS,aAAa;CAC5B,OAAO,iBAAiB,UAAU,OAAO;CACzC,aAAa,OAAO,oBAAoB,UAAU,OAAO;AAC3D"}
@@ -0,0 +1,39 @@
1
+ //#region src/core/focusedContentBus.ts
2
+ const BUS_KEY = "__intlayer_focused_content_bus__";
3
+ const BUS_EVENTS_KEY = "__intlayer_focused_content_bus_events__";
4
+ const getBusTarget = () => {
5
+ if (typeof window === "undefined") return new EventTarget();
6
+ const w = window;
7
+ if (!w[BUS_EVENTS_KEY]) w[BUS_EVENTS_KEY] = new EventTarget();
8
+ return w[BUS_EVENTS_KEY];
9
+ };
10
+ const getGlobalFocusedContent = () => {
11
+ if (typeof window === "undefined") return void 0;
12
+ const w = window;
13
+ if (!w.__intlayer_focused_content_bus_set__) return void 0;
14
+ return w[BUS_KEY] ?? null;
15
+ };
16
+ const setGlobalFocusedContent = (content, sourceId) => {
17
+ if (typeof window !== "undefined") {
18
+ const w = window;
19
+ w[BUS_KEY] = content;
20
+ w.__intlayer_focused_content_bus_set__ = true;
21
+ }
22
+ getBusTarget().dispatchEvent(new CustomEvent("change", { detail: {
23
+ content,
24
+ sourceId
25
+ } }));
26
+ };
27
+ const subscribeToGlobalFocusedContent = (cb) => {
28
+ const handler = (e) => {
29
+ const { content, sourceId } = e.detail;
30
+ cb(content, sourceId);
31
+ };
32
+ const target = getBusTarget();
33
+ target.addEventListener("change", handler);
34
+ return () => target.removeEventListener("change", handler);
35
+ };
36
+
37
+ //#endregion
38
+ export { getGlobalFocusedContent, setGlobalFocusedContent, subscribeToGlobalFocusedContent };
39
+ //# sourceMappingURL=focusedContentBus.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focusedContentBus.mjs","names":[],"sources":["../../../src/core/focusedContentBus.ts"],"sourcesContent":["import type { FileContent } from './EditorStateManager';\n\nconst BUS_KEY = '__intlayer_focused_content_bus__';\nconst BUS_EVENTS_KEY = '__intlayer_focused_content_bus_events__';\n\ntype WindowWithBus = typeof window & {\n [BUS_KEY]?: FileContent | null;\n [BUS_EVENTS_KEY]?: EventTarget;\n __intlayer_focused_content_bus_set__?: boolean;\n};\n\ntype BusEvent = { content: FileContent | null; sourceId?: string };\n\nconst getBusTarget = (): EventTarget => {\n if (typeof window === 'undefined') return new EventTarget();\n const w = window as WindowWithBus;\n if (!w[BUS_EVENTS_KEY]) w[BUS_EVENTS_KEY] = new EventTarget();\n return w[BUS_EVENTS_KEY]!;\n};\n\nexport const getGlobalFocusedContent = (): FileContent | null | undefined => {\n if (typeof window === 'undefined') return undefined;\n const w = window as WindowWithBus;\n if (!w.__intlayer_focused_content_bus_set__) return undefined;\n return w[BUS_KEY] ?? null;\n};\n\nexport const setGlobalFocusedContent = (\n content: FileContent | null,\n sourceId?: string\n): void => {\n if (typeof window !== 'undefined') {\n const w = window as WindowWithBus;\n w[BUS_KEY] = content;\n w.__intlayer_focused_content_bus_set__ = true;\n }\n getBusTarget().dispatchEvent(\n new CustomEvent<BusEvent>('change', { detail: { content, sourceId } })\n );\n};\n\nexport const subscribeToGlobalFocusedContent = (\n cb: (content: FileContent | null, sourceId?: string) => void\n): (() => void) => {\n const handler = (e: Event) => {\n const { content, sourceId } = (e as CustomEvent<BusEvent>).detail;\n cb(content, sourceId);\n };\n const target = getBusTarget();\n target.addEventListener('change', handler);\n return () => target.removeEventListener('change', handler);\n};\n"],"mappings":";AAEA,MAAM,UAAU;AAChB,MAAM,iBAAiB;AAUvB,MAAM,qBAAkC;CACtC,IAAI,OAAO,WAAW,aAAa,OAAO,IAAI,YAAY;CAC1D,MAAM,IAAI;CACV,IAAI,CAAC,EAAE,iBAAiB,EAAE,kBAAkB,IAAI,YAAY;CAC5D,OAAO,EAAE;AACX;AAEA,MAAa,gCAAgE;CAC3E,IAAI,OAAO,WAAW,aAAa,OAAO;CAC1C,MAAM,IAAI;CACV,IAAI,CAAC,EAAE,sCAAsC,OAAO;CACpD,OAAO,EAAE,YAAY;AACvB;AAEA,MAAa,2BACX,SACA,aACS;CACT,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,IAAI;EACV,EAAE,WAAW;EACb,EAAE,uCAAuC;CAC3C;CACA,aAAa,EAAE,cACb,IAAI,YAAsB,UAAU,EAAE,QAAQ;EAAE;EAAS;CAAS,EAAE,CAAC,CACvE;AACF;AAEA,MAAa,mCACX,OACiB;CACjB,MAAM,WAAW,MAAa;EAC5B,MAAM,EAAE,SAAS,aAAc,EAA4B;EAC3D,GAAG,SAAS,QAAQ;CACtB;CACA,MAAM,SAAS,aAAa;CAC5B,OAAO,iBAAiB,UAAU,OAAO;CACzC,aAAa,OAAO,oBAAoB,UAAU,OAAO;AAC3D"}
@@ -1,9 +1,11 @@
1
1
  import { getGlobalEditorManager, onGlobalEditorManagerChange, setGlobalEditorManager } from "./globalManager.mjs";
2
2
  import { CrossFrameMessenger } from "./CrossFrameMessenger.mjs";
3
3
  import { CrossFrameStateManager } from "./CrossFrameStateManager.mjs";
4
+ import { getGlobalEditedContent, setGlobalEditedContent, subscribeToGlobalEditedContent } from "./editedContentBus.mjs";
5
+ import { getGlobalFocusedContent, setGlobalFocusedContent, subscribeToGlobalFocusedContent } from "./focusedContentBus.mjs";
4
6
  import { IframeClickInterceptor } from "./IframeClickInterceptor.mjs";
5
7
  import { UrlStateManager } from "./UrlStateManager.mjs";
6
8
  import { EditorStateManager } from "./EditorStateManager.mjs";
7
9
  import { buildClientMessengerConfig, initEditorClient, stopEditorClient } from "./initEditorClient.mjs";
8
10
 
9
- export { CrossFrameMessenger, CrossFrameStateManager, EditorStateManager, IframeClickInterceptor, UrlStateManager, buildClientMessengerConfig, getGlobalEditorManager, initEditorClient, onGlobalEditorManagerChange, setGlobalEditorManager, stopEditorClient };
11
+ export { CrossFrameMessenger, CrossFrameStateManager, EditorStateManager, IframeClickInterceptor, UrlStateManager, buildClientMessengerConfig, getGlobalEditedContent, getGlobalEditorManager, getGlobalFocusedContent, initEditorClient, onGlobalEditorManagerChange, setGlobalEditedContent, setGlobalEditorManager, setGlobalFocusedContent, stopEditorClient, subscribeToGlobalEditedContent, subscribeToGlobalFocusedContent };
@@ -6,6 +6,8 @@ import { IntlayerContentSelectorWrapperElement, defineIntlayerContentSelectorWra
6
6
  import { IntlayerEditedContentElement, defineIntlayerEditedContent } from "./components/EditedContent.mjs";
7
7
  import { CrossFrameMessenger } from "./core/CrossFrameMessenger.mjs";
8
8
  import { CrossFrameStateManager } from "./core/CrossFrameStateManager.mjs";
9
+ import { getGlobalEditedContent, setGlobalEditedContent, subscribeToGlobalEditedContent } from "./core/editedContentBus.mjs";
10
+ import { getGlobalFocusedContent, setGlobalFocusedContent, subscribeToGlobalFocusedContent } from "./core/focusedContentBus.mjs";
9
11
  import { IframeClickInterceptor } from "./core/IframeClickInterceptor.mjs";
10
12
  import { UrlStateManager } from "./core/UrlStateManager.mjs";
11
13
  import { EditorStateManager } from "./core/EditorStateManager.mjs";
@@ -13,4 +15,4 @@ import { buildClientMessengerConfig, initEditorClient, stopEditorClient } from "
13
15
  import { IntlayerEditorElement, defineIntlayerEditorElement } from "./components/IntlayerEditor.mjs";
14
16
  import { IntlayerContentSelectorElement, defineIntlayerElements } from "./components/ContentSelector.mjs";
15
17
 
16
- export { CrossFrameMessenger, CrossFrameStateManager, EditorStateManager, IframeClickInterceptor, IntlayerContentSelectorElement, IntlayerContentSelectorWrapperElement, IntlayerEditedContentElement, IntlayerEditorElement, MessageKey, UrlStateManager, buildClientMessengerConfig, compareUrls, defineIntlayerContentSelectorWrapper, defineIntlayerEditedContent, defineIntlayerEditorElement, defineIntlayerElements, getGlobalEditorManager, initEditorClient, mergeIframeClick, onGlobalEditorManagerChange, setGlobalEditorManager, stopEditorClient };
18
+ export { CrossFrameMessenger, CrossFrameStateManager, EditorStateManager, IframeClickInterceptor, IntlayerContentSelectorElement, IntlayerContentSelectorWrapperElement, IntlayerEditedContentElement, IntlayerEditorElement, MessageKey, UrlStateManager, buildClientMessengerConfig, compareUrls, defineIntlayerContentSelectorWrapper, defineIntlayerEditedContent, defineIntlayerEditorElement, defineIntlayerElements, getGlobalEditedContent, getGlobalEditorManager, getGlobalFocusedContent, initEditorClient, mergeIframeClick, onGlobalEditorManagerChange, setGlobalEditedContent, setGlobalEditorManager, setGlobalFocusedContent, stopEditorClient, subscribeToGlobalEditedContent, subscribeToGlobalFocusedContent };
@@ -21,6 +21,8 @@ let MessageKey = /* @__PURE__ */ function(MessageKey) {
21
21
  MessageKey["INTLAYER_EDITED_CONTENT_CHANGED"] = "INTLAYER_EDITED_CONTENT_CHANGED";
22
22
  /** Client → editor: Helper to sync iframe client reactivity on click client */
23
23
  MessageKey["INTLAYER_IFRAME_CLICKED"] = "INTLAYER_IFRAME_CLICKED";
24
+ /** Client → editor: list of dictionary keys currently rendered in the iframe */
25
+ MessageKey["INTLAYER_DISPLAYED_DICTIONARY_KEYS"] = "INTLAYER_DISPLAYED_DICTIONARY_KEYS";
24
26
  return MessageKey;
25
27
  }({});
26
28
 
@@ -1 +1 @@
1
- {"version":3,"file":"messageKey.mjs","names":[],"sources":["../../src/messageKey.ts"],"sourcesContent":["export enum MessageKey {\n /** Client → editor: announces the client is ready (also used as response to ARE_YOU_THERE) */\n INTLAYER_CLIENT_READY = 'INTLAYER_CLIENT_READY',\n /** Editor → client: asks if the client is still alive */\n INTLAYER_ARE_YOU_THERE = 'INTLAYER_ARE_YOU_THERE',\n /** Editor → client: instructs the client to activate the editor selector */\n INTLAYER_EDITOR_ACTIVATE = 'INTLAYER_EDITOR_ACTIVATE',\n /** Client → editor: instructs the editor that the client is enabled */\n INTLAYER_EDITOR_ENABLED = 'INTLAYER_EDITOR_ENABLED',\n\n /** Client → editor: send information related the app */\n INTLAYER_CONFIGURATION = 'INTLAYER_CONFIGURATION',\n INTLAYER_CURRENT_LOCALE = 'INTLAYER_CURRENT_LOCALE',\n INTLAYER_URL_CHANGE = 'INTLAYER_URL_CHANGE',\n\n /** Client → editor: load and update content */\n INTLAYER_LOCALE_DICTIONARIES_CHANGED = 'INTLAYER_LOCALE_DICTIONARIES_CHANGED',\n INTLAYER_DISTANT_DICTIONARIES_CHANGED = 'INTLAYER_DISTANT_DICTIONARIES_CHANGED',\n\n /** Client → editor & Editor → client: Update focus and changed content */\n INTLAYER_HOVERED_CONTENT_CHANGED = 'INTLAYER_HOVERED_CONTENT_CHANGED',\n INTLAYER_FOCUSED_CONTENT_CHANGED = 'INTLAYER_FOCUSED_CONTENT_CHANGED',\n INTLAYER_EDITED_CONTENT_CHANGED = 'INTLAYER_EDITED_CONTENT_CHANGED',\n\n /** Client → editor: Helper to sync iframe client reactivity on click client */\n INTLAYER_IFRAME_CLICKED = 'INTLAYER_IFRAME_CLICKED',\n}\n"],"mappings":";AAAA,IAAY,aAAL;;CAEL;;CAEA;;CAEA;;CAEA;;CAGA;CACA;CACA;;CAGA;CACA;;CAGA;CACA;CACA;;CAGA;;AACF"}
1
+ {"version":3,"file":"messageKey.mjs","names":[],"sources":["../../src/messageKey.ts"],"sourcesContent":["export enum MessageKey {\n /** Client → editor: announces the client is ready (also used as response to ARE_YOU_THERE) */\n INTLAYER_CLIENT_READY = 'INTLAYER_CLIENT_READY',\n /** Editor → client: asks if the client is still alive */\n INTLAYER_ARE_YOU_THERE = 'INTLAYER_ARE_YOU_THERE',\n /** Editor → client: instructs the client to activate the editor selector */\n INTLAYER_EDITOR_ACTIVATE = 'INTLAYER_EDITOR_ACTIVATE',\n /** Client → editor: instructs the editor that the client is enabled */\n INTLAYER_EDITOR_ENABLED = 'INTLAYER_EDITOR_ENABLED',\n\n /** Client → editor: send information related the app */\n INTLAYER_CONFIGURATION = 'INTLAYER_CONFIGURATION',\n INTLAYER_CURRENT_LOCALE = 'INTLAYER_CURRENT_LOCALE',\n INTLAYER_URL_CHANGE = 'INTLAYER_URL_CHANGE',\n\n /** Client → editor: load and update content */\n INTLAYER_LOCALE_DICTIONARIES_CHANGED = 'INTLAYER_LOCALE_DICTIONARIES_CHANGED',\n INTLAYER_DISTANT_DICTIONARIES_CHANGED = 'INTLAYER_DISTANT_DICTIONARIES_CHANGED',\n\n /** Client → editor & Editor → client: Update focus and changed content */\n INTLAYER_HOVERED_CONTENT_CHANGED = 'INTLAYER_HOVERED_CONTENT_CHANGED',\n INTLAYER_FOCUSED_CONTENT_CHANGED = 'INTLAYER_FOCUSED_CONTENT_CHANGED',\n INTLAYER_EDITED_CONTENT_CHANGED = 'INTLAYER_EDITED_CONTENT_CHANGED',\n\n /** Client → editor: Helper to sync iframe client reactivity on click client */\n INTLAYER_IFRAME_CLICKED = 'INTLAYER_IFRAME_CLICKED',\n\n /** Client → editor: list of dictionary keys currently rendered in the iframe */\n INTLAYER_DISPLAYED_DICTIONARY_KEYS = 'INTLAYER_DISPLAYED_DICTIONARY_KEYS',\n}\n"],"mappings":";AAAA,IAAY,aAAL;;CAEL;;CAEA;;CAEA;;CAEA;;CAGA;CACA;CACA;;CAGA;CACA;;CAGA;CACA;CACA;;CAGA;;CAGA;;AACF"}
@@ -33,6 +33,7 @@ declare class EditorStateManager {
33
33
  readonly editedContent: CrossFrameStateManager<DictionaryContent>;
34
34
  readonly configuration: CrossFrameStateManager<IntlayerConfig>;
35
35
  readonly currentLocale: CrossFrameStateManager<Locale | undefined>;
36
+ readonly displayedDictionaryKeys: CrossFrameStateManager<string[]>;
36
37
  private readonly _urlManager;
37
38
  private readonly _iframeInterceptor;
38
39
  private readonly _mode;
@@ -40,6 +41,15 @@ declare class EditorStateManager {
40
41
  private _unsubAreYouThere;
41
42
  private _unsubActivate;
42
43
  private _unsubClientReady;
44
+ private _displayedKeysObserver;
45
+ private _displayedKeysTimer;
46
+ private _displayedKeysListeners;
47
+ private _editedContentFromBus;
48
+ private _unsubGlobalEditedContent;
49
+ private _editedContentBusHandler;
50
+ private _focusedContentFromBus;
51
+ private _unsubGlobalFocusedContent;
52
+ private _focusedContentBusHandler;
43
53
  constructor(config: EditorStateManagerConfig);
44
54
  start(): void;
45
55
  stop(): void;
@@ -59,6 +69,13 @@ declare class EditorStateManager {
59
69
  clearContent(localDictionaryId: LocalDictionaryId): void;
60
70
  clearAllContent(): void;
61
71
  getContentValue(localDictionaryIdOrKey: LocalDictionaryId | string, keyPath: KeyPath[]): ContentNode | undefined;
72
+ private _startEditedContentBusSync;
73
+ private _stopEditedContentBusSync;
74
+ private _startFocusedContentBusSync;
75
+ private _stopFocusedContentBusSync;
76
+ private _scanDisplayedDictionaryKeys;
77
+ private _startDisplayedDictionariesTracking;
78
+ private _stopDisplayedDictionariesTracking;
62
79
  /**
63
80
  * EDITOR mode: listen for CLIENT_READY and respond with EDITOR_ACTIVATE.
64
81
  * Also pings the client immediately in case it loaded before the editor.
@@ -1 +1 @@
1
- {"version":3,"file":"EditorStateManager.d.ts","names":[],"sources":["../../../src/core/EditorStateManager.ts"],"mappings":";;;;;;;;KAuBY,iBAAA,GAAoB,MAAA,CAAO,iBAAA,EAAmB,UAAA;AAAA,KAE9C,WAAA;EACV,aAAA;EACA,iBAAA,GAAoB,iBAAA;EACpB,OAAA,GAAU,OAAO;AAAA;AAAA,KAGP,wBAAA;EARoB,2FAU9B,IAAA,uBAVoC;EAYpC,SAAA,EAAW,eAAA,EAZ0B;EAcrC,aAAA,GAAgB,cAAc;AAAA;AAdoC;AAEpE;;;;;;;AAFoE,cAyBvD,kBAAA;EAAA,SACF,SAAA,EAAW,mBAAA;EAAA,SACX,aAAA,EAAe,sBAAA;EAAA,SACf,cAAA,EAAgB,sBAAA,CAAuB,WAAA;EAAA,SACvC,kBAAA,EAAoB,sBAAA,CAAuB,iBAAA;EAAA,SAC3C,aAAA,EAAe,sBAAA,CAAuB,iBAAA;EAAA,SACtC,aAAA,EAAe,sBAAA,CAAuB,cAAA;EAAA,SACtC,aAAA,EAAe,sBAAA,CAAuB,MAAA;EAAA,iBAE9B,WAAA;EAAA,iBACA,kBAAA;EAAA,iBACA,KAAA;EAAA,iBACA,cAAA;EAAA,QAGT,iBAAA;EAAA,QACA,cAAA;EAAA,QAEA,iBAAA;cAEI,MAAA,EAAQ,wBAAA;EAoDpB,KAAA,CAAA;EAyBA,IAAA,CAAA;EAhGoB;;;;EAyHpB,UAAA,CAAA;EAQA,wBAAA,CAAyB,OAAA,EAAS,OAAA;EAalC,mBAAA,CAAoB,UAAA,EAAY,UAAA;EAYhC,mBAAA,CAAoB,OAAA,EAAS,UAAA;EAa7B,gBAAA,CACE,iBAAA,EAAmB,iBAAA,EACnB,QAAA,EAAU,UAAA;EAaZ,UAAA,CACE,iBAAA,EAAmB,iBAAA,EACnB,QAAA,EAAU,WAAA,EACV,OAAA,GAAS,OAAA,IACT,SAAA;EAgDF,aAAA,CACE,iBAAA,EAAmB,iBAAA,EACnB,MAAA,EAAQ,OAAA,SACR,OAAA,GAAS,OAAA;EAmBX,aAAA,CACE,iBAAA,EAAmB,iBAAA,EACnB,OAAA,EAAS,OAAA;EAwBX,cAAA,CAAe,iBAAA,EAAmB,iBAAA;EASlC,YAAA,CAAa,iBAAA,EAAmB,iBAAA;EAShC,eAAA,CAAA;EAIA,eAAA,CACE,sBAAA,EAAwB,iBAAA,WACxB,OAAA,EAAS,OAAA,KACR,WAAA;EA3IkB;;;;EAAA,QAmMb,qBAAA;EAAA,QAcA,yBAAA;EAAA,QAsBA,cAAA;EAAA,QA2BM,iBAAA;AAAA"}
1
+ {"version":3,"file":"EditorStateManager.d.ts","names":[],"sources":["../../../src/core/EditorStateManager.ts"],"mappings":";;;;;;;;KAiCY,iBAAA,GAAoB,MAAA,CAAO,iBAAA,EAAmB,UAAA;AAAA,KAE9C,WAAA;EACV,aAAA;EACA,iBAAA,GAAoB,iBAAA;EACpB,OAAA,GAAU,OAAO;AAAA;AAAA,KAGP,wBAAA;EARoB,2FAU9B,IAAA,uBAVoC;EAYpC,SAAA,EAAW,eAAA,EAZ0B;EAcrC,aAAA,GAAgB,cAAc;AAAA;AAdoC;AAEpE;;;;;;;AAFoE,cAyBvD,kBAAA;EAAA,SACF,SAAA,EAAW,mBAAA;EAAA,SACX,aAAA,EAAe,sBAAA;EAAA,SACf,cAAA,EAAgB,sBAAA,CAAuB,WAAA;EAAA,SACvC,kBAAA,EAAoB,sBAAA,CAAuB,iBAAA;EAAA,SAC3C,aAAA,EAAe,sBAAA,CAAuB,iBAAA;EAAA,SACtC,aAAA,EAAe,sBAAA,CAAuB,cAAA;EAAA,SACtC,aAAA,EAAe,sBAAA,CAAuB,MAAA;EAAA,SACtC,uBAAA,EAAyB,sBAAA;EAAA,iBAEjB,WAAA;EAAA,iBACA,kBAAA;EAAA,iBACA,KAAA;EAAA,iBACA,cAAA;EAAA,QAGT,iBAAA;EAAA,QACA,cAAA;EAAA,QAEA,iBAAA;EAAA,QAGA,sBAAA;EAAA,QACA,mBAAA;EAAA,QACA,uBAAA;EAAA,QAGA,qBAAA;EAAA,QACA,yBAAA;EAAA,QACA,wBAAA;EAAA,QAGA,sBAAA;EAAA,QACA,0BAAA;EAAA,QACA,yBAAA;cAEI,MAAA,EAAQ,wBAAA;EA+DpB,KAAA,CAAA;EA6BA,IAAA,CAAA;EAzH+C;;;;EAsJ/C,UAAA,CAAA;EAQA,wBAAA,CAAyB,OAAA,EAAS,OAAA;EAalC,mBAAA,CAAoB,UAAA,EAAY,UAAA;EAYhC,mBAAA,CAAoB,OAAA,EAAS,UAAA;EAa7B,gBAAA,CACE,iBAAA,EAAmB,iBAAA,EACnB,QAAA,EAAU,UAAA;EAaZ,UAAA,CACE,iBAAA,EAAmB,iBAAA,EACnB,QAAA,EAAU,WAAA,EACV,OAAA,GAAS,OAAA,IACT,SAAA;EAgDF,aAAA,CACE,iBAAA,EAAmB,iBAAA,EACnB,MAAA,EAAQ,OAAA,SACR,OAAA,GAAS,OAAA;EAmBX,aAAA,CACE,iBAAA,EAAmB,iBAAA,EACnB,OAAA,EAAS,OAAA;EAwBX,cAAA,CAAe,iBAAA,EAAmB,iBAAA;EASlC,YAAA,CAAa,iBAAA,EAAmB,iBAAA;EAShC,eAAA,CAAA;EAIA,eAAA,CACE,sBAAA,EAAwB,iBAAA,WACxB,OAAA,EAAS,OAAA,KACR,WAAA;EAAA,QAsDK,0BAAA;EAAA,QA+BA,yBAAA;EAAA,QAcA,2BAAA;EAAA,QA4BA,0BAAA;EAAA,QAcA,4BAAA;EAAA,QAeA,mCAAA;EAAA,QA8BA,kCAAA;EA1LM;;;;EAAA,QA6MN,qBAAA;EAAA,QAcA,yBAAA;EAAA,QAsBA,cAAA;EAAA,QA2BM,iBAAA;AAAA"}
@@ -0,0 +1,9 @@
1
+ import { DictionaryContent } from "./EditorStateManager.js";
2
+
3
+ //#region src/core/editedContentBus.d.ts
4
+ declare const getGlobalEditedContent: () => DictionaryContent;
5
+ declare const setGlobalEditedContent: (content: DictionaryContent, sourceId?: string) => void;
6
+ declare const subscribeToGlobalEditedContent: (cb: (content: DictionaryContent, sourceId?: string) => void) => (() => void);
7
+ //#endregion
8
+ export { getGlobalEditedContent, setGlobalEditedContent, subscribeToGlobalEditedContent };
9
+ //# sourceMappingURL=editedContentBus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editedContentBus.d.ts","names":[],"sources":["../../../src/core/editedContentBus.ts"],"mappings":";;;cAmBa,sBAAA,QAA6B,iBAGzC;AAAA,cAEY,sBAAA,GACX,OAAA,EAAS,iBAAiB,EAC1B,QAAA;AAAA,cAUW,8BAAA,GACX,EAAA,GAAK,OAAA,EAAS,iBAAiB,EAAE,QAAA"}
@@ -0,0 +1,9 @@
1
+ import { FileContent } from "./EditorStateManager.js";
2
+
3
+ //#region src/core/focusedContentBus.d.ts
4
+ declare const getGlobalFocusedContent: () => FileContent | null | undefined;
5
+ declare const setGlobalFocusedContent: (content: FileContent | null, sourceId?: string) => void;
6
+ declare const subscribeToGlobalFocusedContent: (cb: (content: FileContent | null, sourceId?: string) => void) => (() => void);
7
+ //#endregion
8
+ export { getGlobalFocusedContent, setGlobalFocusedContent, subscribeToGlobalFocusedContent };
9
+ //# sourceMappingURL=focusedContentBus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focusedContentBus.d.ts","names":[],"sources":["../../../src/core/focusedContentBus.ts"],"mappings":";;;cAoBa,uBAAA,QAA8B,WAAW;AAAA,cAOzC,uBAAA,GACX,OAAA,EAAS,WAAW,SACpB,QAAA;AAAA,cAYW,+BAAA,GACX,EAAA,GAAK,OAAA,EAAS,WAAW,SAAS,QAAA"}
@@ -3,6 +3,8 @@ import { CrossFrameStateManager, CrossFrameStateOptions } from "./CrossFrameStat
3
3
  import { DictionaryContent, EditorStateManager, EditorStateManagerConfig, FileContent } from "./EditorStateManager.js";
4
4
  import { IframeClickInterceptor } from "./IframeClickInterceptor.js";
5
5
  import { UrlStateManager } from "./UrlStateManager.js";
6
+ import { getGlobalEditedContent, setGlobalEditedContent, subscribeToGlobalEditedContent } from "./editedContentBus.js";
7
+ import { getGlobalFocusedContent, setGlobalFocusedContent, subscribeToGlobalFocusedContent } from "./focusedContentBus.js";
6
8
  import { getGlobalEditorManager, onGlobalEditorManagerChange, setGlobalEditorManager } from "./globalManager.js";
7
9
  import { buildClientMessengerConfig, initEditorClient, stopEditorClient } from "./initEditorClient.js";
8
- export { CrossFrameMessenger, CrossFrameStateManager, CrossFrameStateOptions, DictionaryContent, EditorStateManager, EditorStateManagerConfig, FileContent, IframeClickInterceptor, MessagePayload, MessengerConfig, UrlStateManager, buildClientMessengerConfig, getGlobalEditorManager, initEditorClient, onGlobalEditorManagerChange, setGlobalEditorManager, stopEditorClient };
10
+ export { CrossFrameMessenger, CrossFrameStateManager, CrossFrameStateOptions, DictionaryContent, EditorStateManager, EditorStateManagerConfig, FileContent, IframeClickInterceptor, MessagePayload, MessengerConfig, UrlStateManager, buildClientMessengerConfig, getGlobalEditedContent, getGlobalEditorManager, getGlobalFocusedContent, initEditorClient, onGlobalEditorManagerChange, setGlobalEditedContent, setGlobalEditorManager, setGlobalFocusedContent, stopEditorClient, subscribeToGlobalEditedContent, subscribeToGlobalFocusedContent };
@@ -8,8 +8,10 @@ import { CrossFrameStateManager, CrossFrameStateOptions } from "./core/CrossFram
8
8
  import { DictionaryContent, EditorStateManager, EditorStateManagerConfig, FileContent } from "./core/EditorStateManager.js";
9
9
  import { IframeClickInterceptor } from "./core/IframeClickInterceptor.js";
10
10
  import { UrlStateManager } from "./core/UrlStateManager.js";
11
+ import { getGlobalEditedContent, setGlobalEditedContent, subscribeToGlobalEditedContent } from "./core/editedContentBus.js";
12
+ import { getGlobalFocusedContent, setGlobalFocusedContent, subscribeToGlobalFocusedContent } from "./core/focusedContentBus.js";
11
13
  import { getGlobalEditorManager, onGlobalEditorManagerChange, setGlobalEditorManager } from "./core/globalManager.js";
12
14
  import { buildClientMessengerConfig, initEditorClient, stopEditorClient } from "./core/initEditorClient.js";
13
15
  import { mergeIframeClick } from "./mergeIframeClick.js";
14
16
  import { MessageKey } from "./messageKey.js";
15
- export { CrossFrameMessenger, CrossFrameStateManager, CrossFrameStateOptions, DictionaryContent, EditorStateManager, EditorStateManagerConfig, FileContent, IframeClickInterceptor, IntlayerContentSelectorElement, IntlayerContentSelectorWrapperElement, IntlayerEditedContentElement, IntlayerEditorElement, MessageKey, MessagePayload, MessengerConfig, UrlStateManager, buildClientMessengerConfig, compareUrls, defineIntlayerContentSelectorWrapper, defineIntlayerEditedContent, defineIntlayerEditorElement, defineIntlayerElements, getGlobalEditorManager, initEditorClient, mergeIframeClick, onGlobalEditorManagerChange, setGlobalEditorManager, stopEditorClient };
17
+ export { CrossFrameMessenger, CrossFrameStateManager, CrossFrameStateOptions, DictionaryContent, EditorStateManager, EditorStateManagerConfig, FileContent, IframeClickInterceptor, IntlayerContentSelectorElement, IntlayerContentSelectorWrapperElement, IntlayerEditedContentElement, IntlayerEditorElement, MessageKey, MessagePayload, MessengerConfig, UrlStateManager, buildClientMessengerConfig, compareUrls, defineIntlayerContentSelectorWrapper, defineIntlayerEditedContent, defineIntlayerEditorElement, defineIntlayerElements, getGlobalEditedContent, getGlobalEditorManager, getGlobalFocusedContent, initEditorClient, mergeIframeClick, onGlobalEditorManagerChange, setGlobalEditedContent, setGlobalEditorManager, setGlobalFocusedContent, stopEditorClient, subscribeToGlobalEditedContent, subscribeToGlobalFocusedContent };
@@ -20,7 +20,9 @@ declare enum MessageKey {
20
20
  INTLAYER_FOCUSED_CONTENT_CHANGED = "INTLAYER_FOCUSED_CONTENT_CHANGED",
21
21
  INTLAYER_EDITED_CONTENT_CHANGED = "INTLAYER_EDITED_CONTENT_CHANGED",
22
22
  /** Client → editor: Helper to sync iframe client reactivity on click client */
23
- INTLAYER_IFRAME_CLICKED = "INTLAYER_IFRAME_CLICKED"
23
+ INTLAYER_IFRAME_CLICKED = "INTLAYER_IFRAME_CLICKED",
24
+ /** Client → editor: list of dictionary keys currently rendered in the iframe */
25
+ INTLAYER_DISPLAYED_DICTIONARY_KEYS = "INTLAYER_DISPLAYED_DICTIONARY_KEYS"
24
26
  }
25
27
  //#endregion
26
28
  export { MessageKey };
@@ -1 +1 @@
1
- {"version":3,"file":"messageKey.d.ts","names":[],"sources":["../../src/messageKey.ts"],"mappings":";aAAY,UAAA;EAAA;EAEV,qBAAA;;EAEA,sBAAA;EAFA;EAIA,wBAAA;EAAA;EAEA,uBAAA;EAGA;EAAA,sBAAA;EACA,uBAAA;EACA,mBAAA;EAIA;EADA,oCAAA;EACA,qCAAA;EAKA;EAFA,gCAAA;EACA,gCAAA;EACA,+BAAA;;EAGA,uBAAA;AAAA"}
1
+ {"version":3,"file":"messageKey.d.ts","names":[],"sources":["../../src/messageKey.ts"],"mappings":";aAAY,UAAA;EAAA;EAEV,qBAAA;;EAEA,sBAAA;EAFA;EAIA,wBAAA;EAAA;EAEA,uBAAA;EAGA;EAAA,sBAAA;EACA,uBAAA;EACA,mBAAA;EAIA;EADA,oCAAA;EACA,qCAAA;EAKA;EAFA,gCAAA;EACA,gCAAA;EACA,+BAAA;EAMkC;EAHlC,uBAAA;;EAGA,kCAAA;AAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intlayer/editor",
3
- "version": "8.9.8",
3
+ "version": "8.10.0-canary.1",
4
4
  "private": false,
5
5
  "description": "Provides the utilities to interface the application with the Intlayer editor and manipulate dictionaries",
6
6
  "keywords": [
@@ -75,13 +75,13 @@
75
75
  "typecheck": "tsc --noEmit --project tsconfig.types.json"
76
76
  },
77
77
  "dependencies": {
78
- "@intlayer/config": "8.9.8",
79
- "@intlayer/core": "8.9.8",
80
- "@intlayer/types": "8.9.8",
81
- "@intlayer/unmerged-dictionaries-entry": "8.9.8"
78
+ "@intlayer/config": "8.10.0-canary.1",
79
+ "@intlayer/core": "8.10.0-canary.1",
80
+ "@intlayer/types": "8.10.0-canary.1",
81
+ "@intlayer/unmerged-dictionaries-entry": "8.10.0-canary.1"
82
82
  },
83
83
  "devDependencies": {
84
- "@types/node": "25.8.0",
84
+ "@types/node": "25.9.1",
85
85
  "@utils/ts-config": "1.0.4",
86
86
  "@utils/ts-config-types": "1.0.4",
87
87
  "@utils/tsdown-config": "1.0.4",