@memberjunction/ng-react 5.22.0 → 5.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,145 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
5
+ // Minimal mock for AngularAdapterService
6
+ function createMockAdapter(options) {
7
+ const { initializeShouldFail = false, createRootAvailable = true } = options ?? {};
8
+ return {
9
+ initialize: initializeShouldFail
10
+ ? vi.fn().mockRejectedValue(new Error('Failed to load React from all CDN sources'))
11
+ : vi.fn().mockResolvedValue(undefined),
12
+ getRuntimeContext: vi.fn().mockReturnValue({
13
+ ReactDOM: createRootAvailable
14
+ ? { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) }
15
+ : undefined,
16
+ }),
17
+ isInitialized: vi.fn().mockReturnValue(!initializeShouldFail && createRootAvailable),
18
+ destroy: vi.fn(),
19
+ };
20
+ }
21
+ // Mock external dependencies
22
+ vi.mock('@memberjunction/react-runtime', () => ({
23
+ reactRootManager: {
24
+ RegisterHook: vi.fn(),
25
+ cleanup: vi.fn(),
26
+ },
27
+ }));
28
+ vi.mock('../lib/config/react-debug.config', () => ({
29
+ ReactDebugConfig: {
30
+ getDebugMode: () => false,
31
+ },
32
+ }));
33
+ vi.mock('../lib/hooks/antd-dropdown-position-hook', () => ({
34
+ createAntdDropdownPositionHook: vi.fn().mockReturnValue({}),
35
+ }));
36
+ import { ReactBridgeService } from '../lib/services/react-bridge.service';
37
+ /** Construct a ReactBridgeService with a mocked adapter. */
38
+ function createService(adapter) {
39
+ vi.spyOn(console, 'log').mockImplementation(() => { });
40
+ vi.spyOn(console, 'warn').mockImplementation(() => { });
41
+ vi.spyOn(console, 'error').mockImplementation(() => { });
42
+ const service = new ReactBridgeService(adapter);
43
+ return service;
44
+ }
45
+ /** Shorten timeouts so tests run fast. */
46
+ function setFastTimeouts(service, overrides) {
47
+ const s = service;
48
+ s.maxWaitTime = overrides?.maxWaitTime ?? 150;
49
+ s.checkInterval = overrides?.checkInterval ?? 30;
50
+ s.maxBootstrapRetries = overrides?.maxBootstrapRetries ?? 2;
51
+ s.retryBaseDelay = overrides?.retryBaseDelay ?? 50;
52
+ }
53
+ describe('ReactBridgeService', () => {
54
+ beforeEach(() => {
55
+ vi.restoreAllMocks();
56
+ });
57
+ describe('bootstrapWithRetries (via getReactContext)', () => {
58
+ it('should resolve on first attempt when CDN is reachable', async () => {
59
+ const adapter = createMockAdapter();
60
+ const service = createService(adapter);
61
+ setFastTimeouts(service);
62
+ const context = await service.getReactContext();
63
+ expect(context.ReactDOM).toBeDefined();
64
+ expect(adapter.initialize).toHaveBeenCalled();
65
+ });
66
+ it('should retry and succeed when CDN recovers after initial failure', async () => {
67
+ const adapter = createMockAdapter({ initializeShouldFail: true });
68
+ const service = createService(adapter);
69
+ // Use 0 retries so the constructor's bootstrap fails fast
70
+ setFastTimeouts(service, { maxBootstrapRetries: 0 });
71
+ // Wait for constructor's bootstrap to fail (sets bootstrapPromise to null)
72
+ try {
73
+ await service.bootstrapPromise;
74
+ }
75
+ catch {
76
+ // Expected — CDN was "down"
77
+ }
78
+ // CDN recovers
79
+ adapter.initialize.mockResolvedValue(undefined);
80
+ adapter.getRuntimeContext.mockReturnValue({
81
+ ReactDOM: { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) },
82
+ });
83
+ // Restore retries and call getReactContext — should create a fresh bootstrap and succeed
84
+ setFastTimeouts(service, { maxBootstrapRetries: 2 });
85
+ const context = await service.getReactContext();
86
+ expect(context.ReactDOM).toBeDefined();
87
+ });
88
+ it('should throw after all bootstrap retries are exhausted', async () => {
89
+ const adapter = createMockAdapter({ initializeShouldFail: true });
90
+ const service = createService(adapter);
91
+ setFastTimeouts(service, { maxBootstrapRetries: 1 });
92
+ await expect(service.getReactContext()).rejects.toThrow(/CDN/);
93
+ });
94
+ });
95
+ describe('waitForReactReady', () => {
96
+ it('should resolve immediately when React is already available', async () => {
97
+ const adapter = createMockAdapter();
98
+ const service = createService(adapter);
99
+ setFastTimeouts(service);
100
+ // Await bootstrap first (same as component flow)
101
+ await service.getReactContext();
102
+ await expect(service.waitForReactReady()).resolves.toBeUndefined();
103
+ expect(service.isReady()).toBe(true);
104
+ });
105
+ it('should resolve after re-bootstrap when createRoot is initially broken (stale cache)', async () => {
106
+ // Bootstrap succeeds (scripts loaded) but createRoot not available (corrupt cache)
107
+ const adapter = createMockAdapter({ createRootAvailable: false });
108
+ const service = createService(adapter);
109
+ setFastTimeouts(service);
110
+ // Wait for initial bootstrap to complete
111
+ await service.getReactContext().catch(() => { });
112
+ // On re-bootstrap (triggered by createRoot poll failure), fix createRoot
113
+ let destroyCount = 0;
114
+ adapter.destroy.mockImplementation(() => {
115
+ destroyCount++;
116
+ // After destroy + re-init, make createRoot available
117
+ adapter.getRuntimeContext.mockReturnValue({
118
+ ReactDOM: { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) },
119
+ });
120
+ adapter.initialize.mockResolvedValue(undefined);
121
+ });
122
+ await expect(service.waitForReactReady()).resolves.toBeUndefined();
123
+ expect(service.isReady()).toBe(true);
124
+ expect(destroyCount).toBeGreaterThanOrEqual(1);
125
+ });
126
+ it('should throw with hard-refresh suggestion after all attempts fail', async () => {
127
+ const adapter = createMockAdapter({ createRootAvailable: false });
128
+ const service = createService(adapter);
129
+ setFastTimeouts(service, { maxBootstrapRetries: 0 });
130
+ await service.getReactContext().catch(() => { });
131
+ await expect(service.waitForReactReady()).rejects.toThrow(/hard refresh/);
132
+ });
133
+ it('should allow subsequent components to resolve via reactReady$ after first succeeds', async () => {
134
+ const adapter = createMockAdapter();
135
+ const service = createService(adapter);
136
+ setFastTimeouts(service);
137
+ await service.getReactContext();
138
+ await service.waitForReactReady();
139
+ // Second call resolves immediately via BehaviorSubject
140
+ await expect(service.waitForReactReady()).resolves.toBeUndefined();
141
+ expect(service.isReady()).toBe(true);
142
+ });
143
+ });
144
+ });
145
+ //# sourceMappingURL=react-bridge.service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-bridge.service.test.js","sourceRoot":"","sources":["../../src/__tests__/react-bridge.service.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @vitest-environment jsdom\n */\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\n\n// Minimal mock for AngularAdapterService\nfunction createMockAdapter(options?: {\n initializeShouldFail?: boolean;\n createRootAvailable?: boolean;\n}) {\n const { initializeShouldFail = false, createRootAvailable = true } = options ?? {};\n\n return {\n initialize: initializeShouldFail\n ? vi.fn().mockRejectedValue(new Error('Failed to load React from all CDN sources'))\n : vi.fn().mockResolvedValue(undefined),\n getRuntimeContext: vi.fn().mockReturnValue({\n ReactDOM: createRootAvailable\n ? { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) }\n : undefined,\n }),\n isInitialized: vi.fn().mockReturnValue(!initializeShouldFail && createRootAvailable),\n destroy: vi.fn(),\n };\n}\n\n// Mock external dependencies\nvi.mock('@memberjunction/react-runtime', () => ({\n reactRootManager: {\n RegisterHook: vi.fn(),\n cleanup: vi.fn(),\n },\n}));\n\nvi.mock('../lib/config/react-debug.config', () => ({\n ReactDebugConfig: {\n getDebugMode: () => false,\n },\n}));\n\nvi.mock('../lib/hooks/antd-dropdown-position-hook', () => ({\n createAntdDropdownPositionHook: vi.fn().mockReturnValue({}),\n}));\n\nimport { ReactBridgeService } from '../lib/services/react-bridge.service';\n\n/** Construct a ReactBridgeService with a mocked adapter. */\nfunction createService(adapter: ReturnType<typeof createMockAdapter>): ReactBridgeService {\n vi.spyOn(console, 'log').mockImplementation(() => {});\n vi.spyOn(console, 'warn').mockImplementation(() => {});\n vi.spyOn(console, 'error').mockImplementation(() => {});\n\n const service = new (ReactBridgeService as unknown as new (a: unknown) => ReactBridgeService)(adapter);\n\n return service;\n}\n\n/** Shorten timeouts so tests run fast. */\nfunction setFastTimeouts(\n service: ReactBridgeService,\n overrides?: { maxWaitTime?: number; checkInterval?: number; maxBootstrapRetries?: number; retryBaseDelay?: number }\n) {\n const s = service as unknown as Record<string, number>;\n s.maxWaitTime = overrides?.maxWaitTime ?? 150;\n s.checkInterval = overrides?.checkInterval ?? 30;\n s.maxBootstrapRetries = overrides?.maxBootstrapRetries ?? 2;\n s.retryBaseDelay = overrides?.retryBaseDelay ?? 50;\n}\n\ndescribe('ReactBridgeService', () => {\n beforeEach(() => {\n vi.restoreAllMocks();\n });\n\n describe('bootstrapWithRetries (via getReactContext)', () => {\n it('should resolve on first attempt when CDN is reachable', async () => {\n const adapter = createMockAdapter();\n const service = createService(adapter);\n setFastTimeouts(service);\n\n const context = await service.getReactContext();\n expect(context.ReactDOM).toBeDefined();\n expect(adapter.initialize).toHaveBeenCalled();\n });\n\n it('should retry and succeed when CDN recovers after initial failure', async () => {\n const adapter = createMockAdapter({ initializeShouldFail: true });\n const service = createService(adapter);\n // Use 0 retries so the constructor's bootstrap fails fast\n setFastTimeouts(service, { maxBootstrapRetries: 0 });\n\n // Wait for constructor's bootstrap to fail (sets bootstrapPromise to null)\n try {\n await (service as unknown as { bootstrapPromise: Promise<void> | null }).bootstrapPromise;\n } catch {\n // Expected — CDN was \"down\"\n }\n\n // CDN recovers\n adapter.initialize.mockResolvedValue(undefined);\n adapter.getRuntimeContext.mockReturnValue({\n ReactDOM: { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) },\n });\n\n // Restore retries and call getReactContext — should create a fresh bootstrap and succeed\n setFastTimeouts(service, { maxBootstrapRetries: 2 });\n const context = await service.getReactContext();\n expect(context.ReactDOM).toBeDefined();\n });\n\n it('should throw after all bootstrap retries are exhausted', async () => {\n const adapter = createMockAdapter({ initializeShouldFail: true });\n const service = createService(adapter);\n setFastTimeouts(service, { maxBootstrapRetries: 1 });\n\n await expect(service.getReactContext()).rejects.toThrow(/CDN/);\n });\n });\n\n describe('waitForReactReady', () => {\n it('should resolve immediately when React is already available', async () => {\n const adapter = createMockAdapter();\n const service = createService(adapter);\n setFastTimeouts(service);\n\n // Await bootstrap first (same as component flow)\n await service.getReactContext();\n await expect(service.waitForReactReady()).resolves.toBeUndefined();\n expect(service.isReady()).toBe(true);\n });\n\n it('should resolve after re-bootstrap when createRoot is initially broken (stale cache)', async () => {\n // Bootstrap succeeds (scripts loaded) but createRoot not available (corrupt cache)\n const adapter = createMockAdapter({ createRootAvailable: false });\n const service = createService(adapter);\n setFastTimeouts(service);\n\n // Wait for initial bootstrap to complete\n await service.getReactContext().catch(() => {});\n\n // On re-bootstrap (triggered by createRoot poll failure), fix createRoot\n let destroyCount = 0;\n adapter.destroy.mockImplementation(() => {\n destroyCount++;\n // After destroy + re-init, make createRoot available\n adapter.getRuntimeContext.mockReturnValue({\n ReactDOM: { createRoot: vi.fn().mockReturnValue({ unmount: vi.fn() }) },\n });\n adapter.initialize.mockResolvedValue(undefined);\n });\n\n await expect(service.waitForReactReady()).resolves.toBeUndefined();\n expect(service.isReady()).toBe(true);\n expect(destroyCount).toBeGreaterThanOrEqual(1);\n });\n\n it('should throw with hard-refresh suggestion after all attempts fail', async () => {\n const adapter = createMockAdapter({ createRootAvailable: false });\n const service = createService(adapter);\n setFastTimeouts(service, { maxBootstrapRetries: 0 });\n\n await service.getReactContext().catch(() => {});\n\n await expect(service.waitForReactReady()).rejects.toThrow(/hard refresh/);\n });\n\n it('should allow subsequent components to resolve via reactReady$ after first succeeds', async () => {\n const adapter = createMockAdapter();\n const service = createService(adapter);\n setFastTimeouts(service);\n\n await service.getReactContext();\n await service.waitForReactReady();\n\n // Second call resolves immediately via BehaviorSubject\n await expect(service.waitForReactReady()).resolves.toBeUndefined();\n expect(service.isReady()).toBe(true);\n });\n });\n});\n"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @fileoverview Runtime hook that fixes antd dropdown positioning
3
+ * when running inside Angular's CSS transform context.
4
+ *
5
+ * antd's popup positioning (via rc-trigger / dom-align) calculates wrong
6
+ * coordinates inside Angular because Angular applies CSS transforms that
7
+ * break getBoundingClientRect-based positioning. This hook:
8
+ *
9
+ * 1. Injects a CSS rule using !important with CSS custom properties to
10
+ * override antd's inline left/top values. The fallback (-9999px) hides
11
+ * dropdowns until positioned, preventing flash-of-wrong-position.
12
+ *
13
+ * 2. Creates a MutationObserver that catches dropdown elements when
14
+ * added to the DOM and sets the CSS variables to correct viewport-
15
+ * relative coordinates based on the trigger element's position.
16
+ *
17
+ * This hook is Angular-specific and should only be registered when
18
+ * running inside an Angular host application.
19
+ *
20
+ * @module @memberjunction/ng-react
21
+ */
22
+ import { RuntimeHook } from '@memberjunction/react-runtime';
23
+ /**
24
+ * Creates a RuntimeHook that fixes antd dropdown positioning inside Angular.
25
+ * Uses position:absolute (antd's default) rather than position:fixed to
26
+ * preserve antd's virtual scroll behavior inside dropdown panels.
27
+ */
28
+ export declare function createAntdDropdownPositionHook(): RuntimeHook;
@@ -0,0 +1,81 @@
1
+ const DROPDOWN_SELECTOR = '.ant-select-dropdown, .ant-picker-dropdown, .ant-cascader-dropdown';
2
+ const TRIGGER_SELECTOR = '.ant-select-open, .ant-picker-focused, .ant-dropdown-open';
3
+ /**
4
+ * Creates a RuntimeHook that fixes antd dropdown positioning inside Angular.
5
+ * Uses position:absolute (antd's default) rather than position:fixed to
6
+ * preserve antd's virtual scroll behavior inside dropdown panels.
7
+ */
8
+ export function createAntdDropdownPositionHook() {
9
+ let observer = null;
10
+ let styleElement = null;
11
+ const fixDropdown = (dd) => {
12
+ if (dd.hasAttribute('data-pos-fixed'))
13
+ return;
14
+ const trigger = document.querySelector(TRIGGER_SELECTOR);
15
+ if (!trigger)
16
+ return;
17
+ const tRect = trigger.getBoundingClientRect();
18
+ const op = dd.offsetParent;
19
+ if (op) {
20
+ // Position relative to offset parent (keeps position:absolute working)
21
+ const opRect = op.getBoundingClientRect();
22
+ dd.style.setProperty('--dd-left', `${tRect.left - opRect.left}px`);
23
+ dd.style.setProperty('--dd-top', `${tRect.bottom - opRect.top + 2}px`);
24
+ }
25
+ else {
26
+ // No offset parent — fall back to viewport coordinates
27
+ dd.style.setProperty('--dd-left', `${tRect.left}px`);
28
+ dd.style.setProperty('--dd-top', `${tRect.bottom + 2}px`);
29
+ }
30
+ dd.setAttribute('data-pos-fixed', '1');
31
+ };
32
+ return {
33
+ name: 'antd-dropdown-position-fix',
34
+ OnFirstRootCreated(_context) {
35
+ if (typeof document === 'undefined')
36
+ return;
37
+ // CSS !important overrides antd's inline left/top permanently.
38
+ // CSS custom properties (--dd-left, --dd-top) are set per-element from JS.
39
+ // Fallback of -9999px hides dropdowns until the observer positions them.
40
+ styleElement = document.createElement('style');
41
+ styleElement.textContent =
42
+ DROPDOWN_SELECTOR +
43
+ ' { left: var(--dd-left, -9999px) !important; top: var(--dd-top, -9999px) !important; z-index: 99999 !important; }';
44
+ document.head.appendChild(styleElement);
45
+ // Watch for dropdown elements being added to the DOM.
46
+ // Only watches childList (not attributes) to avoid firing during scroll/hover.
47
+ observer = new MutationObserver((mutations) => {
48
+ for (const m of mutations) {
49
+ const addedNodes = Array.from(m.addedNodes);
50
+ for (const node of addedNodes) {
51
+ if (node.nodeType !== 1)
52
+ continue;
53
+ const el = node;
54
+ // Check if the added node itself is a dropdown
55
+ if (el.className && typeof el.className === 'string' &&
56
+ /\bant-(select|picker|cascader)-dropdown\b/.test(el.className)) {
57
+ fixDropdown(el);
58
+ }
59
+ // Check descendants (dropdown may be nested inside a wrapper)
60
+ if (el.querySelectorAll) {
61
+ const nested = el.querySelectorAll(DROPDOWN_SELECTOR);
62
+ nested.forEach((n) => fixDropdown(n));
63
+ }
64
+ }
65
+ }
66
+ });
67
+ observer.observe(document.body, { childList: true, subtree: true });
68
+ },
69
+ OnCleanup() {
70
+ if (observer) {
71
+ observer.disconnect();
72
+ observer = null;
73
+ }
74
+ if (styleElement && styleElement.parentNode) {
75
+ styleElement.parentNode.removeChild(styleElement);
76
+ styleElement = null;
77
+ }
78
+ }
79
+ };
80
+ }
81
+ //# sourceMappingURL=antd-dropdown-position-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"antd-dropdown-position-hook.js","sourceRoot":"","sources":["../../../src/lib/hooks/antd-dropdown-position-hook.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Runtime hook that fixes antd dropdown positioning\n * when running inside Angular's CSS transform context.\n *\n * antd's popup positioning (via rc-trigger / dom-align) calculates wrong\n * coordinates inside Angular because Angular applies CSS transforms that\n * break getBoundingClientRect-based positioning. This hook:\n *\n * 1. Injects a CSS rule using !important with CSS custom properties to\n * override antd's inline left/top values. The fallback (-9999px) hides\n * dropdowns until positioned, preventing flash-of-wrong-position.\n *\n * 2. Creates a MutationObserver that catches dropdown elements when\n * added to the DOM and sets the CSS variables to correct viewport-\n * relative coordinates based on the trigger element's position.\n *\n * This hook is Angular-specific and should only be registered when\n * running inside an Angular host application.\n *\n * @module @memberjunction/ng-react\n */\nimport { RuntimeHook, RootHookContext } from '@memberjunction/react-runtime';\n\nconst DROPDOWN_SELECTOR = '.ant-select-dropdown, .ant-picker-dropdown, .ant-cascader-dropdown';\nconst TRIGGER_SELECTOR = '.ant-select-open, .ant-picker-focused, .ant-dropdown-open';\n\n/**\n * Creates a RuntimeHook that fixes antd dropdown positioning inside Angular.\n * Uses position:absolute (antd's default) rather than position:fixed to\n * preserve antd's virtual scroll behavior inside dropdown panels.\n */\nexport function createAntdDropdownPositionHook(): RuntimeHook {\n let observer: MutationObserver | null = null;\n let styleElement: HTMLStyleElement | null = null;\n\n const fixDropdown = (dd: HTMLElement): void => {\n if (dd.hasAttribute('data-pos-fixed')) return;\n const trigger = document.querySelector(TRIGGER_SELECTOR);\n if (!trigger) return;\n\n const tRect = trigger.getBoundingClientRect();\n const op = dd.offsetParent as HTMLElement | null;\n\n if (op) {\n // Position relative to offset parent (keeps position:absolute working)\n const opRect = op.getBoundingClientRect();\n dd.style.setProperty('--dd-left', `${tRect.left - opRect.left}px`);\n dd.style.setProperty('--dd-top', `${tRect.bottom - opRect.top + 2}px`);\n } else {\n // No offset parent — fall back to viewport coordinates\n dd.style.setProperty('--dd-left', `${tRect.left}px`);\n dd.style.setProperty('--dd-top', `${tRect.bottom + 2}px`);\n }\n dd.setAttribute('data-pos-fixed', '1');\n };\n\n return {\n name: 'antd-dropdown-position-fix',\n\n OnFirstRootCreated(_context: RootHookContext): void {\n if (typeof document === 'undefined') return;\n\n // CSS !important overrides antd's inline left/top permanently.\n // CSS custom properties (--dd-left, --dd-top) are set per-element from JS.\n // Fallback of -9999px hides dropdowns until the observer positions them.\n styleElement = document.createElement('style');\n styleElement.textContent =\n DROPDOWN_SELECTOR +\n ' { left: var(--dd-left, -9999px) !important; top: var(--dd-top, -9999px) !important; z-index: 99999 !important; }';\n document.head.appendChild(styleElement);\n\n // Watch for dropdown elements being added to the DOM.\n // Only watches childList (not attributes) to avoid firing during scroll/hover.\n observer = new MutationObserver((mutations) => {\n for (const m of mutations) {\n const addedNodes = Array.from(m.addedNodes);\n for (const node of addedNodes) {\n if (node.nodeType !== 1) continue;\n const el = node as HTMLElement;\n\n // Check if the added node itself is a dropdown\n if (el.className && typeof el.className === 'string' &&\n /\\bant-(select|picker|cascader)-dropdown\\b/.test(el.className)) {\n fixDropdown(el);\n }\n\n // Check descendants (dropdown may be nested inside a wrapper)\n if (el.querySelectorAll) {\n const nested = el.querySelectorAll(DROPDOWN_SELECTOR);\n nested.forEach((n) => fixDropdown(n as HTMLElement));\n }\n }\n }\n });\n\n observer.observe(document.body, { childList: true, subtree: true });\n },\n\n OnCleanup(): void {\n if (observer) {\n observer.disconnect();\n observer = null;\n }\n if (styleElement && styleElement.parentNode) {\n styleElement.parentNode.removeChild(styleElement);\n styleElement = null;\n }\n }\n };\n}\n"]}
@@ -11,6 +11,23 @@ export declare class AngularAdapterService {
11
11
  private runtimeContext?;
12
12
  private initializationPromise;
13
13
  constructor(scriptLoader: ScriptLoaderService);
14
+ /**
15
+ * Eagerly start loading the React runtime in the background.
16
+ * Call this at app startup (e.g., in APP_INITIALIZER or after auth) so that
17
+ * React, ReactDOM, and Babel are already downloaded from CDN by the time the
18
+ * user opens an interactive component artifact.
19
+ *
20
+ * Two-phase approach:
21
+ * 1. Immediately inject `<link rel="preload">` hints so the browser starts
22
+ * downloading the CDN scripts in parallel with other page work.
23
+ * 2. Fire-and-forget `initialize()` which creates `<script>` tags and
24
+ * executes them. If the preload hints already fetched the bytes, the
25
+ * script load is nearly instant (served from HTTP cache).
26
+ *
27
+ * Safe to call multiple times — the underlying initialize() deduplicates.
28
+ * Does not block: returns immediately, initialization continues in background.
29
+ */
30
+ preload(): void;
14
31
  /**
15
32
  * Initialize the React runtime with Angular-specific configuration
16
33
  * @param config Optional library configuration
@@ -4,7 +4,7 @@
4
4
  * @module @memberjunction/ng-react
5
5
  */
6
6
  import { Injectable } from '@angular/core';
7
- import { createReactRuntime, SetupStyles } from '@memberjunction/react-runtime';
7
+ import { createReactRuntime, LibraryLoader, SetupStyles } from '@memberjunction/react-runtime';
8
8
  import { ScriptLoaderService } from './script-loader.service';
9
9
  import * as i0 from "@angular/core";
10
10
  import * as i1 from "./script-loader.service";
@@ -16,6 +16,30 @@ export class AngularAdapterService {
16
16
  constructor(scriptLoader) {
17
17
  this.scriptLoader = scriptLoader;
18
18
  }
19
+ /**
20
+ * Eagerly start loading the React runtime in the background.
21
+ * Call this at app startup (e.g., in APP_INITIALIZER or after auth) so that
22
+ * React, ReactDOM, and Babel are already downloaded from CDN by the time the
23
+ * user opens an interactive component artifact.
24
+ *
25
+ * Two-phase approach:
26
+ * 1. Immediately inject `<link rel="preload">` hints so the browser starts
27
+ * downloading the CDN scripts in parallel with other page work.
28
+ * 2. Fire-and-forget `initialize()` which creates `<script>` tags and
29
+ * executes them. If the preload hints already fetched the bytes, the
30
+ * script load is nearly instant (served from HTTP cache).
31
+ *
32
+ * Safe to call multiple times — the underlying initialize() deduplicates.
33
+ * Does not block: returns immediately, initialization continues in background.
34
+ */
35
+ preload() {
36
+ // Phase 1: Inject browser preload hints for CDN scripts
37
+ LibraryLoader.preloadCoreScripts();
38
+ // Phase 2: Fire-and-forget full initialization (script execution + runtime setup)
39
+ this.initialize().catch(err => {
40
+ console.warn('React runtime preload failed (will retry on demand):', err);
41
+ });
42
+ }
19
43
  /**
20
44
  * Initialize the React runtime with Angular-specific configuration
21
45
  * @param config Optional library configuration
@@ -1 +1 @@
1
- {"version":3,"file":"angular-adapter.service.js","sourceRoot":"","sources":["../../../src/lib/services/angular-adapter.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Angular adapter service that bridges the React runtime with Angular.\n * Provides Angular-specific functionality for the platform-agnostic React runtime.\n * @module @memberjunction/ng-react\n */\n\nimport { Injectable } from '@angular/core';\nimport { \n ComponentCompiler,\n ComponentRegistry,\n ComponentResolver,\n ComponentManager,\n createReactRuntime,\n CompileOptions,\n RuntimeContext,\n ExternalLibraryConfig,\n LibraryConfiguration,\n SetupStyles\n} from '@memberjunction/react-runtime';\nimport { ScriptLoaderService } from './script-loader.service';\nimport { ComponentStyles } from '@memberjunction/interactive-component-types';\n\n/**\n * Angular-specific adapter for the React runtime.\n * Manages the integration between Angular services and the platform-agnostic React runtime.\n */\n@Injectable({ providedIn: 'root' })\nexport class AngularAdapterService {\n private runtime?: {\n compiler: ComponentCompiler;\n registry: ComponentRegistry;\n resolver: ComponentResolver;\n manager: ComponentManager;\n version: string;\n };\n private runtimeContext?: RuntimeContext;\n private initializationPromise: Promise<void> | undefined;\n\n constructor(private scriptLoader: ScriptLoaderService) {}\n\n /**\n * Initialize the React runtime with Angular-specific configuration\n * @param config Optional library configuration\n * @param additionalLibraries Optional additional libraries to merge\n * @param options Optional options including debug flag\n * @returns Promise resolving when runtime is ready\n */\n async initialize(\n config?: LibraryConfiguration,\n additionalLibraries?: ExternalLibraryConfig[],\n options?: { debug?: boolean }\n ): Promise<void> {\n if (this.runtime) {\n return; // Already initialized\n }\n if (this.initializationPromise) {\n return this.initializationPromise; // in progress\n }\n\n // Start initialization and store the promise immediately\n this.initializationPromise = this.doInitialize(config, additionalLibraries, options);\n\n try {\n await this.initializationPromise;\n } catch (error) {\n // Clear the promise on error so it can be retried\n this.initializationPromise = undefined;\n throw error;\n }\n\n return;\n }\n\n private async doInitialize(\n config?: LibraryConfiguration,\n additionalLibraries?: ExternalLibraryConfig[],\n options?: { debug?: boolean }\n ): Promise<void> {\n // Load React ecosystem with optional additional libraries\n const ecosystem = await this.scriptLoader.loadReactEcosystem(config, additionalLibraries, options);\n \n // Create runtime context\n this.runtimeContext = {\n React: ecosystem.React,\n ReactDOM: ecosystem.ReactDOM,\n libraries: ecosystem.libraries,\n utilities: {\n // Add any Angular-specific utilities here\n }\n };\n\n // Create the React runtime with runtime context for registry support\n this.runtime = createReactRuntime(ecosystem.Babel, {\n compiler: {\n cache: true,\n maxCacheSize: 100,\n debug: options?.debug\n },\n registry: {\n maxComponents: 1000,\n cleanupInterval: 60000,\n useLRU: true,\n enableNamespaces: true,\n debug: options?.debug\n }\n }, this.runtimeContext, options?.debug);\n }\n\n /**\n * Get the component compiler\n * @returns Component compiler instance\n */\n getCompiler(): ComponentCompiler {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.compiler;\n }\n\n /**\n * Get the component registry\n * @returns Component registry instance\n */\n getRegistry(): ComponentRegistry {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.registry;\n }\n\n /**\n * Get the component resolver\n * @returns Component resolver instance\n */\n getResolver(): ComponentResolver {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.resolver;\n }\n\n /**\n * Get the runtime context\n * @returns Runtime context with React and libraries\n */\n getRuntimeContext(): RuntimeContext {\n if (!this.runtimeContext) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtimeContext;\n }\n\n /**\n * Get the unified component manager\n * @returns Component manager instance\n */\n getComponentManager(): ComponentManager {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.manager;\n }\n\n\n /**\n * Compile a component with Angular-specific defaults\n * @param options - Compilation options\n * @returns Promise resolving to compilation result\n */\n async compileComponent(options: CompileOptions) {\n // Validate options before initialization\n if (!options) {\n throw new Error(\n 'Angular adapter error: No compilation options provided.\\n' +\n 'This usually means the component spec is null or undefined.\\n' +\n 'Please check that:\\n' +\n '1. Your component data is loaded properly\\n' +\n '2. The component spec has \"name\" and \"code\" properties\\n' +\n '3. The component input is not undefined'\n );\n }\n\n if (!options.componentName || options.componentName.trim() === '') {\n throw new Error(\n 'Angular adapter error: Component name is missing or empty.\\n' +\n `Received options: ${JSON.stringify(options, null, 2)}\\n` +\n 'Make sure your component spec includes a \"name\" property.'\n );\n }\n\n if (!options.componentCode || options.componentCode.trim() === '') {\n throw new Error(\n `Angular adapter error: Component code is missing or empty for component \"${options.componentName}\".\\n` +\n 'Make sure your component spec includes a \"code\" property with the React component source.'\n );\n }\n\n await this.initialize();\n \n // Apply default styles if not provided\n const optionsWithDefaults = {\n ...options,\n styles: options.styles || SetupStyles()\n };\n\n return this.runtime!.compiler.compile(optionsWithDefaults);\n }\n\n /**\n * Register a component in the registry\n * @param name - Component name\n * @param component - Compiled component\n * @param namespace - Component namespace\n * @param version - Component version\n * @returns Component metadata\n */\n registerComponent(\n name: string,\n component: any,\n namespace: string = 'Global',\n version: string = 'v1'\n ) {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.registry.register(name, component, namespace, version);\n }\n\n /**\n * Get a component from the registry\n * @param name - Component name\n * @param namespace - Component namespace\n * @param version - Component version\n * @returns Component if found\n */\n getComponent(name: string, namespace: string = 'Global', version?: string) {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.registry.get(name, namespace, version);\n }\n\n /**\n * Check if runtime is initialized\n * @returns true if initialized\n */\n isInitialized(): boolean {\n return !!this.runtime && !!this.runtimeContext;\n }\n\n /**\n * Get runtime version\n * @returns Runtime version string\n */\n getVersion(): string {\n return this.runtime?.version || 'unknown';\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n if (this.runtime) {\n this.runtime.registry.destroy();\n this.runtime = undefined;\n this.runtimeContext = undefined;\n }\n }\n\n /**\n * Get Babel instance for direct use\n * @returns Babel instance\n */\n getBabel(): any {\n return this.runtimeContext?.libraries?.Babel || (window as any).Babel;\n }\n\n /**\n * Transpile JSX code directly\n * @param code - JSX code to transpile\n * @param filename - Optional filename for better error messages\n * @returns Transpiled JavaScript code\n */\n transpileJSX(code: string, filename?: string): string {\n const babel = this.getBabel();\n if (!babel) {\n throw new Error('Babel not loaded. Initialize the runtime first.');\n }\n\n try {\n const result = babel.transform(code, {\n presets: ['react'],\n filename: filename || 'component.jsx'\n });\n return result.code;\n } catch (error: any) {\n throw new Error(`Failed to transpile JSX: ${error.message}`);\n }\n }\n}"]}
1
+ {"version":3,"file":"angular-adapter.service.js","sourceRoot":"","sources":["../../../src/lib/services/angular-adapter.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Angular adapter service that bridges the React runtime with Angular.\n * Provides Angular-specific functionality for the platform-agnostic React runtime.\n * @module @memberjunction/ng-react\n */\n\nimport { Injectable } from '@angular/core';\nimport {\n ComponentCompiler,\n ComponentRegistry,\n ComponentResolver,\n ComponentManager,\n createReactRuntime,\n CompileOptions,\n RuntimeContext,\n ExternalLibraryConfig,\n LibraryConfiguration,\n LibraryLoader,\n SetupStyles\n} from '@memberjunction/react-runtime';\nimport { ScriptLoaderService } from './script-loader.service';\nimport { ComponentStyles } from '@memberjunction/interactive-component-types';\n\n/**\n * Angular-specific adapter for the React runtime.\n * Manages the integration between Angular services and the platform-agnostic React runtime.\n */\n@Injectable({ providedIn: 'root' })\nexport class AngularAdapterService {\n private runtime?: {\n compiler: ComponentCompiler;\n registry: ComponentRegistry;\n resolver: ComponentResolver;\n manager: ComponentManager;\n version: string;\n };\n private runtimeContext?: RuntimeContext;\n private initializationPromise: Promise<void> | undefined;\n\n constructor(private scriptLoader: ScriptLoaderService) {}\n\n /**\n * Eagerly start loading the React runtime in the background.\n * Call this at app startup (e.g., in APP_INITIALIZER or after auth) so that\n * React, ReactDOM, and Babel are already downloaded from CDN by the time the\n * user opens an interactive component artifact.\n *\n * Two-phase approach:\n * 1. Immediately inject `<link rel=\"preload\">` hints so the browser starts\n * downloading the CDN scripts in parallel with other page work.\n * 2. Fire-and-forget `initialize()` which creates `<script>` tags and\n * executes them. If the preload hints already fetched the bytes, the\n * script load is nearly instant (served from HTTP cache).\n *\n * Safe to call multiple times — the underlying initialize() deduplicates.\n * Does not block: returns immediately, initialization continues in background.\n */\n preload(): void {\n // Phase 1: Inject browser preload hints for CDN scripts\n LibraryLoader.preloadCoreScripts();\n\n // Phase 2: Fire-and-forget full initialization (script execution + runtime setup)\n this.initialize().catch(err => {\n console.warn('React runtime preload failed (will retry on demand):', err);\n });\n }\n\n /**\n * Initialize the React runtime with Angular-specific configuration\n * @param config Optional library configuration\n * @param additionalLibraries Optional additional libraries to merge\n * @param options Optional options including debug flag\n * @returns Promise resolving when runtime is ready\n */\n async initialize(\n config?: LibraryConfiguration,\n additionalLibraries?: ExternalLibraryConfig[],\n options?: { debug?: boolean }\n ): Promise<void> {\n if (this.runtime) {\n return; // Already initialized\n }\n if (this.initializationPromise) {\n return this.initializationPromise; // in progress\n }\n\n // Start initialization and store the promise immediately\n this.initializationPromise = this.doInitialize(config, additionalLibraries, options);\n\n try {\n await this.initializationPromise;\n } catch (error) {\n // Clear the promise on error so it can be retried\n this.initializationPromise = undefined;\n throw error;\n }\n\n return;\n }\n\n private async doInitialize(\n config?: LibraryConfiguration,\n additionalLibraries?: ExternalLibraryConfig[],\n options?: { debug?: boolean }\n ): Promise<void> {\n // Load React ecosystem with optional additional libraries\n const ecosystem = await this.scriptLoader.loadReactEcosystem(config, additionalLibraries, options);\n \n // Create runtime context\n this.runtimeContext = {\n React: ecosystem.React,\n ReactDOM: ecosystem.ReactDOM,\n libraries: ecosystem.libraries,\n utilities: {\n // Add any Angular-specific utilities here\n }\n };\n\n // Create the React runtime with runtime context for registry support\n this.runtime = createReactRuntime(ecosystem.Babel, {\n compiler: {\n cache: true,\n maxCacheSize: 100,\n debug: options?.debug\n },\n registry: {\n maxComponents: 1000,\n cleanupInterval: 60000,\n useLRU: true,\n enableNamespaces: true,\n debug: options?.debug\n }\n }, this.runtimeContext, options?.debug);\n }\n\n /**\n * Get the component compiler\n * @returns Component compiler instance\n */\n getCompiler(): ComponentCompiler {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.compiler;\n }\n\n /**\n * Get the component registry\n * @returns Component registry instance\n */\n getRegistry(): ComponentRegistry {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.registry;\n }\n\n /**\n * Get the component resolver\n * @returns Component resolver instance\n */\n getResolver(): ComponentResolver {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.resolver;\n }\n\n /**\n * Get the runtime context\n * @returns Runtime context with React and libraries\n */\n getRuntimeContext(): RuntimeContext {\n if (!this.runtimeContext) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtimeContext;\n }\n\n /**\n * Get the unified component manager\n * @returns Component manager instance\n */\n getComponentManager(): ComponentManager {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.manager;\n }\n\n\n /**\n * Compile a component with Angular-specific defaults\n * @param options - Compilation options\n * @returns Promise resolving to compilation result\n */\n async compileComponent(options: CompileOptions) {\n // Validate options before initialization\n if (!options) {\n throw new Error(\n 'Angular adapter error: No compilation options provided.\\n' +\n 'This usually means the component spec is null or undefined.\\n' +\n 'Please check that:\\n' +\n '1. Your component data is loaded properly\\n' +\n '2. The component spec has \"name\" and \"code\" properties\\n' +\n '3. The component input is not undefined'\n );\n }\n\n if (!options.componentName || options.componentName.trim() === '') {\n throw new Error(\n 'Angular adapter error: Component name is missing or empty.\\n' +\n `Received options: ${JSON.stringify(options, null, 2)}\\n` +\n 'Make sure your component spec includes a \"name\" property.'\n );\n }\n\n if (!options.componentCode || options.componentCode.trim() === '') {\n throw new Error(\n `Angular adapter error: Component code is missing or empty for component \"${options.componentName}\".\\n` +\n 'Make sure your component spec includes a \"code\" property with the React component source.'\n );\n }\n\n await this.initialize();\n \n // Apply default styles if not provided\n const optionsWithDefaults = {\n ...options,\n styles: options.styles || SetupStyles()\n };\n\n return this.runtime!.compiler.compile(optionsWithDefaults);\n }\n\n /**\n * Register a component in the registry\n * @param name - Component name\n * @param component - Compiled component\n * @param namespace - Component namespace\n * @param version - Component version\n * @returns Component metadata\n */\n registerComponent(\n name: string,\n component: any,\n namespace: string = 'Global',\n version: string = 'v1'\n ) {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.registry.register(name, component, namespace, version);\n }\n\n /**\n * Get a component from the registry\n * @param name - Component name\n * @param namespace - Component namespace\n * @param version - Component version\n * @returns Component if found\n */\n getComponent(name: string, namespace: string = 'Global', version?: string) {\n if (!this.runtime) {\n throw new Error('React runtime not initialized. Call initialize() first.');\n }\n return this.runtime.registry.get(name, namespace, version);\n }\n\n /**\n * Check if runtime is initialized\n * @returns true if initialized\n */\n isInitialized(): boolean {\n return !!this.runtime && !!this.runtimeContext;\n }\n\n /**\n * Get runtime version\n * @returns Runtime version string\n */\n getVersion(): string {\n return this.runtime?.version || 'unknown';\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n if (this.runtime) {\n this.runtime.registry.destroy();\n this.runtime = undefined;\n this.runtimeContext = undefined;\n }\n }\n\n /**\n * Get Babel instance for direct use\n * @returns Babel instance\n */\n getBabel(): any {\n return this.runtimeContext?.libraries?.Babel || (window as any).Babel;\n }\n\n /**\n * Transpile JSX code directly\n * @param code - JSX code to transpile\n * @param filename - Optional filename for better error messages\n * @returns Transpiled JavaScript code\n */\n transpileJSX(code: string, filename?: string): string {\n const babel = this.getBabel();\n if (!babel) {\n throw new Error('Babel not loaded. Initialize the runtime first.');\n }\n\n try {\n const result = babel.transform(code, {\n presets: ['react'],\n filename: filename || 'component.jsx'\n });\n return result.code;\n } catch (error: any) {\n throw new Error(`Failed to transpile JSX: ${error.message}`);\n }\n }\n}"]}
@@ -19,19 +19,43 @@ export declare class ReactBridgeService implements OnDestroy {
19
19
  private firstComponentAttempted;
20
20
  private maxWaitTime;
21
21
  private checkInterval;
22
+ private maxBootstrapRetries;
23
+ private retryBaseDelay;
22
24
  debug: boolean;
25
+ private bootstrapPromise;
23
26
  constructor(adapter: AngularAdapterService);
24
27
  ngOnDestroy(): void;
25
28
  /**
26
- * Bootstrap React early during service initialization
29
+ * Bootstrap React from CDN with automatic retries.
30
+ * Deduplicates: concurrent callers share the same in-flight promise.
31
+ * On failure, clears the promise so the next call can retry.
27
32
  */
28
- private bootstrapReact;
33
+ private bootstrapWithRetries;
29
34
  /**
30
- * Wait for React to be ready, with special handling for first component
35
+ * Attempt to load the React ecosystem from CDN, retrying with exponential
36
+ * backoff if the CDN is unreachable or returns an error.
37
+ */
38
+ private doBootstrapWithRetries;
39
+ /**
40
+ * Wait for React to be ready, with special handling for first component.
41
+ * If bootstrap succeeded but createRoot isn't usable (stale browser cache),
42
+ * forces a re-bootstrap and retries.
31
43
  */
32
44
  waitForReactReady(): Promise<void>;
33
45
  /**
34
- * Get the current React context if loaded
46
+ * Poll for ReactDOM.createRoot availability. If the poll window expires
47
+ * (bootstrap succeeded but createRoot is broken, e.g. stale cache),
48
+ * force a fresh re-bootstrap and retry.
49
+ */
50
+ private pollForReactReadyWithRetries;
51
+ /**
52
+ * Poll for ReactDOM.createRoot for up to maxWaitTime ms.
53
+ * @returns true if createRoot became available, false if the poll window expired.
54
+ */
55
+ private pollForCreateRoot;
56
+ /**
57
+ * Get the current React context if loaded.
58
+ * Awaits the bootstrap (with retries) rather than calling adapter.initialize() independently.
35
59
  * @returns React context with React, ReactDOM, Babel, and libraries
36
60
  */
37
61
  getReactContext(): Promise<RuntimeContext>;
@@ -7,7 +7,9 @@ import { Injectable } from '@angular/core';
7
7
  import { BehaviorSubject, firstValueFrom } from 'rxjs';
8
8
  import { filter } from 'rxjs/operators';
9
9
  import { AngularAdapterService } from './angular-adapter.service';
10
+ import { reactRootManager } from '@memberjunction/react-runtime';
10
11
  import { ReactDebugConfig } from '../config/react-debug.config';
12
+ import { createAntdDropdownPositionHook } from '../hooks/antd-dropdown-position-hook';
11
13
  import * as i0 from "@angular/core";
12
14
  import * as i1 from "./angular-adapter.service";
13
15
  /**
@@ -23,38 +25,69 @@ export class ReactBridgeService {
23
25
  this.reactReady$ = this.reactReadySubject.asObservable();
24
26
  // Track if this is the first component trying to use React
25
27
  this.firstComponentAttempted = false;
26
- this.maxWaitTime = 5000; // Maximum 5 seconds wait time
28
+ this.maxWaitTime = 5000; // Maximum 5 seconds wait time for createRoot poll
27
29
  this.checkInterval = 200; // Check every 200ms
30
+ this.maxBootstrapRetries = 2; // Retry bootstrap up to 2 additional times
31
+ this.retryBaseDelay = 2000; // Base delay between bootstrap retries (ms)
28
32
  // Debug flag from project configuration
29
33
  this.debug = ReactDebugConfig.getDebugMode();
34
+ // The current bootstrap attempt — shared between constructor and getReactContext()
35
+ this.bootstrapPromise = null;
30
36
  // Bootstrap React immediately on service initialization
31
- this.bootstrapReact();
37
+ this.bootstrapWithRetries();
32
38
  }
33
39
  ngOnDestroy() {
34
40
  this.cleanup();
35
41
  }
36
42
  /**
37
- * Bootstrap React early during service initialization
43
+ * Bootstrap React from CDN with automatic retries.
44
+ * Deduplicates: concurrent callers share the same in-flight promise.
45
+ * On failure, clears the promise so the next call can retry.
38
46
  */
39
- async bootstrapReact() {
40
- try {
41
- // Log the debug mode being used
42
- console.log(`ReactBridgeService: Initializing React with debug mode = ${this.debug} (from ReactDebugConfig)`);
43
- // Pass debug flag to get development builds when debug is enabled
44
- await this.adapter.initialize(undefined, undefined, { debug: this.debug });
45
- if (this.debug) {
46
- console.log('React ecosystem pre-loaded successfully with DEVELOPMENT builds (detailed error messages)');
47
+ bootstrapWithRetries() {
48
+ if (!this.bootstrapPromise) {
49
+ this.bootstrapPromise = this.doBootstrapWithRetries();
50
+ }
51
+ return this.bootstrapPromise;
52
+ }
53
+ /**
54
+ * Attempt to load the React ecosystem from CDN, retrying with exponential
55
+ * backoff if the CDN is unreachable or returns an error.
56
+ */
57
+ async doBootstrapWithRetries() {
58
+ console.log(`ReactBridgeService: Initializing React with debug mode = ${this.debug} (from ReactDebugConfig)`);
59
+ let lastError;
60
+ for (let attempt = 0; attempt <= this.maxBootstrapRetries; attempt++) {
61
+ try {
62
+ if (attempt > 0) {
63
+ const delay = this.retryBaseDelay * Math.pow(2, attempt - 1);
64
+ console.warn(`React CDN bootstrap attempt ${attempt + 1} of ${this.maxBootstrapRetries + 1} — retrying in ${delay}ms...`);
65
+ await new Promise(resolve => setTimeout(resolve, delay));
66
+ }
67
+ await this.adapter.initialize(undefined, undefined, { debug: this.debug });
68
+ // Register Angular-specific runtime hooks for library compatibility
69
+ reactRootManager.RegisterHook(createAntdDropdownPositionHook());
70
+ if (this.debug) {
71
+ console.log('React ecosystem pre-loaded successfully with DEVELOPMENT builds (detailed error messages)');
72
+ }
73
+ else {
74
+ console.log('React ecosystem pre-loaded successfully with PRODUCTION builds (minified)');
75
+ }
76
+ return; // Success
47
77
  }
48
- else {
49
- console.log('React ecosystem pre-loaded successfully with PRODUCTION builds (minified)');
78
+ catch (error) {
79
+ lastError = error;
80
+ console.error(`React bootstrap attempt ${attempt + 1} failed:`, error);
50
81
  }
51
82
  }
52
- catch (error) {
53
- console.error('Failed to pre-load React ecosystem:', error);
54
- }
83
+ // All attempts exhausted — clear promise so future calls can retry
84
+ this.bootstrapPromise = null;
85
+ throw lastError;
55
86
  }
56
87
  /**
57
- * Wait for React to be ready, with special handling for first component
88
+ * Wait for React to be ready, with special handling for first component.
89
+ * If bootstrap succeeded but createRoot isn't usable (stale browser cache),
90
+ * forces a re-bootstrap and retries.
58
91
  */
59
92
  async waitForReactReady() {
60
93
  // If already ready, return immediately
@@ -65,51 +98,82 @@ export class ReactBridgeService {
65
98
  const isFirstComponent = !this.firstComponentAttempted;
66
99
  this.firstComponentAttempted = true;
67
100
  if (isFirstComponent) {
68
- // First component - check periodically until React is ready
69
- if (this.debug) {
70
- console.log('First React component loading - checking for React initialization');
71
- }
72
- const startTime = Date.now();
73
- while (Date.now() - startTime < this.maxWaitTime) {
101
+ await this.pollForReactReadyWithRetries();
102
+ }
103
+ else {
104
+ // Subsequent components wait for the ready signal
105
+ await firstValueFrom(this.reactReady$.pipe(filter(ready => ready)));
106
+ }
107
+ }
108
+ /**
109
+ * Poll for ReactDOM.createRoot availability. If the poll window expires
110
+ * (bootstrap succeeded but createRoot is broken, e.g. stale cache),
111
+ * force a fresh re-bootstrap and retry.
112
+ */
113
+ async pollForReactReadyWithRetries() {
114
+ const overallStart = Date.now();
115
+ for (let attempt = 0; attempt <= this.maxBootstrapRetries; attempt++) {
116
+ if (attempt > 0) {
117
+ console.warn(`React createRoot check failed (attempt ${attempt}) — forcing fresh CDN re-bootstrap...`);
118
+ // Clear the cached bootstrap promise so doBootstrapWithRetries runs fresh
119
+ this.bootstrapPromise = null;
120
+ this.adapter.destroy();
74
121
  try {
75
- const testDiv = document.createElement('div');
76
- const context = this.adapter.getRuntimeContext();
77
- if (context.ReactDOM?.createRoot) {
78
- // Try to create a test root
79
- const testRoot = context.ReactDOM.createRoot(testDiv);
80
- if (testRoot) {
81
- testRoot.unmount();
82
- // React is ready!
83
- this.reactReadySubject.next(true);
84
- if (this.debug) {
85
- console.log(`React is fully ready after ${Date.now() - startTime}ms`);
86
- }
87
- return;
88
- }
89
- }
122
+ await this.bootstrapWithRetries();
90
123
  }
91
- catch (error) {
92
- // Not ready yet, continue checking
124
+ catch {
125
+ // Bootstrap itself failed continue to next poll attempt
126
+ continue;
127
+ }
128
+ }
129
+ const ready = await this.pollForCreateRoot();
130
+ if (ready) {
131
+ this.reactReadySubject.next(true);
132
+ if (this.debug) {
133
+ console.log(`React is fully ready after ${Date.now() - overallStart}ms (attempt ${attempt + 1})`);
93
134
  }
94
- // Wait before next check
95
- await new Promise(resolve => setTimeout(resolve, this.checkInterval));
135
+ return;
96
136
  }
97
- // If we've exhausted the wait time, throw error
98
- console.error('React readiness test failed after maximum wait time');
99
- this.firstComponentAttempted = false;
100
- throw new Error(`ReactDOM.createRoot not available after ${this.maxWaitTime}ms`);
101
137
  }
102
- else {
103
- // Subsequent components wait for the ready signal
104
- await firstValueFrom(this.reactReady$.pipe(filter(ready => ready)));
138
+ // All attempts exhausted
139
+ const totalTime = Date.now() - overallStart;
140
+ console.error(`React readiness test failed after ${this.maxBootstrapRetries + 1} attempts (${totalTime}ms total)`);
141
+ this.firstComponentAttempted = false;
142
+ throw new Error(`ReactDOM.createRoot not available after ${this.maxBootstrapRetries + 1} attempts (${totalTime}ms). ` +
143
+ `Try a hard refresh (Ctrl+Shift+R) to clear the browser cache.`);
144
+ }
145
+ /**
146
+ * Poll for ReactDOM.createRoot for up to maxWaitTime ms.
147
+ * @returns true if createRoot became available, false if the poll window expired.
148
+ */
149
+ async pollForCreateRoot() {
150
+ const startTime = Date.now();
151
+ while (Date.now() - startTime < this.maxWaitTime) {
152
+ try {
153
+ const testDiv = document.createElement('div');
154
+ const context = this.adapter.getRuntimeContext();
155
+ if (context.ReactDOM?.createRoot) {
156
+ const testRoot = context.ReactDOM.createRoot(testDiv);
157
+ if (testRoot) {
158
+ testRoot.unmount();
159
+ return true;
160
+ }
161
+ }
162
+ }
163
+ catch {
164
+ // Not ready yet, continue checking
165
+ }
166
+ await new Promise(resolve => setTimeout(resolve, this.checkInterval));
105
167
  }
168
+ return false;
106
169
  }
107
170
  /**
108
- * Get the current React context if loaded
171
+ * Get the current React context if loaded.
172
+ * Awaits the bootstrap (with retries) rather than calling adapter.initialize() independently.
109
173
  * @returns React context with React, ReactDOM, Babel, and libraries
110
174
  */
111
175
  async getReactContext() {
112
- await this.adapter.initialize(undefined, undefined, { debug: this.debug });
176
+ await this.bootstrapWithRetries();
113
177
  return this.adapter.getRuntimeContext();
114
178
  }
115
179
  /**
@@ -177,6 +241,8 @@ export class ReactBridgeService {
177
241
  // Reset readiness state
178
242
  this.reactReadySubject.next(false);
179
243
  this.firstComponentAttempted = false;
244
+ // Clean up runtime hooks (disconnects observers, removes injected styles, etc.)
245
+ reactRootManager.cleanup();
180
246
  // Clean up adapter
181
247
  this.adapter.destroy();
182
248
  }
@@ -1 +1 @@
1
- {"version":3,"file":"react-bridge.service.js","sourceRoot":"","sources":["../../../src/lib/services/react-bridge.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Service to manage React and ReactDOM instances with proper lifecycle.\n * Bridges Angular components with React rendering capabilities.\n * @module @memberjunction/ng-react\n */\n\nimport { Injectable, OnDestroy } from '@angular/core';\nimport { BehaviorSubject, firstValueFrom } from 'rxjs';\nimport { filter } from 'rxjs/operators';\nimport { AngularAdapterService } from './angular-adapter.service';\nimport { RuntimeContext } from '@memberjunction/react-runtime';\nimport { ReactDebugConfig } from '../config/react-debug.config';\n\n/**\n * Service to manage React and ReactDOM instances with proper lifecycle.\n * Provides methods for creating and managing React roots in Angular applications.\n */\n@Injectable({ providedIn: 'root' })\nexport class ReactBridgeService implements OnDestroy {\n private reactRoots = new Set<any>();\n \n // Track React readiness state\n private reactReadySubject = new BehaviorSubject<boolean>(false);\n public reactReady$ = this.reactReadySubject.asObservable();\n \n // Track if this is the first component trying to use React\n private firstComponentAttempted = false;\n private maxWaitTime = 5000; // Maximum 5 seconds wait time\n private checkInterval = 200; // Check every 200ms\n \n // Debug flag from project configuration\n public debug: boolean = ReactDebugConfig.getDebugMode();\n\n constructor(private adapter: AngularAdapterService) {\n // Bootstrap React immediately on service initialization\n this.bootstrapReact();\n }\n\n ngOnDestroy(): void {\n this.cleanup();\n }\n\n /**\n * Bootstrap React early during service initialization\n */\n private async bootstrapReact(): Promise<void> {\n try {\n // Log the debug mode being used\n console.log(`ReactBridgeService: Initializing React with debug mode = ${this.debug} (from ReactDebugConfig)`);\n \n // Pass debug flag to get development builds when debug is enabled\n await this.adapter.initialize(undefined, undefined, { debug: this.debug });\n \n if (this.debug) {\n console.log('React ecosystem pre-loaded successfully with DEVELOPMENT builds (detailed error messages)');\n } else {\n console.log('React ecosystem pre-loaded successfully with PRODUCTION builds (minified)');\n }\n } catch (error) {\n console.error('Failed to pre-load React ecosystem:', error);\n }\n }\n\n /**\n * Wait for React to be ready, with special handling for first component\n */\n async waitForReactReady(): Promise<void> {\n // If already ready, return immediately\n if (this.reactReadySubject.value) {\n return;\n }\n\n // Check if this is the first component attempting to use React\n const isFirstComponent = !this.firstComponentAttempted;\n this.firstComponentAttempted = true;\n\n if (isFirstComponent) {\n // First component - check periodically until React is ready\n if (this.debug) {\n console.log('First React component loading - checking for React initialization');\n }\n \n const startTime = Date.now();\n \n while (Date.now() - startTime < this.maxWaitTime) {\n try {\n const testDiv = document.createElement('div');\n const context = this.adapter.getRuntimeContext();\n \n if (context.ReactDOM?.createRoot) {\n // Try to create a test root\n const testRoot = context.ReactDOM.createRoot(testDiv);\n if (testRoot) {\n testRoot.unmount();\n // React is ready!\n this.reactReadySubject.next(true);\n if (this.debug) {\n console.log(`React is fully ready after ${Date.now() - startTime}ms`);\n }\n return;\n }\n }\n } catch (error) {\n // Not ready yet, continue checking\n }\n \n // Wait before next check\n await new Promise(resolve => setTimeout(resolve, this.checkInterval));\n }\n \n // If we've exhausted the wait time, throw error\n console.error('React readiness test failed after maximum wait time');\n this.firstComponentAttempted = false;\n throw new Error(`ReactDOM.createRoot not available after ${this.maxWaitTime}ms`);\n } else {\n // Subsequent components wait for the ready signal\n await firstValueFrom(this.reactReady$.pipe(filter(ready => ready)));\n }\n }\n\n /**\n * Get the current React context if loaded\n * @returns React context with React, ReactDOM, Babel, and libraries\n */\n async getReactContext(): Promise<RuntimeContext> {\n await this.adapter.initialize(undefined, undefined, {debug: this.debug});\n return this.adapter.getRuntimeContext();\n }\n\n /**\n * Get the current React context synchronously\n * @returns React context or null if not loaded\n */\n getCurrentContext(): RuntimeContext | null {\n if (!this.adapter.isInitialized()) {\n return null;\n }\n return this.adapter.getRuntimeContext();\n }\n\n /**\n * Create a React root for rendering\n * @param container - DOM element to render into\n * @returns React root instance\n */\n createRoot(container: HTMLElement): any {\n const context = this.getCurrentContext();\n if (!context?.ReactDOM?.createRoot) {\n throw new Error('ReactDOM.createRoot not available');\n }\n\n const root = context.ReactDOM.createRoot(container);\n this.reactRoots.add(root);\n return root;\n }\n\n /**\n * Unmount and clean up a React root\n * @param root - React root to unmount\n */\n unmountRoot(root: any): void {\n if (root && typeof root.unmount === 'function') {\n try {\n root.unmount();\n } catch (error) {\n console.warn('Failed to unmount React root:', error);\n }\n }\n this.reactRoots.delete(root);\n }\n\n /**\n * Transpile JSX code to JavaScript\n * @param code - JSX code to transpile\n * @param filename - Optional filename for error messages\n * @returns Transpiled JavaScript code\n */\n transpileJSX(code: string, filename: string): string {\n return this.adapter.transpileJSX(code, filename);\n }\n\n /**\n * Clean up all React roots and reset context\n */\n private cleanup(): void {\n // Unmount all tracked React roots\n for (const root of this.reactRoots) {\n try {\n root.unmount();\n } catch (error) {\n console.warn('Failed to unmount React root:', error);\n }\n }\n this.reactRoots.clear();\n \n // Reset readiness state\n this.reactReadySubject.next(false);\n this.firstComponentAttempted = false;\n\n // Clean up adapter\n this.adapter.destroy();\n }\n\n /**\n * Check if React is currently ready\n * @returns true if React is ready\n */\n isReady(): boolean {\n return this.reactReadySubject.value;\n }\n\n /**\n * Get the number of active React roots\n * @returns Number of active roots\n */\n getActiveRootsCount(): number {\n return this.reactRoots.size;\n }\n}"]}
1
+ {"version":3,"file":"react-bridge.service.js","sourceRoot":"","sources":["../../../src/lib/services/react-bridge.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Service to manage React and ReactDOM instances with proper lifecycle.\n * Bridges Angular components with React rendering capabilities.\n * @module @memberjunction/ng-react\n */\n\nimport { Injectable, OnDestroy } from '@angular/core';\nimport { BehaviorSubject, firstValueFrom } from 'rxjs';\nimport { filter } from 'rxjs/operators';\nimport { AngularAdapterService } from './angular-adapter.service';\nimport { RuntimeContext, reactRootManager } from '@memberjunction/react-runtime';\nimport { ReactDebugConfig } from '../config/react-debug.config';\nimport { createAntdDropdownPositionHook } from '../hooks/antd-dropdown-position-hook';\n\n/**\n * Service to manage React and ReactDOM instances with proper lifecycle.\n * Provides methods for creating and managing React roots in Angular applications.\n */\n@Injectable({ providedIn: 'root' })\nexport class ReactBridgeService implements OnDestroy {\n private reactRoots = new Set<any>();\n\n // Track React readiness state\n private reactReadySubject = new BehaviorSubject<boolean>(false);\n public reactReady$ = this.reactReadySubject.asObservable();\n\n // Track if this is the first component trying to use React\n private firstComponentAttempted = false;\n private maxWaitTime = 5000; // Maximum 5 seconds wait time for createRoot poll\n private checkInterval = 200; // Check every 200ms\n private maxBootstrapRetries = 2; // Retry bootstrap up to 2 additional times\n private retryBaseDelay = 2000; // Base delay between bootstrap retries (ms)\n\n // Debug flag from project configuration\n public debug: boolean = ReactDebugConfig.getDebugMode();\n\n // The current bootstrap attempt — shared between constructor and getReactContext()\n private bootstrapPromise: Promise<void> | null = null;\n\n constructor(private adapter: AngularAdapterService) {\n // Bootstrap React immediately on service initialization\n this.bootstrapWithRetries();\n }\n\n ngOnDestroy(): void {\n this.cleanup();\n }\n\n /**\n * Bootstrap React from CDN with automatic retries.\n * Deduplicates: concurrent callers share the same in-flight promise.\n * On failure, clears the promise so the next call can retry.\n */\n private bootstrapWithRetries(): Promise<void> {\n if (!this.bootstrapPromise) {\n this.bootstrapPromise = this.doBootstrapWithRetries();\n }\n return this.bootstrapPromise;\n }\n\n /**\n * Attempt to load the React ecosystem from CDN, retrying with exponential\n * backoff if the CDN is unreachable or returns an error.\n */\n private async doBootstrapWithRetries(): Promise<void> {\n console.log(`ReactBridgeService: Initializing React with debug mode = ${this.debug} (from ReactDebugConfig)`);\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= this.maxBootstrapRetries; attempt++) {\n try {\n if (attempt > 0) {\n const delay = this.retryBaseDelay * Math.pow(2, attempt - 1);\n console.warn(\n `React CDN bootstrap attempt ${attempt + 1} of ${this.maxBootstrapRetries + 1} — retrying in ${delay}ms...`\n );\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n\n await this.adapter.initialize(undefined, undefined, { debug: this.debug });\n\n // Register Angular-specific runtime hooks for library compatibility\n reactRootManager.RegisterHook(createAntdDropdownPositionHook());\n\n if (this.debug) {\n console.log('React ecosystem pre-loaded successfully with DEVELOPMENT builds (detailed error messages)');\n } else {\n console.log('React ecosystem pre-loaded successfully with PRODUCTION builds (minified)');\n }\n return; // Success\n } catch (error) {\n lastError = error;\n console.error(`React bootstrap attempt ${attempt + 1} failed:`, error);\n }\n }\n\n // All attempts exhausted — clear promise so future calls can retry\n this.bootstrapPromise = null;\n throw lastError;\n }\n\n /**\n * Wait for React to be ready, with special handling for first component.\n * If bootstrap succeeded but createRoot isn't usable (stale browser cache),\n * forces a re-bootstrap and retries.\n */\n async waitForReactReady(): Promise<void> {\n // If already ready, return immediately\n if (this.reactReadySubject.value) {\n return;\n }\n\n // Check if this is the first component attempting to use React\n const isFirstComponent = !this.firstComponentAttempted;\n this.firstComponentAttempted = true;\n\n if (isFirstComponent) {\n await this.pollForReactReadyWithRetries();\n } else {\n // Subsequent components wait for the ready signal\n await firstValueFrom(this.reactReady$.pipe(filter(ready => ready)));\n }\n }\n\n /**\n * Poll for ReactDOM.createRoot availability. If the poll window expires\n * (bootstrap succeeded but createRoot is broken, e.g. stale cache),\n * force a fresh re-bootstrap and retry.\n */\n private async pollForReactReadyWithRetries(): Promise<void> {\n const overallStart = Date.now();\n\n for (let attempt = 0; attempt <= this.maxBootstrapRetries; attempt++) {\n if (attempt > 0) {\n console.warn(\n `React createRoot check failed (attempt ${attempt}) — forcing fresh CDN re-bootstrap...`\n );\n // Clear the cached bootstrap promise so doBootstrapWithRetries runs fresh\n this.bootstrapPromise = null;\n this.adapter.destroy();\n try {\n await this.bootstrapWithRetries();\n } catch {\n // Bootstrap itself failed — continue to next poll attempt\n continue;\n }\n }\n\n const ready = await this.pollForCreateRoot();\n if (ready) {\n this.reactReadySubject.next(true);\n if (this.debug) {\n console.log(`React is fully ready after ${Date.now() - overallStart}ms (attempt ${attempt + 1})`);\n }\n return;\n }\n }\n\n // All attempts exhausted\n const totalTime = Date.now() - overallStart;\n console.error(\n `React readiness test failed after ${this.maxBootstrapRetries + 1} attempts (${totalTime}ms total)`\n );\n this.firstComponentAttempted = false;\n throw new Error(\n `ReactDOM.createRoot not available after ${this.maxBootstrapRetries + 1} attempts (${totalTime}ms). ` +\n `Try a hard refresh (Ctrl+Shift+R) to clear the browser cache.`\n );\n }\n\n /**\n * Poll for ReactDOM.createRoot for up to maxWaitTime ms.\n * @returns true if createRoot became available, false if the poll window expired.\n */\n private async pollForCreateRoot(): Promise<boolean> {\n const startTime = Date.now();\n\n while (Date.now() - startTime < this.maxWaitTime) {\n try {\n const testDiv = document.createElement('div');\n const context = this.adapter.getRuntimeContext();\n\n if (context.ReactDOM?.createRoot) {\n const testRoot = context.ReactDOM.createRoot(testDiv);\n if (testRoot) {\n testRoot.unmount();\n return true;\n }\n }\n } catch {\n // Not ready yet, continue checking\n }\n\n await new Promise(resolve => setTimeout(resolve, this.checkInterval));\n }\n\n return false;\n }\n\n /**\n * Get the current React context if loaded.\n * Awaits the bootstrap (with retries) rather than calling adapter.initialize() independently.\n * @returns React context with React, ReactDOM, Babel, and libraries\n */\n async getReactContext(): Promise<RuntimeContext> {\n await this.bootstrapWithRetries();\n return this.adapter.getRuntimeContext();\n }\n\n /**\n * Get the current React context synchronously\n * @returns React context or null if not loaded\n */\n getCurrentContext(): RuntimeContext | null {\n if (!this.adapter.isInitialized()) {\n return null;\n }\n return this.adapter.getRuntimeContext();\n }\n\n /**\n * Create a React root for rendering\n * @param container - DOM element to render into\n * @returns React root instance\n */\n createRoot(container: HTMLElement): any {\n const context = this.getCurrentContext();\n if (!context?.ReactDOM?.createRoot) {\n throw new Error('ReactDOM.createRoot not available');\n }\n\n const root = context.ReactDOM.createRoot(container);\n this.reactRoots.add(root);\n return root;\n }\n\n /**\n * Unmount and clean up a React root\n * @param root - React root to unmount\n */\n unmountRoot(root: any): void {\n if (root && typeof root.unmount === 'function') {\n try {\n root.unmount();\n } catch (error) {\n console.warn('Failed to unmount React root:', error);\n }\n }\n this.reactRoots.delete(root);\n }\n\n /**\n * Transpile JSX code to JavaScript\n * @param code - JSX code to transpile\n * @param filename - Optional filename for error messages\n * @returns Transpiled JavaScript code\n */\n transpileJSX(code: string, filename: string): string {\n return this.adapter.transpileJSX(code, filename);\n }\n\n /**\n * Clean up all React roots and reset context\n */\n private cleanup(): void {\n // Unmount all tracked React roots\n for (const root of this.reactRoots) {\n try {\n root.unmount();\n } catch (error) {\n console.warn('Failed to unmount React root:', error);\n }\n }\n this.reactRoots.clear();\n \n // Reset readiness state\n this.reactReadySubject.next(false);\n this.firstComponentAttempted = false;\n\n // Clean up runtime hooks (disconnects observers, removes injected styles, etc.)\n reactRootManager.cleanup();\n\n // Clean up adapter\n this.adapter.destroy();\n }\n\n /**\n * Check if React is currently ready\n * @returns true if React is ready\n */\n isReady(): boolean {\n return this.reactReadySubject.value;\n }\n\n /**\n * Get the number of active React roots\n * @returns Number of active roots\n */\n getActiveRootsCount(): number {\n return this.reactRoots.size;\n }\n}"]}
@@ -9,3 +9,4 @@ export * from './lib/services/script-loader.service';
9
9
  export * from './lib/services/react-bridge.service';
10
10
  export * from './lib/services/angular-adapter.service';
11
11
  export * from './lib/config/react-debug.config';
12
+ export * from './lib/hooks/antd-dropdown-position-hook';
@@ -13,4 +13,6 @@ export * from './lib/services/react-bridge.service';
13
13
  export * from './lib/services/angular-adapter.service';
14
14
  // Configuration
15
15
  export * from './lib/config/react-debug.config';
16
+ // Hooks
17
+ export * from './lib/hooks/antd-dropdown-position-hook';
16
18
  //# sourceMappingURL=public-api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,SAAS;AACT,cAAc,cAAc,CAAC;AAE7B,aAAa;AACb,cAAc,+CAA+C,CAAC;AAE9D,WAAW;AACX,cAAc,sCAAsC,CAAC;AACrD,cAAc,qCAAqC,CAAC;AACpD,cAAc,wCAAwC,CAAC;AAEvD,gBAAgB;AAChB,cAAc,iCAAiC,CAAC","sourcesContent":["/**\n * @fileoverview Public API Surface of @memberjunction/ng-react\n * This file exports all public APIs from the Angular React integration library.\n * @module @memberjunction/ng-react\n */\n\n// Module\nexport * from './lib/module';\n\n// Components\nexport * from './lib/components/mj-react-component.component';\n\n// Services\nexport * from './lib/services/script-loader.service';\nexport * from './lib/services/react-bridge.service';\nexport * from './lib/services/angular-adapter.service';\n\n// Configuration\nexport * from './lib/config/react-debug.config';\n"]}
1
+ {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,SAAS;AACT,cAAc,cAAc,CAAC;AAE7B,aAAa;AACb,cAAc,+CAA+C,CAAC;AAE9D,WAAW;AACX,cAAc,sCAAsC,CAAC;AACrD,cAAc,qCAAqC,CAAC;AACpD,cAAc,wCAAwC,CAAC;AAEvD,gBAAgB;AAChB,cAAc,iCAAiC,CAAC;AAEhD,QAAQ;AACR,cAAc,yCAAyC,CAAC","sourcesContent":["/**\n * @fileoverview Public API Surface of @memberjunction/ng-react\n * This file exports all public APIs from the Angular React integration library.\n * @module @memberjunction/ng-react\n */\n\n// Module\nexport * from './lib/module';\n\n// Components\nexport * from './lib/components/mj-react-component.component';\n\n// Services\nexport * from './lib/services/script-loader.service';\nexport * from './lib/services/react-bridge.service';\nexport * from './lib/services/angular-adapter.service';\n\n// Configuration\nexport * from './lib/config/react-debug.config';\n\n// Hooks\nexport * from './lib/hooks/antd-dropdown-position-hook';\n"]}
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-react",
3
- "version": "5.22.0",
3
+ "version": "5.24.0",
4
4
  "description": "Angular components for hosting React components in MemberJunction applications",
5
5
  "scripts": {
6
6
  "build": "ngc -p tsconfig.json",
7
7
  "watch": "ngc -p tsconfig.json --watch",
8
- "test": "echo \"Tests not yet implemented\"",
8
+ "test": "vitest run",
9
9
  "patchVersion": "npm version patch",
10
10
  "test:watch": "vitest"
11
11
  },
@@ -41,14 +41,14 @@
41
41
  "styles"
42
42
  ],
43
43
  "dependencies": {
44
- "@memberjunction/ai-vectors-memory": "5.22.0",
45
- "@memberjunction/core": "5.22.0",
46
- "@memberjunction/core-entities": "5.22.0",
47
- "@memberjunction/global": "5.22.0",
48
- "@memberjunction/graphql-dataprovider": "5.22.0",
49
- "@memberjunction/interactive-component-types": "5.22.0",
50
- "@memberjunction/ng-notifications": "5.22.0",
51
- "@memberjunction/react-runtime": "5.22.0",
44
+ "@memberjunction/ai-vectors-memory": "5.24.0",
45
+ "@memberjunction/core": "5.24.0",
46
+ "@memberjunction/core-entities": "5.24.0",
47
+ "@memberjunction/global": "5.24.0",
48
+ "@memberjunction/graphql-dataprovider": "5.24.0",
49
+ "@memberjunction/interactive-component-types": "5.24.0",
50
+ "@memberjunction/ng-notifications": "5.24.0",
51
+ "@memberjunction/react-runtime": "5.24.0",
52
52
  "@angular/common": "21.1.3",
53
53
  "@angular/core": "21.1.3",
54
54
  "@angular/platform-browser": "21.1.3",