@intlayer/editor 8.4.4 → 8.4.6

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 (124) hide show
  1. package/dist/cjs/_virtual/_@oxc-project_runtime@0.115.0/helpers/decorate.cjs +11 -0
  2. package/dist/cjs/_virtual/_rolldown/runtime.cjs +29 -0
  3. package/dist/cjs/compareUrls.cjs +39 -1
  4. package/dist/cjs/compareUrls.cjs.map +1 -1
  5. package/dist/cjs/components/ContentSelector.cjs +168 -1
  6. package/dist/cjs/components/ContentSelector.cjs.map +1 -0
  7. package/dist/cjs/components/ContentSelectorWrapper.cjs +193 -4
  8. package/dist/cjs/components/ContentSelectorWrapper.cjs.map +1 -1
  9. package/dist/cjs/components/EditedContent.cjs +128 -4
  10. package/dist/cjs/components/EditedContent.cjs.map +1 -1
  11. package/dist/cjs/components/IntlayerEditor.cjs +87 -1
  12. package/dist/cjs/components/IntlayerEditor.cjs.map +1 -0
  13. package/dist/cjs/components/index.cjs +14 -1
  14. package/dist/cjs/core/CrossFrameMessenger.cjs +77 -1
  15. package/dist/cjs/core/CrossFrameMessenger.cjs.map +1 -1
  16. package/dist/cjs/core/CrossFrameStateManager.cjs +71 -1
  17. package/dist/cjs/core/CrossFrameStateManager.cjs.map +1 -1
  18. package/dist/cjs/core/EditorStateManager.cjs +258 -1
  19. package/dist/cjs/core/EditorStateManager.cjs.map +1 -1
  20. package/dist/cjs/core/IframeClickInterceptor.cjs +45 -1
  21. package/dist/cjs/core/IframeClickInterceptor.cjs.map +1 -1
  22. package/dist/cjs/core/UrlStateManager.cjs +59 -1
  23. package/dist/cjs/core/UrlStateManager.cjs.map +1 -1
  24. package/dist/cjs/core/globalManager.cjs +82 -1
  25. package/dist/cjs/core/globalManager.cjs.map +1 -1
  26. package/dist/cjs/core/index.cjs +20 -1
  27. package/dist/cjs/core/initEditorClient.cjs +58 -1
  28. package/dist/cjs/core/initEditorClient.cjs.map +1 -0
  29. package/dist/cjs/index.cjs +38 -1
  30. package/dist/cjs/isEnabled.cjs +10 -1
  31. package/dist/cjs/isEnabled.cjs.map +1 -1
  32. package/dist/cjs/mergeIframeClick.cjs +22 -1
  33. package/dist/cjs/mergeIframeClick.cjs.map +1 -1
  34. package/dist/cjs/messageKey.cjs +30 -1
  35. package/dist/cjs/messageKey.cjs.map +1 -1
  36. package/dist/esm/_virtual/_@oxc-project_runtime@0.115.0/helpers/decorate.mjs +10 -0
  37. package/dist/esm/compareUrls.mjs +37 -1
  38. package/dist/esm/compareUrls.mjs.map +1 -1
  39. package/dist/esm/components/ContentSelector.mjs +165 -1
  40. package/dist/esm/components/ContentSelector.mjs.map +1 -0
  41. package/dist/esm/components/ContentSelectorWrapper.mjs +189 -4
  42. package/dist/esm/components/ContentSelectorWrapper.mjs.map +1 -1
  43. package/dist/esm/components/EditedContent.mjs +125 -4
  44. package/dist/esm/components/EditedContent.mjs.map +1 -1
  45. package/dist/esm/components/IntlayerEditor.mjs +84 -1
  46. package/dist/esm/components/IntlayerEditor.mjs.map +1 -0
  47. package/dist/esm/components/index.mjs +6 -1
  48. package/dist/esm/core/CrossFrameMessenger.mjs +76 -1
  49. package/dist/esm/core/CrossFrameMessenger.mjs.map +1 -1
  50. package/dist/esm/core/CrossFrameStateManager.mjs +69 -1
  51. package/dist/esm/core/CrossFrameStateManager.mjs.map +1 -1
  52. package/dist/esm/core/EditorStateManager.mjs +255 -1
  53. package/dist/esm/core/EditorStateManager.mjs.map +1 -1
  54. package/dist/esm/core/IframeClickInterceptor.mjs +44 -1
  55. package/dist/esm/core/IframeClickInterceptor.mjs.map +1 -1
  56. package/dist/esm/core/UrlStateManager.mjs +58 -1
  57. package/dist/esm/core/UrlStateManager.mjs.map +1 -1
  58. package/dist/esm/core/globalManager.mjs +78 -1
  59. package/dist/esm/core/globalManager.mjs.map +1 -1
  60. package/dist/esm/core/index.mjs +9 -1
  61. package/dist/esm/core/initEditorClient.mjs +53 -1
  62. package/dist/esm/core/initEditorClient.mjs.map +1 -0
  63. package/dist/esm/index.mjs +16 -1
  64. package/dist/esm/isEnabled.mjs +7 -1
  65. package/dist/esm/isEnabled.mjs.map +1 -1
  66. package/dist/esm/mergeIframeClick.mjs +20 -1
  67. package/dist/esm/mergeIframeClick.mjs.map +1 -1
  68. package/dist/esm/messageKey.mjs +28 -1
  69. package/dist/esm/messageKey.mjs.map +1 -1
  70. package/dist/types/components/ContentSelector.d.ts +49 -2
  71. package/dist/types/components/ContentSelector.d.ts.map +1 -0
  72. package/dist/types/components/ContentSelectorWrapper.d.ts +49 -2
  73. package/dist/types/components/ContentSelectorWrapper.d.ts.map +1 -0
  74. package/dist/types/components/EditedContent.d.ts +39 -2
  75. package/dist/types/components/EditedContent.d.ts.map +1 -0
  76. package/dist/types/components/IntlayerEditor.d.ts +40 -2
  77. package/dist/types/components/IntlayerEditor.d.ts.map +1 -0
  78. package/dist/types/components/index.d.ts +4 -4
  79. package/dist/types/core/CrossFrameMessenger.d.ts +51 -2
  80. package/dist/types/core/CrossFrameMessenger.d.ts.map +1 -0
  81. package/dist/types/core/CrossFrameStateManager.d.ts +44 -2
  82. package/dist/types/core/CrossFrameStateManager.d.ts.map +1 -0
  83. package/dist/types/core/EditorStateManager.d.ts +73 -2
  84. package/dist/types/core/EditorStateManager.d.ts.map +1 -0
  85. package/dist/types/core/IframeClickInterceptor.d.ts +26 -2
  86. package/dist/types/core/IframeClickInterceptor.d.ts.map +1 -0
  87. package/dist/types/core/UrlStateManager.d.ts +21 -2
  88. package/dist/types/core/UrlStateManager.d.ts.map +1 -0
  89. package/dist/types/core/globalManager.d.ts +39 -2
  90. package/dist/types/core/globalManager.d.ts.map +1 -0
  91. package/dist/types/core/index.d.ts +7 -7
  92. package/dist/types/core/initEditorClient.d.ts +20 -2
  93. package/dist/types/core/initEditorClient.d.ts.map +1 -0
  94. package/dist/types/index.d.ts +11 -11
  95. package/package.json +5 -5
  96. package/dist/cjs/chunk-Bmb41Sf3.cjs +0 -1
  97. package/dist/cjs/components-DWu35JEb.cjs +0 -41
  98. package/dist/cjs/components-DWu35JEb.cjs.map +0 -1
  99. package/dist/cjs/decorate-Bg73f0d3.cjs +0 -1
  100. package/dist/esm/components-RtOXxg9h.mjs +0 -41
  101. package/dist/esm/components-RtOXxg9h.mjs.map +0 -1
  102. package/dist/esm/decorate-BWURH4oJ.mjs +0 -1
  103. package/dist/types/ContentSelector-sIfZu4Dd.d.ts +0 -49
  104. package/dist/types/ContentSelector-sIfZu4Dd.d.ts.map +0 -1
  105. package/dist/types/ContentSelectorWrapper-CID6anMf.d.ts +0 -49
  106. package/dist/types/ContentSelectorWrapper-CID6anMf.d.ts.map +0 -1
  107. package/dist/types/CrossFrameMessenger-CPt3Bu8S.d.ts +0 -51
  108. package/dist/types/CrossFrameMessenger-CPt3Bu8S.d.ts.map +0 -1
  109. package/dist/types/CrossFrameStateManager-CW1DPY_Z.d.ts +0 -44
  110. package/dist/types/CrossFrameStateManager-CW1DPY_Z.d.ts.map +0 -1
  111. package/dist/types/EditedContent-2kq4wk4R.d.ts +0 -39
  112. package/dist/types/EditedContent-2kq4wk4R.d.ts.map +0 -1
  113. package/dist/types/EditorStateManager-Y9j0SYCd.d.ts +0 -73
  114. package/dist/types/EditorStateManager-Y9j0SYCd.d.ts.map +0 -1
  115. package/dist/types/IframeClickInterceptor-Cm89LRcI.d.ts +0 -26
  116. package/dist/types/IframeClickInterceptor-Cm89LRcI.d.ts.map +0 -1
  117. package/dist/types/IntlayerEditor-ePRSIuBI.d.ts +0 -40
  118. package/dist/types/IntlayerEditor-ePRSIuBI.d.ts.map +0 -1
  119. package/dist/types/UrlStateManager-CIOVEeTq.d.ts +0 -21
  120. package/dist/types/UrlStateManager-CIOVEeTq.d.ts.map +0 -1
  121. package/dist/types/globalManager-BD6UaK_1.d.ts +0 -39
  122. package/dist/types/globalManager-BD6UaK_1.d.ts.map +0 -1
  123. package/dist/types/initEditorClient-ovRVUf_n.d.ts +0 -20
  124. package/dist/types/initEditorClient-ovRVUf_n.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CrossFrameMessenger.mjs","names":[],"sources":["../../../src/core/CrossFrameMessenger.ts"],"sourcesContent":["import { compareUrls } from '../compareUrls';\n\nconst randomUUID = (): string => Math.random().toString(36).slice(2);\n\nexport type MessagePayload = {\n type: string;\n data?: unknown;\n senderId?: string;\n /** Unique ID per send() call — used to deduplicate when the same payload arrives via multiple target origins */\n messageId?: string;\n};\n\nexport type MessengerConfig = {\n /**\n * Origins allowed to send messages to this instance.\n * Use '*' to allow all origins (not recommended for production).\n */\n allowedOrigins: string[];\n /**\n * Function used to send messages to other frames.\n * Injected so the messenger is agnostic to the frame topology.\n */\n postMessageFn: (payload: MessagePayload, origin: string) => void;\n};\n\ntype MessageHandler<T = unknown> = (data: T, senderId?: string) => void;\n\n/**\n * CrossFrameMessenger manages all cross-frame postMessage communication.\n * It owns a single window message listener and routes incoming messages to\n * type-specific subscribers.\n *\n * Replaces CommunicatorContext + useCrossFrameMessageListener across all frameworks.\n */\nexport class CrossFrameMessenger {\n readonly senderId: string;\n private readonly _config: MessengerConfig;\n private readonly _subscribers = new Map<string, Set<MessageHandler>>();\n\n private _windowHandler: ((event: MessageEvent) => void) | null = null;\n /** Tracks recently processed messageIds to discard duplicates (same payload sent to multiple origins) */\n private readonly _seenMessageIds = new Set<string>();\n\n constructor(config: MessengerConfig) {\n this._config = config;\n this.senderId = randomUUID();\n }\n\n /** Start listening for incoming messages on window. */\n start(): void {\n if (typeof window === 'undefined') return;\n if (this._windowHandler) return;\n this._windowHandler = (event: MessageEvent<MessagePayload>) => {\n this._handleMessage(event);\n };\n window.addEventListener('message', this._windowHandler);\n }\n\n /** Stop listening and clean up. */\n stop(): void {\n if (this._windowHandler) {\n window.removeEventListener('message', this._windowHandler);\n this._windowHandler = null;\n }\n }\n\n /** Send a message payload to all configured target origins. */\n send(type: string, data?: unknown): void {\n const payload: MessagePayload = {\n type,\n data,\n senderId: this.senderId,\n messageId: randomUUID(),\n };\n\n for (const origin of this._config.allowedOrigins) {\n if (origin) {\n this._config.postMessageFn(payload, origin);\n }\n }\n }\n\n /**\n * Subscribe to messages of a given type.\n * Returns an unsubscribe function.\n */\n subscribe<T = unknown>(type: string, handler: MessageHandler<T>): () => void {\n if (!this._subscribers.has(type)) {\n this._subscribers.set(type, new Set());\n }\n this._subscribers.get(type)!.add(handler as MessageHandler);\n return () => {\n this._subscribers.get(type)?.delete(handler as MessageHandler);\n };\n }\n\n private _handleMessage(event: MessageEvent<MessagePayload>): void {\n const payload = event.data;\n if (!payload || typeof payload !== 'object') return;\n\n const { type, data, senderId: msgSenderId, messageId } = payload;\n if (!type || typeof type !== 'string') return;\n\n // Ignore messages originating from this instance\n if (msgSenderId === this.senderId) return;\n\n // Deduplicate: same messageId may arrive multiple times when the sender\n // posts to multiple target origins (one per allowedOrigin)\n if (messageId) {\n if (this._seenMessageIds.has(messageId)) return;\n this._seenMessageIds.add(messageId);\n // Keep the set bounded — clear when it exceeds 200 entries\n if (this._seenMessageIds.size > 200) this._seenMessageIds.clear();\n }\n\n // Validate message origin\n const { allowedOrigins } = this._config;\n const isAllowed =\n !allowedOrigins ||\n allowedOrigins.length === 0 ||\n allowedOrigins.includes('*') ||\n allowedOrigins\n .filter((url) => Boolean(url) && url !== '')\n .some((url) => compareUrls(url, event.origin));\n\n if (!isAllowed) return;\n\n const handlers = this._subscribers.get(type);\n\n if (handlers) {\n for (const handler of handlers) {\n handler(data, msgSenderId);\n }\n }\n }\n}\n"],"mappings":"iDAEA,MAAM,MAA2B,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE,CAgCpE,IAAa,EAAb,KAAiC,CAS/B,YAAY,EAAyB,mBANL,IAAI,wBAE6B,0BAE9B,IAAI,IAGrC,KAAK,QAAU,EACf,KAAK,SAAW,GAAY,CAI9B,OAAc,CACR,OAAO,OAAW,KAClB,KAAK,iBACT,KAAK,eAAkB,GAAwC,CAC7D,KAAK,eAAe,EAAM,EAE5B,OAAO,iBAAiB,UAAW,KAAK,eAAe,EAIzD,MAAa,CACX,AAEE,KAAK,kBADL,OAAO,oBAAoB,UAAW,KAAK,eAAe,CACpC,MAK1B,KAAK,EAAc,EAAsB,CACvC,IAAM,EAA0B,CAC9B,OACA,OACA,SAAU,KAAK,SACf,UAAW,GAAY,CACxB,CAED,IAAK,IAAM,KAAU,KAAK,QAAQ,eAC5B,GACF,KAAK,QAAQ,cAAc,EAAS,EAAO,CASjD,UAAuB,EAAc,EAAwC,CAK3E,OAJK,KAAK,aAAa,IAAI,EAAK,EAC9B,KAAK,aAAa,IAAI,EAAM,IAAI,IAAM,CAExC,KAAK,aAAa,IAAI,EAAK,CAAE,IAAI,EAA0B,KAC9C,CACX,KAAK,aAAa,IAAI,EAAK,EAAE,OAAO,EAA0B,EAIlE,eAAuB,EAA2C,CAChE,IAAM,EAAU,EAAM,KACtB,GAAI,CAAC,GAAW,OAAO,GAAY,SAAU,OAE7C,GAAM,CAAE,OAAM,OAAM,SAAU,EAAa,aAAc,EAIzD,GAHI,CAAC,GAAQ,OAAO,GAAS,UAGzB,IAAgB,KAAK,SAAU,OAInC,GAAI,EAAW,CACb,GAAI,KAAK,gBAAgB,IAAI,EAAU,CAAE,OACzC,KAAK,gBAAgB,IAAI,EAAU,CAE/B,KAAK,gBAAgB,KAAO,KAAK,KAAK,gBAAgB,OAAO,CAInE,GAAM,CAAE,kBAAmB,KAAK,QAShC,GAAI,EAPF,CAAC,GACD,EAAe,SAAW,GAC1B,EAAe,SAAS,IAAI,EAC5B,EACG,OAAQ,GAAQ,EAAQ,GAAQ,IAAQ,GAAG,CAC3C,KAAM,GAAQ,EAAY,EAAK,EAAM,OAAO,CAAC,EAElC,OAEhB,IAAM,EAAW,KAAK,aAAa,IAAI,EAAK,CAE5C,GAAI,EACF,IAAK,IAAM,KAAW,EACpB,EAAQ,EAAM,EAAY"}
1
+ {"version":3,"file":"CrossFrameMessenger.mjs","names":[],"sources":["../../../src/core/CrossFrameMessenger.ts"],"sourcesContent":["import { compareUrls } from '../compareUrls';\n\nconst randomUUID = (): string => Math.random().toString(36).slice(2);\n\nexport type MessagePayload = {\n type: string;\n data?: unknown;\n senderId?: string;\n /** Unique ID per send() call — used to deduplicate when the same payload arrives via multiple target origins */\n messageId?: string;\n};\n\nexport type MessengerConfig = {\n /**\n * Origins allowed to send messages to this instance.\n * Use '*' to allow all origins (not recommended for production).\n */\n allowedOrigins: string[];\n /**\n * Function used to send messages to other frames.\n * Injected so the messenger is agnostic to the frame topology.\n */\n postMessageFn: (payload: MessagePayload, origin: string) => void;\n};\n\ntype MessageHandler<T = unknown> = (data: T, senderId?: string) => void;\n\n/**\n * CrossFrameMessenger manages all cross-frame postMessage communication.\n * It owns a single window message listener and routes incoming messages to\n * type-specific subscribers.\n *\n * Replaces CommunicatorContext + useCrossFrameMessageListener across all frameworks.\n */\nexport class CrossFrameMessenger {\n readonly senderId: string;\n private readonly _config: MessengerConfig;\n private readonly _subscribers = new Map<string, Set<MessageHandler>>();\n\n private _windowHandler: ((event: MessageEvent) => void) | null = null;\n /** Tracks recently processed messageIds to discard duplicates (same payload sent to multiple origins) */\n private readonly _seenMessageIds = new Set<string>();\n\n constructor(config: MessengerConfig) {\n this._config = config;\n this.senderId = randomUUID();\n }\n\n /** Start listening for incoming messages on window. */\n start(): void {\n if (typeof window === 'undefined') return;\n if (this._windowHandler) return;\n this._windowHandler = (event: MessageEvent<MessagePayload>) => {\n this._handleMessage(event);\n };\n window.addEventListener('message', this._windowHandler);\n }\n\n /** Stop listening and clean up. */\n stop(): void {\n if (this._windowHandler) {\n window.removeEventListener('message', this._windowHandler);\n this._windowHandler = null;\n }\n }\n\n /** Send a message payload to all configured target origins. */\n send(type: string, data?: unknown): void {\n const payload: MessagePayload = {\n type,\n data,\n senderId: this.senderId,\n messageId: randomUUID(),\n };\n\n for (const origin of this._config.allowedOrigins) {\n if (origin) {\n this._config.postMessageFn(payload, origin);\n }\n }\n }\n\n /**\n * Subscribe to messages of a given type.\n * Returns an unsubscribe function.\n */\n subscribe<T = unknown>(type: string, handler: MessageHandler<T>): () => void {\n if (!this._subscribers.has(type)) {\n this._subscribers.set(type, new Set());\n }\n this._subscribers.get(type)!.add(handler as MessageHandler);\n return () => {\n this._subscribers.get(type)?.delete(handler as MessageHandler);\n };\n }\n\n private _handleMessage(event: MessageEvent<MessagePayload>): void {\n const payload = event.data;\n if (!payload || typeof payload !== 'object') return;\n\n const { type, data, senderId: msgSenderId, messageId } = payload;\n if (!type || typeof type !== 'string') return;\n\n // Ignore messages originating from this instance\n if (msgSenderId === this.senderId) return;\n\n // Deduplicate: same messageId may arrive multiple times when the sender\n // posts to multiple target origins (one per allowedOrigin)\n if (messageId) {\n if (this._seenMessageIds.has(messageId)) return;\n this._seenMessageIds.add(messageId);\n // Keep the set bounded — clear when it exceeds 200 entries\n if (this._seenMessageIds.size > 200) this._seenMessageIds.clear();\n }\n\n // Validate message origin\n const { allowedOrigins } = this._config;\n const isAllowed =\n !allowedOrigins ||\n allowedOrigins.length === 0 ||\n allowedOrigins.includes('*') ||\n allowedOrigins\n .filter((url) => Boolean(url) && url !== '')\n .some((url) => compareUrls(url, event.origin));\n\n if (!isAllowed) return;\n\n const handlers = this._subscribers.get(type);\n\n if (handlers) {\n for (const handler of handlers) {\n handler(data, msgSenderId);\n }\n }\n }\n}\n"],"mappings":";;;AAEA,MAAM,mBAA2B,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE;;;;;;;;AAgCpE,IAAa,sBAAb,MAAiC;CAS/B,YAAY,QAAyB;sCANL,IAAI,KAAkC;wBAEL;yCAE9B,IAAI,KAAa;AAGlD,OAAK,UAAU;AACf,OAAK,WAAW,YAAY;;;CAI9B,QAAc;AACZ,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI,KAAK,eAAgB;AACzB,OAAK,kBAAkB,UAAwC;AAC7D,QAAK,eAAe,MAAM;;AAE5B,SAAO,iBAAiB,WAAW,KAAK,eAAe;;;CAIzD,OAAa;AACX,MAAI,KAAK,gBAAgB;AACvB,UAAO,oBAAoB,WAAW,KAAK,eAAe;AAC1D,QAAK,iBAAiB;;;;CAK1B,KAAK,MAAc,MAAsB;EACvC,MAAM,UAA0B;GAC9B;GACA;GACA,UAAU,KAAK;GACf,WAAW,YAAY;GACxB;AAED,OAAK,MAAM,UAAU,KAAK,QAAQ,eAChC,KAAI,OACF,MAAK,QAAQ,cAAc,SAAS,OAAO;;;;;;CASjD,UAAuB,MAAc,SAAwC;AAC3E,MAAI,CAAC,KAAK,aAAa,IAAI,KAAK,CAC9B,MAAK,aAAa,IAAI,sBAAM,IAAI,KAAK,CAAC;AAExC,OAAK,aAAa,IAAI,KAAK,CAAE,IAAI,QAA0B;AAC3D,eAAa;AACX,QAAK,aAAa,IAAI,KAAK,EAAE,OAAO,QAA0B;;;CAIlE,AAAQ,eAAe,OAA2C;EAChE,MAAM,UAAU,MAAM;AACtB,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU;EAE7C,MAAM,EAAE,MAAM,MAAM,UAAU,aAAa,cAAc;AACzD,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AAGvC,MAAI,gBAAgB,KAAK,SAAU;AAInC,MAAI,WAAW;AACb,OAAI,KAAK,gBAAgB,IAAI,UAAU,CAAE;AACzC,QAAK,gBAAgB,IAAI,UAAU;AAEnC,OAAI,KAAK,gBAAgB,OAAO,IAAK,MAAK,gBAAgB,OAAO;;EAInE,MAAM,EAAE,mBAAmB,KAAK;AAShC,MAAI,EAPF,CAAC,kBACD,eAAe,WAAW,KAC1B,eAAe,SAAS,IAAI,IAC5B,eACG,QAAQ,QAAQ,QAAQ,IAAI,IAAI,QAAQ,GAAG,CAC3C,MAAM,QAAQ,YAAY,KAAK,MAAM,OAAO,CAAC,EAElC;EAEhB,MAAM,WAAW,KAAK,aAAa,IAAI,KAAK;AAE5C,MAAI,SACF,MAAK,MAAM,WAAW,SACpB,SAAQ,MAAM,YAAY"}
@@ -1,2 +1,70 @@
1
- var e=class extends EventTarget{constructor(e,t,n={}){super(),this._unsubscribers=[],this._key=e,this._messenger=t,this._options={emit:n.emit??!0,receive:n.receive??!0},n.initialValue!==void 0&&(this._value=n.initialValue)}get value(){return this._value}set(e){this._value=e,this.dispatchEvent(new CustomEvent(`change`,{detail:e})),this._options.emit&&this._messenger.send(`${this._key}/post`,e)}start(){if(this._options.receive){let e=this._messenger.subscribe(`${this._key}/post`,e=>{this._value=e,this.dispatchEvent(new CustomEvent(`change`,{detail:e}))});this._unsubscribers.push(e)}if(this._options.emit){let e=this._messenger.subscribe(`${this._key}/get`,(e,t)=>{t!==this._messenger.senderId&&this._value!==void 0&&this._messenger.send(`${this._key}/post`,this._value)});this._unsubscribers.push(e)}this._options.receive&&this._value===void 0&&this._messenger.send(`${this._key}/get`)}stop(){for(let e of this._unsubscribers)e();this._unsubscribers.length=0}postCurrentValue(){this._value!==void 0&&this._messenger.send(`${this._key}/post`,this._value)}};export{e as CrossFrameStateManager};
1
+ //#region src/core/CrossFrameStateManager.ts
2
+ /**
3
+ * CrossFrameStateManager synchronizes a single named value across frames using
4
+ * the postMessage API via CrossFrameMessenger.
5
+ *
6
+ * Protocol:
7
+ * `{key}/post` — broadcast a new value
8
+ * `{key}/get` — request the current value from other frames
9
+ *
10
+ * Replaces useCrossFrameState across all frameworks.
11
+ *
12
+ * @fires change — CustomEvent<T> dispatched whenever the value changes (local set or received)
13
+ */
14
+ var CrossFrameStateManager = class extends EventTarget {
15
+ constructor(key, messenger, options = {}) {
16
+ super();
17
+ this._unsubscribers = [];
18
+ this._key = key;
19
+ this._messenger = messenger;
20
+ this._options = {
21
+ emit: options.emit ?? true,
22
+ receive: options.receive ?? true
23
+ };
24
+ if (options.initialValue !== void 0) this._value = options.initialValue;
25
+ }
26
+ get value() {
27
+ return this._value;
28
+ }
29
+ /** Update the value locally and broadcast it to other frames if emit is enabled. */
30
+ set(newValue) {
31
+ this._value = newValue;
32
+ this.dispatchEvent(new CustomEvent("change", { detail: newValue }));
33
+ if (this._options.emit) this._messenger.send(`${this._key}/post`, newValue);
34
+ }
35
+ /**
36
+ * Start listening for incoming state updates and responding to /get requests.
37
+ * If receive=true and no initial value is set, sends a /get to request the current value.
38
+ */
39
+ start() {
40
+ if (this._options.receive) {
41
+ const unsub = this._messenger.subscribe(`${this._key}/post`, (data) => {
42
+ this._value = data;
43
+ this.dispatchEvent(new CustomEvent("change", { detail: data }));
44
+ });
45
+ this._unsubscribers.push(unsub);
46
+ }
47
+ if (this._options.emit) {
48
+ const unsub = this._messenger.subscribe(`${this._key}/get`, (_, originSenderId) => {
49
+ if (originSenderId === this._messenger.senderId) return;
50
+ if (this._value === void 0) return;
51
+ this._messenger.send(`${this._key}/post`, this._value);
52
+ });
53
+ this._unsubscribers.push(unsub);
54
+ }
55
+ if (this._options.receive && this._value === void 0) this._messenger.send(`${this._key}/get`);
56
+ }
57
+ /** Stop all listeners. */
58
+ stop() {
59
+ for (const unsub of this._unsubscribers) unsub();
60
+ this._unsubscribers.length = 0;
61
+ }
62
+ /** Broadcast the current value to all frames (useful after reconnect). */
63
+ postCurrentValue() {
64
+ if (this._value !== void 0) this._messenger.send(`${this._key}/post`, this._value);
65
+ }
66
+ };
67
+
68
+ //#endregion
69
+ export { CrossFrameStateManager };
2
70
  //# sourceMappingURL=CrossFrameStateManager.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"CrossFrameStateManager.mjs","names":[],"sources":["../../../src/core/CrossFrameStateManager.ts"],"sourcesContent":["import type { CrossFrameMessenger } from './CrossFrameMessenger';\n\nexport type CrossFrameStateOptions = {\n /** Whether to broadcast state changes to other frames. Default: true */\n emit?: boolean;\n /** Whether to listen for state updates from other frames. Default: true */\n receive?: boolean;\n};\n\n/**\n * CrossFrameStateManager synchronizes a single named value across frames using\n * the postMessage API via CrossFrameMessenger.\n *\n * Protocol:\n * `{key}/post` — broadcast a new value\n * `{key}/get` — request the current value from other frames\n *\n * Replaces useCrossFrameState across all frameworks.\n *\n * @fires change — CustomEvent<T> dispatched whenever the value changes (local set or received)\n */\nexport class CrossFrameStateManager<T> extends EventTarget {\n private _value: T | undefined;\n private readonly _key: string;\n private readonly _messenger: CrossFrameMessenger;\n private readonly _options: Required<CrossFrameStateOptions>;\n private readonly _unsubscribers: Array<() => void> = [];\n\n constructor(\n key: string,\n messenger: CrossFrameMessenger,\n options: CrossFrameStateOptions & { initialValue?: T } = {}\n ) {\n super();\n this._key = key;\n this._messenger = messenger;\n this._options = {\n emit: options.emit ?? true,\n receive: options.receive ?? true,\n };\n if (options.initialValue !== undefined) {\n this._value = options.initialValue;\n }\n }\n\n get value(): T | undefined {\n return this._value;\n }\n\n /** Update the value locally and broadcast it to other frames if emit is enabled. */\n set(newValue: T): void {\n this._value = newValue;\n this.dispatchEvent(new CustomEvent<T>('change', { detail: newValue }));\n if (this._options.emit) {\n this._messenger.send(`${this._key}/post`, newValue);\n }\n }\n\n /**\n * Start listening for incoming state updates and responding to /get requests.\n * If receive=true and no initial value is set, sends a /get to request the current value.\n */\n start(): void {\n if (this._options.receive) {\n const unsub = this._messenger.subscribe<T>(\n `${this._key}/post`,\n (data) => {\n this._value = data;\n this.dispatchEvent(new CustomEvent<T>('change', { detail: data }));\n }\n );\n this._unsubscribers.push(unsub);\n }\n\n if (this._options.emit) {\n // Respond to /get requests by broadcasting current value\n const unsub = this._messenger.subscribe(\n `${this._key}/get`,\n (_, originSenderId) => {\n if (originSenderId === this._messenger.senderId) return;\n if (this._value === undefined) return;\n this._messenger.send(`${this._key}/post`, this._value);\n }\n );\n this._unsubscribers.push(unsub);\n }\n\n // If receiving and no initial value, request it from other frames\n if (this._options.receive && this._value === undefined) {\n this._messenger.send(`${this._key}/get`);\n }\n }\n\n /** Stop all listeners. */\n stop(): void {\n for (const unsub of this._unsubscribers) unsub();\n this._unsubscribers.length = 0;\n }\n\n /** Broadcast the current value to all frames (useful after reconnect). */\n postCurrentValue(): void {\n if (this._value !== undefined) {\n this._messenger.send(`${this._key}/post`, this._value);\n }\n }\n}\n"],"mappings":"AAqBA,IAAa,EAAb,cAA+C,WAAY,CAOzD,YACE,EACA,EACA,EAAyD,EAAE,CAC3D,CACA,OAAO,qBAP4C,EAAE,CAQrD,KAAK,KAAO,EACZ,KAAK,WAAa,EAClB,KAAK,SAAW,CACd,KAAM,EAAQ,MAAQ,GACtB,QAAS,EAAQ,SAAW,GAC7B,CACG,EAAQ,eAAiB,IAAA,KAC3B,KAAK,OAAS,EAAQ,cAI1B,IAAI,OAAuB,CACzB,OAAO,KAAK,OAId,IAAI,EAAmB,CACrB,KAAK,OAAS,EACd,KAAK,cAAc,IAAI,YAAe,SAAU,CAAE,OAAQ,EAAU,CAAC,CAAC,CAClE,KAAK,SAAS,MAChB,KAAK,WAAW,KAAK,GAAG,KAAK,KAAK,OAAQ,EAAS,CAQvD,OAAc,CACZ,GAAI,KAAK,SAAS,QAAS,CACzB,IAAM,EAAQ,KAAK,WAAW,UAC5B,GAAG,KAAK,KAAK,OACZ,GAAS,CACR,KAAK,OAAS,EACd,KAAK,cAAc,IAAI,YAAe,SAAU,CAAE,OAAQ,EAAM,CAAC,CAAC,EAErE,CACD,KAAK,eAAe,KAAK,EAAM,CAGjC,GAAI,KAAK,SAAS,KAAM,CAEtB,IAAM,EAAQ,KAAK,WAAW,UAC5B,GAAG,KAAK,KAAK,OACZ,EAAG,IAAmB,CACjB,IAAmB,KAAK,WAAW,UACnC,KAAK,SAAW,IAAA,IACpB,KAAK,WAAW,KAAK,GAAG,KAAK,KAAK,OAAQ,KAAK,OAAO,EAEzD,CACD,KAAK,eAAe,KAAK,EAAM,CAI7B,KAAK,SAAS,SAAW,KAAK,SAAW,IAAA,IAC3C,KAAK,WAAW,KAAK,GAAG,KAAK,KAAK,MAAM,CAK5C,MAAa,CACX,IAAK,IAAM,KAAS,KAAK,eAAgB,GAAO,CAChD,KAAK,eAAe,OAAS,EAI/B,kBAAyB,CACnB,KAAK,SAAW,IAAA,IAClB,KAAK,WAAW,KAAK,GAAG,KAAK,KAAK,OAAQ,KAAK,OAAO"}
1
+ {"version":3,"file":"CrossFrameStateManager.mjs","names":[],"sources":["../../../src/core/CrossFrameStateManager.ts"],"sourcesContent":["import type { CrossFrameMessenger } from './CrossFrameMessenger';\n\nexport type CrossFrameStateOptions = {\n /** Whether to broadcast state changes to other frames. Default: true */\n emit?: boolean;\n /** Whether to listen for state updates from other frames. Default: true */\n receive?: boolean;\n};\n\n/**\n * CrossFrameStateManager synchronizes a single named value across frames using\n * the postMessage API via CrossFrameMessenger.\n *\n * Protocol:\n * `{key}/post` — broadcast a new value\n * `{key}/get` — request the current value from other frames\n *\n * Replaces useCrossFrameState across all frameworks.\n *\n * @fires change — CustomEvent<T> dispatched whenever the value changes (local set or received)\n */\nexport class CrossFrameStateManager<T> extends EventTarget {\n private _value: T | undefined;\n private readonly _key: string;\n private readonly _messenger: CrossFrameMessenger;\n private readonly _options: Required<CrossFrameStateOptions>;\n private readonly _unsubscribers: Array<() => void> = [];\n\n constructor(\n key: string,\n messenger: CrossFrameMessenger,\n options: CrossFrameStateOptions & { initialValue?: T } = {}\n ) {\n super();\n this._key = key;\n this._messenger = messenger;\n this._options = {\n emit: options.emit ?? true,\n receive: options.receive ?? true,\n };\n if (options.initialValue !== undefined) {\n this._value = options.initialValue;\n }\n }\n\n get value(): T | undefined {\n return this._value;\n }\n\n /** Update the value locally and broadcast it to other frames if emit is enabled. */\n set(newValue: T): void {\n this._value = newValue;\n this.dispatchEvent(new CustomEvent<T>('change', { detail: newValue }));\n if (this._options.emit) {\n this._messenger.send(`${this._key}/post`, newValue);\n }\n }\n\n /**\n * Start listening for incoming state updates and responding to /get requests.\n * If receive=true and no initial value is set, sends a /get to request the current value.\n */\n start(): void {\n if (this._options.receive) {\n const unsub = this._messenger.subscribe<T>(\n `${this._key}/post`,\n (data) => {\n this._value = data;\n this.dispatchEvent(new CustomEvent<T>('change', { detail: data }));\n }\n );\n this._unsubscribers.push(unsub);\n }\n\n if (this._options.emit) {\n // Respond to /get requests by broadcasting current value\n const unsub = this._messenger.subscribe(\n `${this._key}/get`,\n (_, originSenderId) => {\n if (originSenderId === this._messenger.senderId) return;\n if (this._value === undefined) return;\n this._messenger.send(`${this._key}/post`, this._value);\n }\n );\n this._unsubscribers.push(unsub);\n }\n\n // If receiving and no initial value, request it from other frames\n if (this._options.receive && this._value === undefined) {\n this._messenger.send(`${this._key}/get`);\n }\n }\n\n /** Stop all listeners. */\n stop(): void {\n for (const unsub of this._unsubscribers) unsub();\n this._unsubscribers.length = 0;\n }\n\n /** Broadcast the current value to all frames (useful after reconnect). */\n postCurrentValue(): void {\n if (this._value !== undefined) {\n this._messenger.send(`${this._key}/post`, this._value);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAqBA,IAAa,yBAAb,cAA+C,YAAY;CAOzD,YACE,KACA,WACA,UAAyD,EAAE,EAC3D;AACA,SAAO;wBAP4C,EAAE;AAQrD,OAAK,OAAO;AACZ,OAAK,aAAa;AAClB,OAAK,WAAW;GACd,MAAM,QAAQ,QAAQ;GACtB,SAAS,QAAQ,WAAW;GAC7B;AACD,MAAI,QAAQ,iBAAiB,OAC3B,MAAK,SAAS,QAAQ;;CAI1B,IAAI,QAAuB;AACzB,SAAO,KAAK;;;CAId,IAAI,UAAmB;AACrB,OAAK,SAAS;AACd,OAAK,cAAc,IAAI,YAAe,UAAU,EAAE,QAAQ,UAAU,CAAC,CAAC;AACtE,MAAI,KAAK,SAAS,KAChB,MAAK,WAAW,KAAK,GAAG,KAAK,KAAK,QAAQ,SAAS;;;;;;CAQvD,QAAc;AACZ,MAAI,KAAK,SAAS,SAAS;GACzB,MAAM,QAAQ,KAAK,WAAW,UAC5B,GAAG,KAAK,KAAK,SACZ,SAAS;AACR,SAAK,SAAS;AACd,SAAK,cAAc,IAAI,YAAe,UAAU,EAAE,QAAQ,MAAM,CAAC,CAAC;KAErE;AACD,QAAK,eAAe,KAAK,MAAM;;AAGjC,MAAI,KAAK,SAAS,MAAM;GAEtB,MAAM,QAAQ,KAAK,WAAW,UAC5B,GAAG,KAAK,KAAK,QACZ,GAAG,mBAAmB;AACrB,QAAI,mBAAmB,KAAK,WAAW,SAAU;AACjD,QAAI,KAAK,WAAW,OAAW;AAC/B,SAAK,WAAW,KAAK,GAAG,KAAK,KAAK,QAAQ,KAAK,OAAO;KAEzD;AACD,QAAK,eAAe,KAAK,MAAM;;AAIjC,MAAI,KAAK,SAAS,WAAW,KAAK,WAAW,OAC3C,MAAK,WAAW,KAAK,GAAG,KAAK,KAAK,MAAM;;;CAK5C,OAAa;AACX,OAAK,MAAM,SAAS,KAAK,eAAgB,QAAO;AAChD,OAAK,eAAe,SAAS;;;CAI/B,mBAAyB;AACvB,MAAI,KAAK,WAAW,OAClB,MAAK,WAAW,KAAK,GAAG,KAAK,KAAK,QAAQ,KAAK,OAAO"}
@@ -1,2 +1,256 @@
1
- import{MessageKey as e}from"../messageKey.mjs";import{CrossFrameMessenger as t}from"./CrossFrameMessenger.mjs";import{CrossFrameStateManager as n}from"./CrossFrameStateManager.mjs";import{IframeClickInterceptor as r}from"./IframeClickInterceptor.mjs";import{UrlStateManager as i}from"./UrlStateManager.mjs";import{NodeType as a}from"@intlayer/types/nodeType";import{editDictionaryByKeyPath as o,getContentNodeByKeyPath as s,renameContentNodeByKeyPath as c}from"@intlayer/core/dictionaryManipulator";var l=class{constructor(a){this._unsubAreYouThere=null,this._unsubActivate=null,this._unsubClientReady=null,this._mode=a.mode,this._configuration=a.configuration,this.messenger=new t(a.messenger),this.editorEnabled=new n(e.INTLAYER_EDITOR_ENABLED,this.messenger,{emit:!1,receive:!0,initialValue:!1}),this.focusedContent=new n(e.INTLAYER_FOCUSED_CONTENT_CHANGED,this.messenger,{emit:!0,receive:!0,initialValue:null}),this.localeDictionaries=new n(e.INTLAYER_LOCALE_DICTIONARIES_CHANGED,this.messenger),this.editedContent=new n(e.INTLAYER_EDITED_CONTENT_CHANGED,this.messenger),this.configuration=new n(e.INTLAYER_CONFIGURATION,this.messenger,{emit:!0,receive:!1,...a.configuration?{initialValue:a.configuration}:{}}),this.currentLocale=new n(e.INTLAYER_CURRENT_LOCALE,this.messenger,{emit:a.mode===`client`,receive:a.mode===`editor`}),this._urlManager=new i(this.messenger),this._iframeInterceptor=new r(this.messenger)}start(){this.messenger.start(),this.editorEnabled.start(),this.focusedContent.start(),this.localeDictionaries.start(),this.editedContent.start(),this.configuration.start(),this.currentLocale.start(),this._mode===`client`?(this._urlManager.start(),this._iframeInterceptor.startInterceptor(),this._loadDictionaries(),this.messenger.send(`${e.INTLAYER_EDITED_CONTENT_CHANGED}/get`),this._configuration?.editor?.enabled!==!1&&this._setupActivationHandshake()):(this._iframeInterceptor.startMerger(),this._setupEditorHandshake())}stop(){this._unsubAreYouThere?.(),this._unsubActivate?.(),this._unsubClientReady?.(),this._unsubAreYouThere=null,this._unsubActivate=null,this._unsubClientReady=null,this.messenger.stop(),this.editorEnabled.stop(),this.focusedContent.stop(),this.localeDictionaries.stop(),this.editedContent.stop(),this.configuration.stop(),this.currentLocale.stop(),this._urlManager.stop(),this._iframeInterceptor.stopInterceptor(),this._iframeInterceptor.stopMerger()}pingClient(){this._mode===`editor`&&this.messenger.send(e.INTLAYER_ARE_YOU_THERE)}setFocusedContentKeyPath(e){let t=e.filter(e=>e.type!==a.Translation),n=this.focusedContent.value;n&&this.focusedContent.set({...n,keyPath:t})}setLocaleDictionary(e){if(!e.localId)return;let t=this.localeDictionaries.value??{};this.localeDictionaries.set({...t,[e.localId]:e})}setEditedDictionary(e){if(!e.localId){console.error(`setEditedDictionary: missing localId`,e);return}let t=this.editedContent.value??{};this.editedContent.set({...t,[e.localId]:e})}setEditedContent(e,t){let n=this.editedContent.value??{};this.editedContent.set({...n,[e]:{...n[e],content:t}})}addContent(e,t,n=[],r=!0){let i=this.editedContent.value??{},a=(this.localeDictionaries.value??{})[e]?.content,c=structuredClone(i[e]?.content??a),l=n;if(!r){let e=0,t=n.slice(0,-1),r=n[n.length-1],i=r.key;for(;s(c,l)!==void 0;)e++,i=e===0?r.key:`${r.key} (${e})`,l=[...t,{...r,key:i}]}let u=o(c,l,t);this.editedContent.set({...i,[e]:{...i[e],content:u}})}renameContent(e,t,n=[]){let r=this.editedContent.value??{},i=(this.localeDictionaries.value??{})[e]?.content,a=c(structuredClone(r[e]?.content??i),t,n);this.editedContent.set({...r,[e]:{...r[e],content:a}})}removeContent(e,t){let n=this.editedContent.value??{},r=(this.localeDictionaries.value??{})[e]?.content,i=o(structuredClone(n[e]?.content??r),t,s(r,t));this.editedContent.set({...n,[e]:{...n[e],content:i}})}restoreContent(e){let t={...this.editedContent.value??{}};delete t[e],this.editedContent.set(t)}clearContent(e){let t={...this.editedContent.value??{}};delete t[e],this.editedContent.set(t)}clearAllContent(){this.editedContent.set({})}getContentValue(e,t){let n=this.editedContent.value;if(!n)return;let r=t.filter(e=>e.type!==a.Translation),i=this.localeDictionaries.value;if(e.includes(`:local:`)||e.includes(`:remote:`))return i&&!(e in i)?void 0:s(n[e]?.content??{},r,this.currentLocale.value);let o=Object.keys(n).filter(t=>t.startsWith(`${e}:`)&&(!i||t in i));for(let e of o){let t=s(n[e]?.content??{},r,this.currentLocale.value);if(t)return t}}_setupEditorHandshake(){this._unsubClientReady=this.messenger.subscribe(e.INTLAYER_CLIENT_READY,()=>{this.editorEnabled.set(!0),this.messenger.send(e.INTLAYER_EDITOR_ACTIVATE)}),this.messenger.send(e.INTLAYER_ARE_YOU_THERE)}_setupActivationHandshake(){this.messenger.send(e.INTLAYER_CLIENT_READY),this._unsubAreYouThere=this.messenger.subscribe(e.INTLAYER_ARE_YOU_THERE,()=>{this.messenger.send(e.INTLAYER_CLIENT_READY)}),this._unsubActivate=this.messenger.subscribe(e.INTLAYER_EDITOR_ACTIVATE,()=>{this.editorEnabled.set(!0),this._broadcastData()})}_broadcastData(){let t=this.configuration.value;t&&this.messenger.send(`${e.INTLAYER_CONFIGURATION}/post`,t);let n=this.currentLocale.value;n&&this.messenger.send(`${e.INTLAYER_CURRENT_LOCALE}/post`,n);let r=this.localeDictionaries.value;r&&this.messenger.send(`${e.INTLAYER_LOCALE_DICTIONARIES_CHANGED}/post`,r)}async _loadDictionaries(){try{let e=(await import(`@intlayer/unmerged-dictionaries-entry`)).getUnmergedDictionaries(),t=Object.fromEntries(Object.values(e).flat().map(e=>[e.localId,e]));this.localeDictionaries.set(t),this.editorEnabled.value&&this._broadcastData()}catch(e){console.warn(`[intlayer] Failed to load unmerged dictionaries:`,e)}}};export{l as EditorStateManager};
1
+ import { MessageKey } from "../messageKey.mjs";
2
+ import { CrossFrameMessenger } from "./CrossFrameMessenger.mjs";
3
+ import { CrossFrameStateManager } from "./CrossFrameStateManager.mjs";
4
+ import { IframeClickInterceptor } from "./IframeClickInterceptor.mjs";
5
+ import { UrlStateManager } from "./UrlStateManager.mjs";
6
+ import * as NodeTypes from "@intlayer/types/nodeType";
7
+ import { editDictionaryByKeyPath, getContentNodeByKeyPath, renameContentNodeByKeyPath } from "@intlayer/core/dictionaryManipulator";
8
+
9
+ //#region src/core/EditorStateManager.ts
10
+ /**
11
+ * EditorStateManager is the single entry point for all Intlayer editor state.
12
+ * It is framework-agnostic: instantiate one instance at the root of the application
13
+ * and subscribe to its EventTarget-based events from any framework adapter.
14
+ *
15
+ * Replaces all context providers, hooks and store files across React, Preact,
16
+ * Solid, Svelte, and Vue integrations.
17
+ */
18
+ var EditorStateManager = class {
19
+ constructor(config) {
20
+ this._unsubAreYouThere = null;
21
+ this._unsubActivate = null;
22
+ this._unsubClientReady = null;
23
+ this._mode = config.mode;
24
+ this._configuration = config.configuration;
25
+ this.messenger = new CrossFrameMessenger(config.messenger);
26
+ this.editorEnabled = new CrossFrameStateManager(MessageKey.INTLAYER_EDITOR_ENABLED, this.messenger, {
27
+ emit: false,
28
+ receive: true,
29
+ initialValue: false
30
+ });
31
+ this.focusedContent = new CrossFrameStateManager(MessageKey.INTLAYER_FOCUSED_CONTENT_CHANGED, this.messenger, {
32
+ emit: true,
33
+ receive: true,
34
+ initialValue: null
35
+ });
36
+ this.localeDictionaries = new CrossFrameStateManager(MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED, this.messenger);
37
+ this.editedContent = new CrossFrameStateManager(MessageKey.INTLAYER_EDITED_CONTENT_CHANGED, this.messenger);
38
+ this.configuration = new CrossFrameStateManager(MessageKey.INTLAYER_CONFIGURATION, this.messenger, {
39
+ emit: true,
40
+ receive: false,
41
+ ...config.configuration ? { initialValue: config.configuration } : {}
42
+ });
43
+ this.currentLocale = new CrossFrameStateManager(MessageKey.INTLAYER_CURRENT_LOCALE, this.messenger, {
44
+ emit: config.mode === "client",
45
+ receive: config.mode === "editor"
46
+ });
47
+ this._urlManager = new UrlStateManager(this.messenger);
48
+ this._iframeInterceptor = new IframeClickInterceptor(this.messenger);
49
+ }
50
+ start() {
51
+ this.messenger.start();
52
+ this.editorEnabled.start();
53
+ this.focusedContent.start();
54
+ this.localeDictionaries.start();
55
+ this.editedContent.start();
56
+ this.configuration.start();
57
+ this.currentLocale.start();
58
+ if (this._mode === "client") {
59
+ this._urlManager.start();
60
+ this._iframeInterceptor.startInterceptor();
61
+ this._loadDictionaries();
62
+ this.messenger.send(`${MessageKey.INTLAYER_EDITED_CONTENT_CHANGED}/get`);
63
+ if (this._configuration?.editor?.enabled !== false) this._setupActivationHandshake();
64
+ } else {
65
+ this._iframeInterceptor.startMerger();
66
+ this._setupEditorHandshake();
67
+ }
68
+ }
69
+ stop() {
70
+ this._unsubAreYouThere?.();
71
+ this._unsubActivate?.();
72
+ this._unsubClientReady?.();
73
+ this._unsubAreYouThere = null;
74
+ this._unsubActivate = null;
75
+ this._unsubClientReady = null;
76
+ this.messenger.stop();
77
+ this.editorEnabled.stop();
78
+ this.focusedContent.stop();
79
+ this.localeDictionaries.stop();
80
+ this.editedContent.stop();
81
+ this.configuration.stop();
82
+ this.currentLocale.stop();
83
+ this._urlManager.stop();
84
+ this._iframeInterceptor.stopInterceptor();
85
+ this._iframeInterceptor.stopMerger();
86
+ }
87
+ /**
88
+ * EDITOR mode: re-send ARE_YOU_THERE to attempt re-connection with the client.
89
+ * Call this when the user clicks "Enable Editor" or when the iframe reloads.
90
+ */
91
+ pingClient() {
92
+ if (this._mode !== "editor") return;
93
+ this.messenger.send(MessageKey.INTLAYER_ARE_YOU_THERE);
94
+ }
95
+ setFocusedContentKeyPath(keyPath) {
96
+ const filtered = keyPath.filter((key) => key.type !== NodeTypes.TRANSLATION);
97
+ const prev = this.focusedContent.value;
98
+ if (!prev) return;
99
+ this.focusedContent.set({
100
+ ...prev,
101
+ keyPath: filtered
102
+ });
103
+ }
104
+ setLocaleDictionary(dictionary) {
105
+ if (!dictionary.localId) return;
106
+ const current = this.localeDictionaries.value ?? {};
107
+ this.localeDictionaries.set({
108
+ ...current,
109
+ [dictionary.localId]: dictionary
110
+ });
111
+ }
112
+ setEditedDictionary(newDict) {
113
+ if (!newDict.localId) {
114
+ console.error("setEditedDictionary: missing localId", newDict);
115
+ return;
116
+ }
117
+ const current = this.editedContent.value ?? {};
118
+ this.editedContent.set({
119
+ ...current,
120
+ [newDict.localId]: newDict
121
+ });
122
+ }
123
+ setEditedContent(localDictionaryId, newValue) {
124
+ const current = this.editedContent.value ?? {};
125
+ this.editedContent.set({
126
+ ...current,
127
+ [localDictionaryId]: {
128
+ ...current[localDictionaryId],
129
+ content: newValue
130
+ }
131
+ });
132
+ }
133
+ addContent(localDictionaryId, newValue, keyPath = [], overwrite = true) {
134
+ const current = this.editedContent.value ?? {};
135
+ const originalContent = (this.localeDictionaries.value ?? {})[localDictionaryId]?.content;
136
+ const currentContent = structuredClone(current[localDictionaryId]?.content ?? originalContent);
137
+ let newKeyPath = keyPath;
138
+ if (!overwrite) {
139
+ let index = 0;
140
+ const otherKeyPath = keyPath.slice(0, -1);
141
+ const lastKeyPath = keyPath[keyPath.length - 1];
142
+ let finalKey = lastKeyPath.key;
143
+ while (typeof getContentNodeByKeyPath(currentContent, newKeyPath) !== "undefined") {
144
+ index++;
145
+ finalKey = index === 0 ? lastKeyPath.key : `${lastKeyPath.key} (${index})`;
146
+ newKeyPath = [...otherKeyPath, {
147
+ ...lastKeyPath,
148
+ key: finalKey
149
+ }];
150
+ }
151
+ }
152
+ const updatedContent = editDictionaryByKeyPath(currentContent, newKeyPath, newValue);
153
+ this.editedContent.set({
154
+ ...current,
155
+ [localDictionaryId]: {
156
+ ...current[localDictionaryId],
157
+ content: updatedContent
158
+ }
159
+ });
160
+ }
161
+ renameContent(localDictionaryId, newKey, keyPath = []) {
162
+ const current = this.editedContent.value ?? {};
163
+ const originalContent = (this.localeDictionaries.value ?? {})[localDictionaryId]?.content;
164
+ const updated = renameContentNodeByKeyPath(structuredClone(current[localDictionaryId]?.content ?? originalContent), newKey, keyPath);
165
+ this.editedContent.set({
166
+ ...current,
167
+ [localDictionaryId]: {
168
+ ...current[localDictionaryId],
169
+ content: updated
170
+ }
171
+ });
172
+ }
173
+ removeContent(localDictionaryId, keyPath) {
174
+ const current = this.editedContent.value ?? {};
175
+ const originalContent = (this.localeDictionaries.value ?? {})[localDictionaryId]?.content;
176
+ const restored = editDictionaryByKeyPath(structuredClone(current[localDictionaryId]?.content ?? originalContent), keyPath, getContentNodeByKeyPath(originalContent, keyPath));
177
+ this.editedContent.set({
178
+ ...current,
179
+ [localDictionaryId]: {
180
+ ...current[localDictionaryId],
181
+ content: restored
182
+ }
183
+ });
184
+ }
185
+ restoreContent(localDictionaryId) {
186
+ const updated = { ...this.editedContent.value ?? {} };
187
+ delete updated[localDictionaryId];
188
+ this.editedContent.set(updated);
189
+ }
190
+ clearContent(localDictionaryId) {
191
+ const filtered = { ...this.editedContent.value ?? {} };
192
+ delete filtered[localDictionaryId];
193
+ this.editedContent.set(filtered);
194
+ }
195
+ clearAllContent() {
196
+ this.editedContent.set({});
197
+ }
198
+ getContentValue(localDictionaryIdOrKey, keyPath) {
199
+ const edited = this.editedContent.value;
200
+ if (!edited) return void 0;
201
+ const filteredKeyPath = keyPath.filter((key) => key.type !== NodeTypes.TRANSLATION);
202
+ const localeDicts = this.localeDictionaries.value;
203
+ if (localDictionaryIdOrKey.includes(":local:") || localDictionaryIdOrKey.includes(":remote:")) {
204
+ if (localeDicts && !(localDictionaryIdOrKey in localeDicts)) return;
205
+ return getContentNodeByKeyPath(edited[localDictionaryIdOrKey]?.content ?? {}, filteredKeyPath, this.currentLocale.value);
206
+ }
207
+ const matchingIds = Object.keys(edited).filter((key) => key.startsWith(`${localDictionaryIdOrKey}:`) && (!localeDicts || key in localeDicts));
208
+ for (const localId of matchingIds) {
209
+ const node = getContentNodeByKeyPath(edited[localId]?.content ?? {}, filteredKeyPath, this.currentLocale.value);
210
+ if (node) return node;
211
+ }
212
+ }
213
+ /**
214
+ * EDITOR mode: listen for CLIENT_READY and respond with EDITOR_ACTIVATE.
215
+ * Also pings the client immediately in case it loaded before the editor.
216
+ */
217
+ _setupEditorHandshake() {
218
+ this._unsubClientReady = this.messenger.subscribe(MessageKey.INTLAYER_CLIENT_READY, () => {
219
+ this.editorEnabled.set(true);
220
+ this.messenger.send(MessageKey.INTLAYER_EDITOR_ACTIVATE);
221
+ });
222
+ this.messenger.send(MessageKey.INTLAYER_ARE_YOU_THERE);
223
+ }
224
+ _setupActivationHandshake() {
225
+ this.messenger.send(MessageKey.INTLAYER_CLIENT_READY);
226
+ this._unsubAreYouThere = this.messenger.subscribe(MessageKey.INTLAYER_ARE_YOU_THERE, () => {
227
+ this.messenger.send(MessageKey.INTLAYER_CLIENT_READY);
228
+ });
229
+ this._unsubActivate = this.messenger.subscribe(MessageKey.INTLAYER_EDITOR_ACTIVATE, () => {
230
+ this.editorEnabled.set(true);
231
+ this._broadcastData();
232
+ });
233
+ }
234
+ _broadcastData() {
235
+ const configVal = this.configuration.value;
236
+ if (configVal) this.messenger.send(`${MessageKey.INTLAYER_CONFIGURATION}/post`, configVal);
237
+ const localeVal = this.currentLocale.value;
238
+ if (localeVal) this.messenger.send(`${MessageKey.INTLAYER_CURRENT_LOCALE}/post`, localeVal);
239
+ const dicts = this.localeDictionaries.value;
240
+ if (dicts) this.messenger.send(`${MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED}/post`, dicts);
241
+ }
242
+ async _loadDictionaries() {
243
+ try {
244
+ const unmergedDictionaries = (await import("@intlayer/unmerged-dictionaries-entry")).getUnmergedDictionaries();
245
+ const dictionariesList = Object.fromEntries(Object.values(unmergedDictionaries).flat().map((dictionary) => [dictionary.localId, dictionary]));
246
+ this.localeDictionaries.set(dictionariesList);
247
+ if (this.editorEnabled.value) this._broadcastData();
248
+ } catch (e) {
249
+ console.warn("[intlayer] Failed to load unmerged dictionaries:", e);
250
+ }
251
+ }
252
+ };
253
+
254
+ //#endregion
255
+ export { EditorStateManager };
2
256
  //# sourceMappingURL=EditorStateManager.mjs.map
@@ -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 { NodeType } 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((key) => key.type !== NodeType.Translation);\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 !== NodeType.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":"mfAgDA,IAAa,EAAb,KAAgC,CAoB9B,YAAY,EAAkC,wBALG,yBACH,4BAEG,KAG/C,KAAK,MAAQ,EAAO,KACpB,KAAK,eAAiB,EAAO,cAE7B,KAAK,UAAY,IAAI,EAAoB,EAAO,UAAU,CAE1D,KAAK,cAAgB,IAAI,EACvB,EAAW,wBACX,KAAK,UACL,CAAE,KAAM,GAAO,QAAS,GAAM,aAAc,GAAO,CACpD,CAED,KAAK,eAAiB,IAAI,EACxB,EAAW,iCACX,KAAK,UACL,CAAE,KAAM,GAAM,QAAS,GAAM,aAAc,KAAM,CAClD,CAED,KAAK,mBAAqB,IAAI,EAC5B,EAAW,qCACX,KAAK,UACN,CAED,KAAK,cAAgB,IAAI,EACvB,EAAW,gCACX,KAAK,UACN,CAED,KAAK,cAAgB,IAAI,EACvB,EAAW,uBACX,KAAK,UACL,CACE,KAAM,GACN,QAAS,GACT,GAAI,EAAO,cAAgB,CAAE,aAAc,EAAO,cAAe,CAAG,EAAE,CACvE,CACF,CAGD,KAAK,cAAgB,IAAI,EACvB,EAAW,wBACX,KAAK,UACL,CACE,KAAM,EAAO,OAAS,SACtB,QAAS,EAAO,OAAS,SAC1B,CACF,CAED,KAAK,YAAc,IAAI,EAAgB,KAAK,UAAU,CACtD,KAAK,mBAAqB,IAAI,EAAuB,KAAK,UAAU,CAGtE,OAAc,CACZ,KAAK,UAAU,OAAO,CACtB,KAAK,cAAc,OAAO,CAC1B,KAAK,eAAe,OAAO,CAC3B,KAAK,mBAAmB,OAAO,CAC/B,KAAK,cAAc,OAAO,CAC1B,KAAK,cAAc,OAAO,CAC1B,KAAK,cAAc,OAAO,CAEtB,KAAK,QAAU,UACjB,KAAK,YAAY,OAAO,CACxB,KAAK,mBAAmB,kBAAkB,CAC1C,KAAK,mBAAmB,CAExB,KAAK,UAAU,KAAK,GAAG,EAAW,gCAAgC,MAAM,CAEpE,KAAK,gBAAgB,QAAQ,UAAY,IAC3C,KAAK,2BAA2B,GAGlC,KAAK,mBAAmB,aAAa,CACrC,KAAK,uBAAuB,EAIhC,MAAa,CACX,KAAK,qBAAqB,CAC1B,KAAK,kBAAkB,CACvB,KAAK,qBAAqB,CAC1B,KAAK,kBAAoB,KACzB,KAAK,eAAiB,KACtB,KAAK,kBAAoB,KACzB,KAAK,UAAU,MAAM,CACrB,KAAK,cAAc,MAAM,CACzB,KAAK,eAAe,MAAM,CAC1B,KAAK,mBAAmB,MAAM,CAC9B,KAAK,cAAc,MAAM,CACzB,KAAK,cAAc,MAAM,CACzB,KAAK,cAAc,MAAM,CACzB,KAAK,YAAY,MAAM,CACvB,KAAK,mBAAmB,iBAAiB,CACzC,KAAK,mBAAmB,YAAY,CAStC,YAAmB,CACb,KAAK,QAAU,UAEnB,KAAK,UAAU,KAAK,EAAW,uBAAuB,CAKxD,yBAAyB,EAA0B,CACjD,IAAM,EAAW,EAAQ,OAAQ,GAAQ,EAAI,OAAS,EAAS,YAAY,CACrE,EAAO,KAAK,eAAe,MAE5B,GAEL,KAAK,eAAe,IAAI,CAAE,GAAG,EAAM,QAAS,EAAU,CAAC,CAKzD,oBAAoB,EAA8B,CAChD,GAAI,CAAC,EAAW,QAAS,OACzB,IAAM,EAAU,KAAK,mBAAmB,OAAS,EAAE,CAEnD,KAAK,mBAAmB,IAAI,CAC1B,GAAG,GACF,EAAW,SAA+B,EAC5C,CAAC,CAKJ,oBAAoB,EAA2B,CAC7C,GAAI,CAAC,EAAQ,QAAS,CACpB,QAAQ,MAAM,uCAAwC,EAAQ,CAC9D,OAEF,IAAM,EAAU,KAAK,cAAc,OAAS,EAAE,CAE9C,KAAK,cAAc,IAAI,CACrB,GAAG,GACF,EAAQ,SAA+B,EACzC,CAAC,CAGJ,iBACE,EACA,EACM,CACN,IAAM,EAAU,KAAK,cAAc,OAAS,EAAE,CAE9C,KAAK,cAAc,IAAI,CACrB,GAAG,GACF,GAAoB,CACnB,GAAG,EAAQ,GACX,QAAS,EACV,CACF,CAAC,CAGJ,WACE,EACA,EACA,EAAqB,EAAE,CACvB,EAAY,GACN,CACN,IAAM,EAAU,KAAK,cAAc,OAAS,EAAE,CAGxC,GAFc,KAAK,mBAAmB,OAAS,EAAE,EAEnB,IAAoB,QAClD,EAAiB,gBACrB,EAAQ,IAAoB,SAAW,EACxC,CAEG,EAAa,EACjB,GAAI,CAAC,EAAW,CACd,IAAI,EAAQ,EAEN,EAAe,EAAQ,MAAM,EAAG,GAAG,CACnC,EAAc,EAAQ,EAAQ,OAAS,GAEzC,EAAW,EAAY,IAE3B,KACS,EAAwB,EAAgB,EAAW,GAC1D,QAEA,IACA,EACE,IAAU,EAAI,EAAY,IAAM,GAAG,EAAY,IAAI,IAAI,EAAM,GAC/D,EAAa,CACX,GAAG,EACH,CAAE,GAAG,EAAa,IAAK,EAAU,CAClC,CAIL,IAAM,EAAiB,EACrB,EACA,EACA,EACD,CAED,KAAK,cAAc,IAAI,CACrB,GAAG,GACF,GAAoB,CACnB,GAAG,EAAQ,GACX,QAAS,EACV,CACF,CAAC,CAGJ,cACE,EACA,EACA,EAAqB,EAAE,CACjB,CACN,IAAM,EAAU,KAAK,cAAc,OAAS,EAAE,CAExC,GADc,KAAK,mBAAmB,OAAS,EAAE,EACnB,IAAoB,QAIlD,EAAU,EAHO,gBACrB,EAAQ,IAAoB,SAAW,EACxC,CAC0D,EAAQ,EAAQ,CAE3E,KAAK,cAAc,IAAI,CACrB,GAAG,GACF,GAAoB,CACnB,GAAG,EAAQ,GACX,QAAS,EACV,CACF,CAAC,CAGJ,cACE,EACA,EACM,CACN,IAAM,EAAU,KAAK,cAAc,OAAS,EAAE,CAExC,GADc,KAAK,mBAAmB,OAAS,EAAE,EACnB,IAAoB,QAKlD,EAAW,EAJM,gBACrB,EAAQ,IAAoB,SAAW,EACxC,CAIC,EAHqB,EAAwB,EAAiB,EAAQ,CAKvE,CAED,KAAK,cAAc,IAAI,CACrB,GAAG,GACF,GAAoB,CACnB,GAAG,EAAQ,GACX,QAAS,EACV,CACF,CAAC,CAGJ,eAAe,EAA4C,CAEzD,IAAM,EAAU,CAAE,GADF,KAAK,cAAc,OAAS,EAAE,CAChB,CAE9B,OAAO,EAAQ,GAEf,KAAK,cAAc,IAAI,EAAQ,CAGjC,aAAa,EAA4C,CAEvD,IAAM,EAAW,CAAE,GADH,KAAK,cAAc,OAAS,EAAE,CACf,CAE/B,OAAO,EAAS,GAEhB,KAAK,cAAc,IAAI,EAAS,CAGlC,iBAAwB,CACtB,KAAK,cAAc,IAAI,EAAE,CAAC,CAG5B,gBACE,EACA,EACyB,CACzB,IAAM,EAAS,KAAK,cAAc,MAClC,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAkB,EAAQ,OAC7B,GAAQ,EAAI,OAAS,EAAS,YAChC,CAKK,EAAc,KAAK,mBAAmB,MAM5C,GAHE,EAAuB,SAAS,UAAU,EAC1C,EAAuB,SAAS,WAAW,CAU3C,OANI,GAAe,EAAE,KAA0B,GAC7C,OAKK,EAFL,EAAO,IAA8C,SAAW,EAAE,CAIlE,EACA,KAAK,cAAc,MACpB,CAGH,IAAM,EAAc,OAAO,KAAK,EAAO,CAAC,OACrC,GACC,EAAI,WAAW,GAAG,EAAuB,GAAG,GAE3C,CAAC,GAAe,KAAO,GAC3B,CAED,IAAK,IAAM,KAAW,EAAa,CAEjC,IAAM,EAAO,EADG,EAAO,IAA+B,SAAW,EAAE,CAGjE,EACA,KAAK,cAAc,MACpB,CACD,GAAI,EAAM,OAAO,GAUrB,uBAAsC,CAEpC,KAAK,kBAAoB,KAAK,UAAU,UACtC,EAAW,0BACL,CACJ,KAAK,cAAc,IAAI,GAAK,CAC5B,KAAK,UAAU,KAAK,EAAW,yBAAyB,EAE3D,CAGD,KAAK,UAAU,KAAK,EAAW,uBAAuB,CAGxD,2BAA0C,CAExC,KAAK,UAAU,KAAK,EAAW,sBAAsB,CAGrD,KAAK,kBAAoB,KAAK,UAAU,UACtC,EAAW,2BACL,CACJ,KAAK,UAAU,KAAK,EAAW,sBAAsB,EAExD,CAGD,KAAK,eAAiB,KAAK,UAAU,UACnC,EAAW,6BACL,CACJ,KAAK,cAAc,IAAI,GAAK,CAC5B,KAAK,gBAAgB,EAExB,CAGH,gBAA+B,CAC7B,IAAM,EAAY,KAAK,cAAc,MAEjC,GACF,KAAK,UAAU,KACb,GAAG,EAAW,uBAAuB,OACrC,EACD,CAEH,IAAM,EAAY,KAAK,cAAc,MAEjC,GACF,KAAK,UAAU,KACb,GAAG,EAAW,wBAAwB,OACtC,EACD,CAEH,IAAM,EAAQ,KAAK,mBAAmB,MAElC,GACF,KAAK,UAAU,KACb,GAAG,EAAW,qCAAqC,OACnD,EACD,CAIL,MAAc,mBAAmC,CAC/C,GAAI,CAEF,IAAM,GADM,MAAM,OAAO,0CACQ,yBAAyB,CACpD,EAAmB,OAAO,YAC9B,OAAO,OAAO,EAAqB,CAChC,MAAM,CACN,IAAK,GAAe,CAAC,EAAW,QAAS,EAAW,CAAC,CACzD,CAED,KAAK,mBAAmB,IAAI,EAAiB,CAIzC,KAAK,cAAc,OACrB,KAAK,gBAAgB,OAEhB,EAAG,CAEV,QAAQ,KAAK,mDAAoD,EAAE"}
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,2 +1,45 @@
1
- import{MessageKey as e}from"../messageKey.mjs";import{mergeIframeClick as t}from"../mergeIframeClick.mjs";var n=class{constructor(e){this._mousedownHandler=null,this._unsubscribeMerge=null,this._messenger=e}startInterceptor(){typeof window>`u`||(this._mousedownHandler=()=>{this._messenger.send(e.INTLAYER_IFRAME_CLICKED)},window.addEventListener(`mousedown`,this._mousedownHandler))}startMerger(){this._unsubscribeMerge=this._messenger.subscribe(e.INTLAYER_IFRAME_CLICKED,t)}stopInterceptor(){this._mousedownHandler&&=(window.removeEventListener(`mousedown`,this._mousedownHandler),null)}stopMerger(){this._unsubscribeMerge?.(),this._unsubscribeMerge=null}};export{n as IframeClickInterceptor};
1
+ import { MessageKey } from "../messageKey.mjs";
2
+ import { mergeIframeClick } from "../mergeIframeClick.mjs";
3
+
4
+ //#region src/core/IframeClickInterceptor.ts
5
+ /**
6
+ * IframeClickInterceptor handles click events across iframe boundaries.
7
+ *
8
+ * - startInterceptor(): called in the client (iframe) — broadcasts mousedown to parent
9
+ * - startMerger(): called in the editor (parent) — merges received clicks into DOM events
10
+ *
11
+ * Replaces useIframeClickInterceptor / useIframeClickMerger across all frameworks.
12
+ */
13
+ var IframeClickInterceptor = class {
14
+ constructor(messenger) {
15
+ this._mousedownHandler = null;
16
+ this._unsubscribeMerge = null;
17
+ this._messenger = messenger;
18
+ }
19
+ /** Called on the client side (inside iframe). Broadcasts click events to parent. */
20
+ startInterceptor() {
21
+ if (typeof window === "undefined") return;
22
+ this._mousedownHandler = () => {
23
+ this._messenger.send(MessageKey.INTLAYER_IFRAME_CLICKED);
24
+ };
25
+ window.addEventListener("mousedown", this._mousedownHandler);
26
+ }
27
+ /** Called on the editor side (parent frame). Merges incoming iframe clicks into DOM. */
28
+ startMerger() {
29
+ this._unsubscribeMerge = this._messenger.subscribe(MessageKey.INTLAYER_IFRAME_CLICKED, mergeIframeClick);
30
+ }
31
+ stopInterceptor() {
32
+ if (this._mousedownHandler) {
33
+ window.removeEventListener("mousedown", this._mousedownHandler);
34
+ this._mousedownHandler = null;
35
+ }
36
+ }
37
+ stopMerger() {
38
+ this._unsubscribeMerge?.();
39
+ this._unsubscribeMerge = null;
40
+ }
41
+ };
42
+
43
+ //#endregion
44
+ export { IframeClickInterceptor };
2
45
  //# sourceMappingURL=IframeClickInterceptor.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"IframeClickInterceptor.mjs","names":[],"sources":["../../../src/core/IframeClickInterceptor.ts"],"sourcesContent":["import { mergeIframeClick } from '../mergeIframeClick';\nimport { MessageKey } from '../messageKey';\nimport type { CrossFrameMessenger } from './CrossFrameMessenger';\n\n/**\n * IframeClickInterceptor handles click events across iframe boundaries.\n *\n * - startInterceptor(): called in the client (iframe) — broadcasts mousedown to parent\n * - startMerger(): called in the editor (parent) — merges received clicks into DOM events\n *\n * Replaces useIframeClickInterceptor / useIframeClickMerger across all frameworks.\n */\nexport class IframeClickInterceptor {\n private readonly _messenger: CrossFrameMessenger;\n private _mousedownHandler: EventListener | null = null;\n private _unsubscribeMerge: (() => void) | null = null;\n\n constructor(messenger: CrossFrameMessenger) {\n this._messenger = messenger;\n }\n\n /** Called on the client side (inside iframe). Broadcasts click events to parent. */\n startInterceptor(): void {\n if (typeof window === 'undefined') return;\n this._mousedownHandler = () => {\n this._messenger.send(MessageKey.INTLAYER_IFRAME_CLICKED);\n };\n window.addEventListener('mousedown', this._mousedownHandler);\n }\n\n /** Called on the editor side (parent frame). Merges incoming iframe clicks into DOM. */\n startMerger(): void {\n this._unsubscribeMerge = this._messenger.subscribe(\n MessageKey.INTLAYER_IFRAME_CLICKED,\n mergeIframeClick as (data: unknown) => void\n );\n }\n\n stopInterceptor(): void {\n if (this._mousedownHandler) {\n window.removeEventListener('mousedown', this._mousedownHandler);\n this._mousedownHandler = null;\n }\n }\n\n stopMerger(): void {\n this._unsubscribeMerge?.();\n this._unsubscribeMerge = null;\n }\n}\n"],"mappings":"0GAYA,IAAa,EAAb,KAAoC,CAKlC,YAAY,EAAgC,wBAHM,4BACD,KAG/C,KAAK,WAAa,EAIpB,kBAAyB,CACnB,OAAO,OAAW,MACtB,KAAK,sBAA0B,CAC7B,KAAK,WAAW,KAAK,EAAW,wBAAwB,EAE1D,OAAO,iBAAiB,YAAa,KAAK,kBAAkB,EAI9D,aAAoB,CAClB,KAAK,kBAAoB,KAAK,WAAW,UACvC,EAAW,wBACX,EACD,CAGH,iBAAwB,CACtB,AAEE,KAAK,qBADL,OAAO,oBAAoB,YAAa,KAAK,kBAAkB,CACtC,MAI7B,YAAmB,CACjB,KAAK,qBAAqB,CAC1B,KAAK,kBAAoB"}
1
+ {"version":3,"file":"IframeClickInterceptor.mjs","names":[],"sources":["../../../src/core/IframeClickInterceptor.ts"],"sourcesContent":["import { mergeIframeClick } from '../mergeIframeClick';\nimport { MessageKey } from '../messageKey';\nimport type { CrossFrameMessenger } from './CrossFrameMessenger';\n\n/**\n * IframeClickInterceptor handles click events across iframe boundaries.\n *\n * - startInterceptor(): called in the client (iframe) — broadcasts mousedown to parent\n * - startMerger(): called in the editor (parent) — merges received clicks into DOM events\n *\n * Replaces useIframeClickInterceptor / useIframeClickMerger across all frameworks.\n */\nexport class IframeClickInterceptor {\n private readonly _messenger: CrossFrameMessenger;\n private _mousedownHandler: EventListener | null = null;\n private _unsubscribeMerge: (() => void) | null = null;\n\n constructor(messenger: CrossFrameMessenger) {\n this._messenger = messenger;\n }\n\n /** Called on the client side (inside iframe). Broadcasts click events to parent. */\n startInterceptor(): void {\n if (typeof window === 'undefined') return;\n this._mousedownHandler = () => {\n this._messenger.send(MessageKey.INTLAYER_IFRAME_CLICKED);\n };\n window.addEventListener('mousedown', this._mousedownHandler);\n }\n\n /** Called on the editor side (parent frame). Merges incoming iframe clicks into DOM. */\n startMerger(): void {\n this._unsubscribeMerge = this._messenger.subscribe(\n MessageKey.INTLAYER_IFRAME_CLICKED,\n mergeIframeClick as (data: unknown) => void\n );\n }\n\n stopInterceptor(): void {\n if (this._mousedownHandler) {\n window.removeEventListener('mousedown', this._mousedownHandler);\n this._mousedownHandler = null;\n }\n }\n\n stopMerger(): void {\n this._unsubscribeMerge?.();\n this._unsubscribeMerge = null;\n }\n}\n"],"mappings":";;;;;;;;;;;;AAYA,IAAa,yBAAb,MAAoC;CAKlC,YAAY,WAAgC;2BAHM;2BACD;AAG/C,OAAK,aAAa;;;CAIpB,mBAAyB;AACvB,MAAI,OAAO,WAAW,YAAa;AACnC,OAAK,0BAA0B;AAC7B,QAAK,WAAW,KAAK,WAAW,wBAAwB;;AAE1D,SAAO,iBAAiB,aAAa,KAAK,kBAAkB;;;CAI9D,cAAoB;AAClB,OAAK,oBAAoB,KAAK,WAAW,UACvC,WAAW,yBACX,iBACD;;CAGH,kBAAwB;AACtB,MAAI,KAAK,mBAAmB;AAC1B,UAAO,oBAAoB,aAAa,KAAK,kBAAkB;AAC/D,QAAK,oBAAoB;;;CAI7B,aAAmB;AACjB,OAAK,qBAAqB;AAC1B,OAAK,oBAAoB"}
@@ -1,2 +1,59 @@
1
- import{MessageKey as e}from"../messageKey.mjs";var t=class{constructor(e){this._originalPushState=null,this._originalReplaceState=null,this._listeners=[],this._messenger=e}start(){if(typeof window>`u`)return;let t=()=>{this._messenger.send(`${e.INTLAYER_URL_CHANGE}/post`,window.location.pathname)};this._originalPushState=history.pushState,this._originalReplaceState=history.replaceState;let n=e=>function(...t){e.apply(this,t),window.dispatchEvent(new Event(`locationchange`))};history.pushState=n(this._originalPushState),history.replaceState=n(this._originalReplaceState);for(let e of[`locationchange`,`popstate`,`hashchange`,`load`]){let n=t;window.addEventListener(e,n),this._listeners.push([e,n])}t()}stop(){if(!(typeof window>`u`)){for(let[e,t]of this._listeners)window.removeEventListener(e,t);this._listeners=[],this._originalPushState&&=(history.pushState=this._originalPushState,null),this._originalReplaceState&&=(history.replaceState=this._originalReplaceState,null)}}};export{t as UrlStateManager};
1
+ import { MessageKey } from "../messageKey.mjs";
2
+
3
+ //#region src/core/UrlStateManager.ts
4
+ /**
5
+ * UrlStateManager patches window.history and broadcasts URL path changes
6
+ * across frames via postMessage.
7
+ *
8
+ * Replaces useCrossURLPathSetter / useCrossURLPathState across all frameworks.
9
+ */
10
+ var UrlStateManager = class {
11
+ constructor(messenger) {
12
+ this._originalPushState = null;
13
+ this._originalReplaceState = null;
14
+ this._listeners = [];
15
+ this._messenger = messenger;
16
+ }
17
+ start() {
18
+ if (typeof window === "undefined") return;
19
+ const updateURLState = () => {
20
+ this._messenger.send(`${MessageKey.INTLAYER_URL_CHANGE}/post`, window.location.pathname);
21
+ };
22
+ this._originalPushState = history.pushState;
23
+ this._originalReplaceState = history.replaceState;
24
+ const injectLocationChange = (method) => function(...args) {
25
+ method.apply(this, args);
26
+ window.dispatchEvent(new Event("locationchange"));
27
+ };
28
+ history.pushState = injectLocationChange(this._originalPushState);
29
+ history.replaceState = injectLocationChange(this._originalReplaceState);
30
+ for (const eventName of [
31
+ "locationchange",
32
+ "popstate",
33
+ "hashchange",
34
+ "load"
35
+ ]) {
36
+ const listener = updateURLState;
37
+ window.addEventListener(eventName, listener);
38
+ this._listeners.push([eventName, listener]);
39
+ }
40
+ updateURLState();
41
+ }
42
+ stop() {
43
+ if (typeof window === "undefined") return;
44
+ for (const [eventName, listener] of this._listeners) window.removeEventListener(eventName, listener);
45
+ this._listeners = [];
46
+ if (this._originalPushState) {
47
+ history.pushState = this._originalPushState;
48
+ this._originalPushState = null;
49
+ }
50
+ if (this._originalReplaceState) {
51
+ history.replaceState = this._originalReplaceState;
52
+ this._originalReplaceState = null;
53
+ }
54
+ }
55
+ };
56
+
57
+ //#endregion
58
+ export { UrlStateManager };
2
59
  //# sourceMappingURL=UrlStateManager.mjs.map