svelte-intlayer 8.3.2 → 8.3.4

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.
@@ -1,6 +1,3 @@
1
- import { createCommunicator } from '../editor/communicator';
2
- import { createEditorEnabledClient } from '../editor/editorEnabled';
3
- import { createFocusDictionaryClient } from '../editor/focusDictionary';
4
1
  import { useEditor } from '../editor/useEditor';
5
2
  // Assuming setIntlayerContext exports a specific key or symbol
6
3
  import { setIntlayerContext } from './intlayerContext';
@@ -25,9 +22,6 @@ export const setupIntlayer = (initialLocale) => {
25
22
  // 1. Initialize your side effects
26
23
  // Note: If these need to run only in the browser, wrap them in $effect or onMount
27
24
  // inside your side-effect logic if they aren't already safe.
28
- createCommunicator();
29
- createEditorEnabledClient();
30
- createFocusDictionaryClient();
31
25
  useEditor();
32
26
  // 2. Create Reactive State (Svelte 5)
33
27
  // We make the locale a "rune" so updates propagate
@@ -1,11 +1,10 @@
1
1
  <script lang="ts">
2
2
  import type { NodeProps } from '@intlayer/core/interpreter';
3
3
  import { isSameKeyPath } from '@intlayer/core/utils';
4
- import { MessageKey } from '@intlayer/editor';
4
+ import { defineIntlayerElements, MessageKey } from '@intlayer/editor';
5
5
  import { NodeType } from '@intlayer/types/nodeType';
6
- import { get } from 'svelte/store';
7
- import ContentSelector from './ContentSelector.svelte';
8
- import { useCommunicator } from './communicator';
6
+ import { onMount } from 'svelte';
7
+ import { getEditorStateManager } from './communicator';
9
8
  import { useEditorEnabled } from './editorEnabled';
10
9
  import { useFocusDictionary } from './focusDictionary';
11
10
  import { useEditor } from './useEditor';
@@ -13,13 +12,14 @@ import { useEditor } from './useEditor';
13
12
  export let dictionaryKey: NodeProps['dictionaryKey'];
14
13
  export let keyPath: NodeProps['keyPath'];
15
14
 
15
+ const manager = getEditorStateManager();
16
16
  const { focusedContent, setFocusedContent } = useFocusDictionary();
17
- const editorEnabled = useEditorEnabled();
18
- const communicatorStore = useCommunicator();
19
- const { enabled } = editorEnabled;
17
+ const { enabled } = useEditorEnabled();
20
18
 
21
19
  useEditor();
22
20
 
21
+ onMount(() => defineIntlayerElements());
22
+
23
23
  $: filteredKeyPath = keyPath.filter((key) => key.type !== NodeType.Translation);
24
24
 
25
25
  $: isSelected =
@@ -27,44 +27,35 @@ $: isSelected =
27
27
  ($focusedContent?.keyPath?.length ?? 0) > 0 &&
28
28
  isSameKeyPath($focusedContent?.keyPath ?? [], filteredKeyPath);
29
29
 
30
- const handleSelect = () => {
31
- setFocusedContent({
32
- dictionaryKey,
33
- keyPath: filteredKeyPath,
34
- });
30
+ const handlePress = () => {
31
+ setFocusedContent({ dictionaryKey, keyPath: filteredKeyPath });
35
32
  };
36
33
 
37
34
  const handleHover = () => {
38
- const { postMessage, senderId } = get(communicatorStore);
39
- postMessage({
40
- type: `${MessageKey.INTLAYER_HOVERED_CONTENT_CHANGED}/post`,
41
- data: {
42
- dictionaryKey,
43
- keyPath: filteredKeyPath,
44
- },
45
- senderId,
46
- });
35
+ manager.messenger.send(
36
+ `${MessageKey.INTLAYER_HOVERED_CONTENT_CHANGED}/post`,
37
+ { dictionaryKey, keyPath: filteredKeyPath }
38
+ );
47
39
  };
48
40
 
49
41
  const handleUnhover = () => {
50
- const { postMessage, senderId } = get(communicatorStore);
51
- postMessage({
52
- type: `${MessageKey.INTLAYER_HOVERED_CONTENT_CHANGED}/post`,
53
- data: null,
54
- senderId,
55
- });
42
+ manager.messenger.send(
43
+ `${MessageKey.INTLAYER_HOVERED_CONTENT_CHANGED}/post`,
44
+ null
45
+ );
56
46
  };
57
47
  </script>
58
48
 
59
49
  {#if $enabled}
60
- <ContentSelector
61
- onPress={handleSelect}
62
- isSelecting={isSelected}
63
- onHover={handleHover}
64
- onUnhover={handleUnhover}
50
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
51
+ <intlayer-content-selector
52
+ is-selecting={isSelected || undefined}
53
+ on:intlayer:press={handlePress}
54
+ on:intlayer:hover={handleHover}
55
+ on:intlayer:unhover={handleUnhover}
65
56
  >
66
57
  <slot />
67
- </ContentSelector>
58
+ </intlayer-content-selector>
68
59
  {:else}
69
60
  <slot />
70
61
  {/if}
@@ -1,9 +1,7 @@
1
- import { type Writable } from 'svelte/store';
2
- export type Communicator = {
3
- postMessage: typeof window.postMessage;
4
- allowedOrigins?: string[];
1
+ import { EditorStateManager } from '@intlayer/editor';
2
+ export declare const createEditorStateManager: () => EditorStateManager;
3
+ export declare const getEditorStateManager: () => EditorStateManager;
4
+ export declare const useCommunicator: () => {
5
+ postMessage: (data: any) => void;
5
6
  senderId: string;
6
7
  };
7
- export type CommunicatorOptions = Omit<Communicator, 'senderId'>;
8
- export declare const createCommunicator: (options?: Partial<CommunicatorOptions>) => Writable<Communicator>;
9
- export declare const useCommunicator: () => Writable<Communicator>;
@@ -1,54 +1,59 @@
1
1
  import configuration from '@intlayer/config/built';
2
+ import { EditorStateManager } from '@intlayer/editor';
2
3
  import { getContext, setContext } from 'svelte';
3
- import { writable } from 'svelte/store';
4
- const randomUUID = () => Math.random().toString(36).slice(2);
5
- const { editor } = configuration;
6
- const postMessage = (data) => {
7
- if (typeof window === 'undefined')
8
- return;
9
- const isInIframe = window.self !== window.top;
10
- if (!isInIframe)
11
- return;
12
- if (editor.applicationURL.length > 0) {
13
- window.postMessage(data, editor.applicationURL);
14
- }
15
- if (editor.editorURL.length > 0) {
16
- window.parent.postMessage(data, editor.editorURL);
17
- }
18
- if (editor.cmsURL.length > 0) {
19
- window.parent.postMessage(data, editor.cmsURL);
20
- }
21
- };
22
- const defaultValue = {
23
- postMessage,
4
+ const { editor } = configuration ?? {};
5
+ const buildDefaultMessengerConfig = () => ({
24
6
  allowedOrigins: [
25
7
  editor?.applicationURL,
26
8
  editor?.editorURL,
27
9
  editor?.cmsURL,
28
- ],
29
- senderId: randomUUID(),
30
- };
31
- const COMMUNICATOR_KEY = Symbol('INTLAYER_COMMUNICATOR');
32
- export const createCommunicator = (options = {}) => {
33
- const store = writable({
34
- ...defaultValue,
35
- ...options,
10
+ ].filter(Boolean),
11
+ postMessageFn: (payload, origin) => {
12
+ if (typeof window === 'undefined')
13
+ return;
14
+ const isInIframe = window.self !== window.top;
15
+ if (!isInIframe)
16
+ return;
17
+ window.parent?.postMessage(payload, origin);
18
+ window.postMessage(payload, origin);
19
+ },
20
+ });
21
+ const MANAGER_KEY = Symbol('INTLAYER_EDITOR_STATE_MANAGER');
22
+ let globalManager = null;
23
+ export const createEditorStateManager = () => {
24
+ const manager = new EditorStateManager({
25
+ mode: 'client',
26
+ messenger: buildDefaultMessengerConfig(),
27
+ configuration,
36
28
  });
37
- setContext(COMMUNICATOR_KEY, store);
38
- return store;
29
+ try {
30
+ setContext(MANAGER_KEY, manager);
31
+ }
32
+ catch {
33
+ // Outside component context
34
+ }
35
+ globalManager = manager;
36
+ return manager;
39
37
  };
40
- export const useCommunicator = () => {
41
- let context;
42
- // `getContext` must only run inside a component.
43
- // If this is called in plain JS, we catch the error and fallback.
38
+ export const getEditorStateManager = () => {
44
39
  try {
45
- context = getContext(COMMUNICATOR_KEY);
40
+ const ctx = getContext(MANAGER_KEY);
41
+ if (ctx)
42
+ return ctx;
46
43
  }
47
44
  catch {
48
- // called outside component -> ignore, we’ll use global store
45
+ // Outside component context
49
46
  }
50
- if (!context) {
51
- return createCommunicator();
47
+ if (!globalManager) {
48
+ return createEditorStateManager();
52
49
  }
53
- return context;
50
+ return globalManager;
51
+ };
52
+ // Backward-compat alias
53
+ export const useCommunicator = () => {
54
+ const manager = getEditorStateManager();
55
+ return {
56
+ postMessage: (data) => manager.messenger.send(data.type, data.data),
57
+ senderId: manager.messenger.senderId,
58
+ };
54
59
  };
@@ -1,5 +1,5 @@
1
- import type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';
2
- export type DictionaryContent = Record<LocalDictionaryId, Dictionary>;
1
+ import type { DictionaryContent } from '@intlayer/editor';
2
+ export type { DictionaryContent };
3
3
  export declare const useDictionariesRecord: () => {
4
- dictionariesRecord: import("svelte/store").Writable<DictionaryContent | undefined>;
4
+ dictionariesRecord: import("svelte/store").Readable<DictionaryContent>;
5
5
  };
@@ -1,18 +1,11 @@
1
- import { MessageKey } from '@intlayer/editor';
2
- import { useCrossFrameState } from './useCrossFrameState';
3
- let loaded = false;
1
+ import { readable } from 'svelte/store';
2
+ import { getEditorStateManager } from './communicator';
4
3
  export const useDictionariesRecord = () => {
5
- const [dictionariesRecord, setDictionariesRecord] = useCrossFrameState(MessageKey.INTLAYER_LOCALE_DICTIONARIES_CHANGED, {});
6
- if (!loaded && typeof window !== 'undefined') {
7
- // Load dictionaries dynamically to do not impact the bundle, and send them to the editor
8
- import('@intlayer/unmerged-dictionaries-entry').then((mod) => {
9
- const unmergedDictionaries = mod.getUnmergedDictionaries();
10
- const dictionariesList = Object.fromEntries(Object.values(unmergedDictionaries)
11
- .flat()
12
- .map((dictionary) => [dictionary.localId, dictionary]));
13
- setDictionariesRecord?.(dictionariesList);
14
- });
15
- loaded = true;
16
- }
4
+ const manager = getEditorStateManager();
5
+ const dictionariesRecord = readable(manager.localeDictionaries.value ?? {}, (set) => {
6
+ const handler = (e) => set(e.detail ?? {});
7
+ manager.localeDictionaries.addEventListener('change', handler);
8
+ return () => manager.localeDictionaries.removeEventListener('change', handler);
9
+ });
17
10
  return { dictionariesRecord };
18
11
  };
@@ -1,14 +1,5 @@
1
- import { type Readable, type Writable } from 'svelte/store';
1
+ import { readable } from 'svelte/store';
2
2
  export type EditorEnabledStateProps = {
3
- settingEnabled: Writable<boolean>;
4
- wrapperEnabled: Writable<boolean>;
5
- isInIframe: Writable<boolean>;
6
- enabled: Readable<boolean>;
7
- };
8
- export declare const createEditorEnabledClient: () => {
9
- settingEnabled: Writable<boolean>;
10
- wrapperEnabled: Writable<boolean>;
11
- isInIframe: Writable<boolean>;
12
- enabled: Readable<boolean>;
3
+ enabled: ReturnType<typeof readable<boolean>>;
13
4
  };
14
5
  export declare const useEditorEnabled: () => EditorEnabledStateProps;
@@ -1,41 +1,11 @@
1
- import configuration from '@intlayer/config/built';
2
- import { getContext, setContext } from 'svelte';
3
- import { derived, writable } from 'svelte/store';
4
- const EDITOR_ENABLED_KEY = Symbol('EDITOR_ENABLED');
5
- const detectIframe = () => {
6
- if (typeof window === 'undefined')
7
- return false;
8
- return window.self !== window.top;
9
- };
10
- export const createEditorEnabledClient = () => {
11
- const settingEnabled = writable(configuration.editor.enabled);
12
- const wrapperEnabled = writable(false);
13
- const isInIframe = writable(false);
14
- const enabled = derived([settingEnabled, wrapperEnabled, isInIframe], ([$setting, $wrapper, $iframe]) => $setting && $wrapper && $iframe);
15
- if (typeof window !== 'undefined') {
16
- isInIframe.set(detectIframe());
17
- }
18
- const state = {
19
- settingEnabled,
20
- wrapperEnabled,
21
- isInIframe,
22
- enabled,
23
- };
24
- setContext(EDITOR_ENABLED_KEY, state);
25
- return state;
26
- };
1
+ import { readable } from 'svelte/store';
2
+ import { getEditorStateManager } from './communicator';
27
3
  export const useEditorEnabled = () => {
28
- let context;
29
- // `getContext` must only run inside a component.
30
- // If this is called in plain JS, we catch the error and fallback.
31
- try {
32
- context = getContext(EDITOR_ENABLED_KEY);
33
- }
34
- catch {
35
- // called outside component -> ignore, we’ll use global store
36
- }
37
- if (!context) {
38
- return createEditorEnabledClient();
39
- }
40
- return context;
4
+ const manager = getEditorStateManager();
5
+ const enabled = readable(manager.editorEnabled.value ?? false, (set) => {
6
+ const handler = (e) => set(e.detail);
7
+ manager.editorEnabled.addEventListener('change', handler);
8
+ return () => manager.editorEnabled.removeEventListener('change', handler);
9
+ });
10
+ return { enabled };
41
11
  };
@@ -1,16 +1,10 @@
1
+ import type { FileContent } from '@intlayer/editor';
1
2
  import type { KeyPath } from '@intlayer/types/keyPath';
2
- import type { DictionaryKeys } from '@intlayer/types/module_augmentation';
3
- import type { Writable } from 'svelte/store';
4
- export type FocusedContent = {
5
- dictionaryKey: DictionaryKeys;
6
- keyPath: KeyPath[];
7
- } | null;
3
+ import { readable } from 'svelte/store';
4
+ export type { FileContent };
8
5
  export type FocusDictionaryStateProps = {
9
- focusedContent?: Writable<FocusedContent | undefined>;
10
- setFocusedContent: (content: FocusedContent) => void;
11
- };
12
- export declare const createFocusDictionaryClient: () => {
13
- focusedContent: Writable<FocusedContent | undefined>;
14
- setFocusedContent: (v: FocusedContent | ((prev: FocusedContent | undefined) => FocusedContent)) => void;
6
+ focusedContent: ReturnType<typeof readable<FileContent | null>>;
7
+ setFocusedContent: (content: FileContent | null) => void;
8
+ setFocusedContentKeyPath: (keyPath: KeyPath[]) => void;
15
9
  };
16
10
  export declare const useFocusDictionary: () => FocusDictionaryStateProps;
@@ -1,22 +1,15 @@
1
- import { MessageKey } from '@intlayer/editor';
2
- import { getContext, setContext } from 'svelte';
3
- import { useCrossFrameState } from './useCrossFrameState';
4
- const FOCUS_DICTIONARY_KEY = Symbol('FOCUS_DICTIONARY');
5
- export const createFocusDictionaryClient = () => {
6
- const [focusedContent, setFocusedContent] = useCrossFrameState(MessageKey.INTLAYER_FOCUSED_CONTENT_CHANGED, null);
7
- setContext(FOCUS_DICTIONARY_KEY, { focusedContent, setFocusedContent });
8
- return { focusedContent, setFocusedContent };
9
- };
1
+ import { readable } from 'svelte/store';
2
+ import { getEditorStateManager } from './communicator';
10
3
  export const useFocusDictionary = () => {
11
- let context;
12
- try {
13
- context = getContext(FOCUS_DICTIONARY_KEY);
14
- }
15
- catch {
16
- // called outside component -> ignore, we’ll use global store
17
- }
18
- if (!context) {
19
- return createFocusDictionaryClient();
20
- }
21
- return context;
4
+ const manager = getEditorStateManager();
5
+ const focusedContent = readable(manager.focusedContent.value ?? null, (set) => {
6
+ const handler = (e) => set(e.detail);
7
+ manager.focusedContent.addEventListener('change', handler);
8
+ return () => manager.focusedContent.removeEventListener('change', handler);
9
+ });
10
+ return {
11
+ focusedContent,
12
+ setFocusedContent: (content) => manager.focusedContent.set(content),
13
+ setFocusedContentKeyPath: (keyPath) => manager.setFocusedContentKeyPath(keyPath),
14
+ };
22
15
  };
@@ -1,6 +1,7 @@
1
1
  export { default as ContentSelector } from './ContentSelector.svelte';
2
2
  export { default as ContentSelectorWrapper } from './ContentSelectorWrapper.svelte';
3
3
  export * from './communicator';
4
+ export * from './dictionariesRecord';
4
5
  export * from './editorEnabled';
5
6
  export * from './focusDictionary';
6
7
  export * from './useCrossFrameMessageListener';
@@ -1,6 +1,7 @@
1
1
  export { default as ContentSelector } from './ContentSelector.svelte';
2
2
  export { default as ContentSelectorWrapper } from './ContentSelectorWrapper.svelte';
3
3
  export * from './communicator';
4
+ export * from './dictionariesRecord';
4
5
  export * from './editorEnabled';
5
6
  export * from './focusDictionary';
6
7
  export * from './useCrossFrameMessageListener';
@@ -1,2 +1,2 @@
1
- import { type MessageKey } from '@intlayer/editor';
1
+ import type { MessageKey } from '@intlayer/editor';
2
2
  export declare const useCrossFrameMessageListener: <S>(key: `${MessageKey}` | `${MessageKey}/post` | `${MessageKey}/get`, onEventTriggered?: (data: S) => void, autoCleanup?: boolean) => (data?: S) => void;
@@ -1,65 +1,17 @@
1
- import { compareUrls } from '@intlayer/editor';
2
1
  import { onDestroy } from 'svelte';
3
- import { get } from 'svelte/store';
4
- import { useCommunicator } from './communicator';
5
- const subscribers = new Map();
6
- let windowListenerAttached = false;
7
- const addSubscriber = (key, cb) => {
8
- let set = subscribers.get(key);
9
- if (!set) {
10
- set = new Set();
11
- subscribers.set(key, set);
12
- }
13
- set.add(cb);
14
- };
15
- const removeSubscriber = (key, cb) => {
16
- const set = subscribers.get(key);
17
- if (!set)
18
- return;
19
- set.delete(cb);
20
- if (set.size === 0)
21
- subscribers.delete(key);
22
- };
23
- const ensureGlobalListener = (allowedOrigins, selfId) => {
24
- if (windowListenerAttached)
25
- return;
26
- if (typeof window === 'undefined')
27
- return;
28
- window.addEventListener('message', (event) => {
29
- const { type, data, senderId } = event.data ?? {};
30
- if (!type)
31
- return;
32
- if (senderId === selfId)
33
- return;
34
- if (!allowedOrigins ||
35
- allowedOrigins.includes('*') ||
36
- allowedOrigins.some((o) => compareUrls(o, event.origin))) {
37
- subscribers.get(type)?.forEach((cb) => {
38
- cb(data);
39
- });
40
- }
41
- });
42
- windowListenerAttached = true;
43
- };
2
+ import { getEditorStateManager } from './communicator';
44
3
  export const useCrossFrameMessageListener = (key, onEventTriggered, autoCleanup = true) => {
45
- const communicatorStore = useCommunicator();
46
- const { allowedOrigins, senderId } = get(communicatorStore);
47
- ensureGlobalListener(allowedOrigins, senderId);
4
+ const manager = getEditorStateManager();
48
5
  if (onEventTriggered) {
49
- const cb = onEventTriggered;
50
- addSubscriber(key, cb);
6
+ const unsub = manager.messenger.subscribe(key, onEventTriggered);
51
7
  if (autoCleanup) {
52
8
  try {
53
- onDestroy(() => removeSubscriber(key, cb));
9
+ onDestroy(unsub);
54
10
  }
55
11
  catch {
56
- // Might be called outside component, manual cleanup needed if needed
12
+ // Outside component context
57
13
  }
58
14
  }
59
15
  }
60
- const postMessageWrapper = (data) => {
61
- const { postMessage, senderId: currentSenderId } = get(communicatorStore);
62
- postMessage({ type: key, data, senderId: currentSenderId }, '*');
63
- };
64
- return postMessageWrapper;
16
+ return (data) => manager.messenger.send(key, data);
65
17
  };
@@ -1,7 +1,7 @@
1
- import type { MessageKey } from '@intlayer/editor';
2
- import { type Writable } from 'svelte/store';
1
+ import { type MessageKey } from '@intlayer/editor';
2
+ import { writable } from 'svelte/store';
3
3
  export type CrossFrameStateOptions = {
4
4
  emit?: boolean;
5
5
  receive?: boolean;
6
6
  };
7
- export declare const useCrossFrameState: <S>(key: `${MessageKey}`, initialState?: S | (() => S), options?: CrossFrameStateOptions) => [Writable<S | undefined>, (v: S | ((prev: S | undefined) => S)) => void];
7
+ export declare const useCrossFrameState: <S>(key: `${MessageKey}`, initialState?: S, options?: CrossFrameStateOptions) => [ReturnType<typeof writable<S | undefined>>, (value: S) => void];
@@ -1,82 +1,30 @@
1
- import { get, writable } from 'svelte/store';
2
- import { useCommunicator } from './communicator';
3
- import { useCrossFrameMessageListener } from './useCrossFrameMessageListener';
4
- const crossFrameStateCache = new Map();
5
- const resolveState = (state, prevState) => {
6
- if (typeof state === 'function') {
7
- return state(prevState);
8
- }
9
- return state;
10
- };
11
- const toSerializable = (obj) => {
12
- if (obj === null || obj === undefined)
13
- return obj;
14
- return JSON.parse(JSON.stringify(obj));
15
- };
1
+ import { CrossFrameStateManager } from '@intlayer/editor';
2
+ import { onDestroy } from 'svelte';
3
+ import { writable } from 'svelte/store';
4
+ import { getEditorStateManager } from './communicator';
16
5
  export const useCrossFrameState = (key, initialState, options = { emit: true, receive: true }) => {
17
- if (crossFrameStateCache.has(key)) {
18
- const { state, setState } = crossFrameStateCache.get(key);
19
- return [state, setState];
20
- }
6
+ const manager = getEditorStateManager();
21
7
  const { emit = true, receive = true } = options;
22
- // Initialize state
23
- const initialValue = resolveState(initialState);
24
- const state = writable(initialValue);
25
- const communicatorStore = useCommunicator();
26
- const broadcastState = (value) => {
27
- const { postMessage, senderId } = get(communicatorStore) ?? {};
28
- if (!emit ||
29
- typeof postMessage !== 'function' ||
30
- typeof value === 'undefined') {
31
- return;
32
- }
33
- postMessage({
34
- type: `${key}/post`,
35
- data: value,
36
- senderId,
37
- }, '*');
38
- };
39
- const setState = (valueOrUpdater) => {
40
- state.update((prev) => {
41
- const next = resolveState(valueOrUpdater, prev);
42
- const serialised = toSerializable(next);
43
- broadcastState(serialised);
44
- return serialised;
8
+ const stateManager = new CrossFrameStateManager(key, manager.messenger, {
9
+ emit,
10
+ receive,
11
+ initialValue: initialState,
12
+ });
13
+ stateManager.start();
14
+ const store = writable(stateManager.value);
15
+ const handler = (e) => store.set(e.detail);
16
+ stateManager.addEventListener('change', handler);
17
+ try {
18
+ onDestroy(() => {
19
+ stateManager.removeEventListener('change', handler);
20
+ stateManager.stop();
45
21
  });
46
- };
47
- const postState = () => {
48
- const { postMessage, senderId } = get(communicatorStore) ?? {};
49
- if (typeof postMessage !== 'function')
50
- return;
51
- postMessage({
52
- type: `${key}/post`,
53
- data: get(state),
54
- senderId,
55
- }, '*');
56
- };
57
- // Emit initial state
58
- broadcastState(initialValue);
59
- // If receiving, ask for state
60
- if (receive && typeof get(state) === 'undefined') {
61
- const { postMessage, senderId } = get(communicatorStore) ?? {};
62
- if (typeof postMessage === 'function') {
63
- postMessage({ type: `${key}/get`, senderId }, '*');
64
- }
65
22
  }
66
- // Listen for updates
67
- const listenerKey = receive ? `${key}/post` : `${key}/ignore`;
68
- useCrossFrameMessageListener(listenerKey, (data) => {
69
- if (receive) {
70
- state.set(data);
71
- }
72
- });
73
- // Listen for requests
74
- const getListenerKey = emit ? `${key}/get` : `${key}/ignore`;
75
- useCrossFrameMessageListener(getListenerKey, (_) => {
76
- if (emit) {
77
- broadcastState(get(state));
78
- }
79
- });
80
- crossFrameStateCache.set(key, { state, setState, postState });
81
- return [state, setState];
23
+ catch {
24
+ // Outside component context
25
+ }
26
+ const setState = (value) => {
27
+ stateManager.set(value);
28
+ };
29
+ return [store, setState];
82
30
  };
@@ -1,46 +1,22 @@
1
- import { MessageKey } from '@intlayer/editor';
2
- import { onDestroy } from 'svelte';
3
- import { get } from 'svelte/store';
4
- import { useDictionariesRecord } from './dictionariesRecord';
5
- import { useEditorEnabled } from './editorEnabled';
6
- import { useCrossFrameMessageListener } from './useCrossFrameMessageListener';
7
- import { useIframeClickMerger } from './useIframeClickInterceptor';
8
- let initialized = false;
9
- let unsubscribeIsInIframe = null;
1
+ import { defineIntlayerElements } from '@intlayer/editor';
2
+ import { onDestroy, onMount } from 'svelte';
3
+ import { createEditorStateManager } from './communicator';
10
4
  export const useEditor = () => {
11
5
  if (typeof window === 'undefined')
12
6
  return;
13
- if (initialized)
14
- return;
15
- initialized = true;
16
- const editorEnabled = useEditorEnabled();
17
- useDictionariesRecord();
18
- useIframeClickMerger();
19
- // Listen for editor enabled
20
- useCrossFrameMessageListener(`${MessageKey.INTLAYER_EDITOR_ENABLED}/post`, (data) => {
21
- editorEnabled.wrapperEnabled.set(data);
22
- }, false);
23
- const getEditorEnabled = useCrossFrameMessageListener(`${MessageKey.INTLAYER_EDITOR_ENABLED}/get`, (data) => {
24
- editorEnabled.wrapperEnabled.set(data);
25
- }, false);
26
- // Initial check
27
- const checkEnabled = () => {
28
- const inIframe = get(editorEnabled.isInIframe);
29
- const setting = get(editorEnabled.settingEnabled);
30
- if (inIframe && setting) {
31
- getEditorEnabled();
32
- }
33
- };
34
- checkEnabled();
35
- // Subscribe to changes
36
- editorEnabled.isInIframe.subscribe(() => checkEnabled());
37
- // Keep track of unsubscribe so we *could* clean up if we ever wanted
38
- unsubscribeIsInIframe = editorEnabled.isInIframe.subscribe(() => checkEnabled());
39
- onDestroy(() => {
40
- initialized = false;
41
- if (unsubscribeIsInIframe) {
42
- unsubscribeIsInIframe();
43
- unsubscribeIsInIframe = null;
44
- }
45
- });
7
+ const manager = createEditorStateManager();
8
+ try {
9
+ onMount(() => {
10
+ defineIntlayerElements();
11
+ manager.start();
12
+ });
13
+ onDestroy(() => {
14
+ manager.stop();
15
+ });
16
+ }
17
+ catch {
18
+ // Outside component context - start immediately
19
+ defineIntlayerElements();
20
+ manager.start();
21
+ }
46
22
  };
@@ -1,29 +1,8 @@
1
1
  import { MessageKey, mergeIframeClick } from '@intlayer/editor';
2
- import { onDestroy, onMount } from 'svelte';
3
2
  import { useCrossFrameMessageListener } from './useCrossFrameMessageListener';
4
3
  export const useIframeClickInterceptor = () => {
5
- const postMessage = useCrossFrameMessageListener(MessageKey.INTLAYER_IFRAME_CLICKED);
6
- const handler = () => {
7
- postMessage();
8
- };
9
- onMount(() => {
10
- if (typeof window !== 'undefined') {
11
- window.addEventListener('mousedown', handler);
12
- }
13
- });
14
- onDestroy(() => {
15
- if (typeof window !== 'undefined') {
16
- window.removeEventListener('mousedown', handler);
17
- }
18
- });
4
+ useCrossFrameMessageListener(MessageKey.INTLAYER_IFRAME_CLICKED);
19
5
  };
20
6
  export const useIframeClickMerger = () => {
21
- useIframeClickInterceptor();
22
- useCrossFrameMessageListener(MessageKey.INTLAYER_IFRAME_CLICKED, (data) => {
23
- // mergeIframeClick(data); // mergeIframeClick expects an event, but data might be stripped?
24
- // Actually mergeIframeClick logic in editor package probably dispatches a custom event or similar.
25
- // The Vue implementation passes `mergeIframeClick` directly as the callback.
26
- // Let's assume mergeIframeClick handles whatever data is passed, or we wrap it if needed.
27
- mergeIframeClick(data);
28
- });
7
+ useCrossFrameMessageListener(MessageKey.INTLAYER_IFRAME_CLICKED, mergeIframeClick);
29
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-intlayer",
3
- "version": "8.3.2",
3
+ "version": "8.3.4",
4
4
  "description": "Easily internationalize i18n your Svelte applications with type-safe multilingual content management.",
5
5
  "keywords": [
6
6
  "intlayer",
@@ -72,12 +72,11 @@
72
72
  "typecheck": "tsc --noEmit --project tsconfig.types.json"
73
73
  },
74
74
  "dependencies": {
75
- "@intlayer/api": "8.3.2",
76
- "@intlayer/config": "8.3.2",
77
- "@intlayer/core": "8.3.2",
78
- "@intlayer/editor": "8.3.2",
79
- "@intlayer/types": "8.3.2",
80
- "@intlayer/unmerged-dictionaries-entry": "8.3.2"
75
+ "@intlayer/api": "8.3.4",
76
+ "@intlayer/config": "8.3.4",
77
+ "@intlayer/core": "8.3.4",
78
+ "@intlayer/editor": "8.3.4",
79
+ "@intlayer/types": "8.3.4"
81
80
  },
82
81
  "devDependencies": {
83
82
  "@sveltejs/adapter-auto": "7.0.1",
@@ -90,7 +89,7 @@
90
89
  "rimraf": "6.1.3",
91
90
  "svelte": "5.53.11",
92
91
  "svelte-check": "4.4.5",
93
- "tsdown": "0.21.2",
92
+ "tsdown": "0.21.4",
94
93
  "typescript": "5.9.3",
95
94
  "vite": "8.0.0",
96
95
  "vitest": "4.1.0"