payaza-storefront-layouts 1.0.11 → 1.0.13

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 +1 @@
1
- {"version":3,"file":"ShadowDOMWrapper.d.ts","sourceRoot":"","sources":["../../src/preview/ShadowDOMWrapper.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AAGrD,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,qBAAqB,2CAyO/G"}
1
+ {"version":3,"file":"ShadowDOMWrapper.d.ts","sourceRoot":"","sources":["../../src/preview/ShadowDOMWrapper.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AAGrD,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,qBAAqB,2CAgO/G"}
@@ -18,20 +18,33 @@ export function ShadowDOMWrapper({ children, className, onReady, onLinkClick, st
18
18
  const reactRootRef = useRef(null);
19
19
  const containerRef = useRef(null);
20
20
  const childrenRef = useRef(children);
21
+ const isUnmountingRef = useRef(false);
22
+ const isMountedRef = useRef(false);
21
23
  // Keep children ref updated
22
24
  useEffect(() => {
23
25
  childrenRef.current = children;
24
26
  }, [children]);
27
+ // Main effect: Create shadow root and React root on mount
25
28
  useEffect(() => {
26
29
  if (!shadowHostRef.current)
27
30
  return;
31
+ // Reset unmounting flag when mounting
32
+ isUnmountingRef.current = false;
33
+ isMountedRef.current = true;
28
34
  // Check if shadow root already exists
29
35
  const existingShadowRoot = shadowHostRef.current.shadowRoot;
30
36
  if (existingShadowRoot) {
31
37
  shadowRootRef.current = existingShadowRoot;
32
- // Update content if shadow root already exists
33
38
  const container = existingShadowRoot.getElementById('shadow-preview-container');
34
- if (container && reactRootRef.current) {
39
+ containerRef.current = container;
40
+ // If React root doesn't exist, create it
41
+ if (!reactRootRef.current && container) {
42
+ const root = createRoot(container);
43
+ reactRootRef.current = root;
44
+ root.render(childrenRef.current);
45
+ }
46
+ else if (reactRootRef.current && container && !isUnmountingRef.current) {
47
+ // Update content if root exists
35
48
  reactRootRef.current.render(childrenRef.current);
36
49
  }
37
50
  onReady?.();
@@ -68,81 +81,67 @@ export function ShadowDOMWrapper({ children, className, onReady, onLinkClick, st
68
81
  // Create React root and render children
69
82
  const root = createRoot(container);
70
83
  reactRootRef.current = root;
84
+ // Always render on initial mount
71
85
  root.render(childrenRef.current);
72
- // Set up link click interception inside Shadow DOM
73
- if (onLinkClick) {
74
- const handleClick = (e) => {
75
- const target = e.target;
76
- const link = target.closest('a[href]');
77
- if (!link || !link.href)
78
- return;
79
- try {
80
- const url = new URL(link.href, window.location.origin);
81
- const pathname = url.pathname;
82
- // Only intercept internal links
83
- if (url.origin !== window.location.origin) {
84
- return; // External link, allow default behavior
85
- }
86
- // Extract route from href (remove store slug prefix if present)
87
- let route = pathname;
88
- // Check if pathname starts with store slug (e.g., /modern-eats/contact)
89
- if (storeSlug && pathname.startsWith(`/${storeSlug}`)) {
90
- // Remove the store slug prefix: /modern-eats/contact -> /contact
91
- route = pathname.slice(storeSlug.length + 1);
92
- // If route is empty after removing slug, it's the homepage
93
- if (!route || route === '') {
94
- route = '/';
95
- }
96
- }
97
- else if (pathname === '/' || pathname === '') {
98
- // Homepage link
99
- route = '/';
100
- }
101
- // Normalize route (ensure it starts with /)
102
- if (route && !route.startsWith('/')) {
103
- route = `/${route}`;
104
- }
105
- // Ensure route is not empty
106
- if (!route) {
107
- route = '/';
108
- }
109
- // Note: We don't preserve query string and hash here because
110
- // the route is stored as a query param. Query strings and hash
111
- // should be handled separately if needed.
112
- // Prevent default navigation and call handler
113
- e.preventDefault();
114
- e.stopPropagation();
115
- onLinkClick(route);
116
- }
117
- catch (err) {
118
- // If URL parsing fails, allow default behavior
119
- console.warn('[ShadowDOMWrapper] Failed to parse link URL:', err, link.href);
120
- }
121
- };
122
- // Handler will be set up in separate useEffect to handle prop changes
123
- }
124
86
  onReady?.();
125
87
  }
126
88
  catch (err) {
127
89
  console.error('[ShadowDOMWrapper] Failed to create shadow root:', err);
128
90
  }
129
91
  return () => {
130
- // Cleanup
131
- if (reactRootRef.current) {
132
- try {
133
- reactRootRef.current.unmount();
134
- }
135
- catch (e) {
136
- console.warn('[ShadowDOMWrapper] Cleanup warning:', e);
137
- }
138
- reactRootRef.current = null;
92
+ // Mark as unmounting to prevent concurrent renders
93
+ isUnmountingRef.current = true;
94
+ isMountedRef.current = false;
95
+ // Defer unmounting to avoid race condition with React's rendering cycle
96
+ // Use setTimeout to ensure React finishes its current render before unmounting
97
+ const rootToUnmount = reactRootRef.current;
98
+ reactRootRef.current = null;
99
+ if (rootToUnmount) {
100
+ setTimeout(() => {
101
+ try {
102
+ rootToUnmount.unmount();
103
+ }
104
+ catch (e) {
105
+ // Ignore errors during unmount - root may already be unmounted
106
+ console.warn('[ShadowDOMWrapper] Unmount warning (safe to ignore):', e);
107
+ }
108
+ }, 0);
139
109
  }
140
110
  };
141
- }, [onReady]);
111
+ // eslint-disable-next-line react-hooks/exhaustive-deps
112
+ }, []); // Run only on mount - shadow root should persist across re-renders
142
113
  // Update content when children change
143
114
  useEffect(() => {
115
+ // Guard: Don't render if unmounting
116
+ if (isUnmountingRef.current) {
117
+ return;
118
+ }
119
+ // Only render if React root and container exist
144
120
  if (reactRootRef.current && containerRef.current) {
145
- reactRootRef.current.render(childrenRef.current);
121
+ try {
122
+ reactRootRef.current.render(childrenRef.current);
123
+ }
124
+ catch (e) {
125
+ // Ignore render errors if we're unmounting
126
+ if (!isUnmountingRef.current) {
127
+ console.warn('[ShadowDOMWrapper] Render error:', e);
128
+ }
129
+ }
130
+ }
131
+ else if (shadowRootRef.current && !reactRootRef.current) {
132
+ // If shadow root exists but React root doesn't, try to recreate it
133
+ const container = shadowRootRef.current.getElementById('shadow-preview-container');
134
+ if (container) {
135
+ try {
136
+ const root = createRoot(container);
137
+ reactRootRef.current = root;
138
+ containerRef.current = container;
139
+ root.render(childrenRef.current);
140
+ }
141
+ catch (e) {
142
+ console.warn('[ShadowDOMWrapper] Failed to recreate React root:', e);
143
+ }
144
+ }
146
145
  }
147
146
  }, [children]);
148
147
  // Set up link click handler when shadow root and props are ready
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payaza-storefront-layouts",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "Shared layout components for StoreFront applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",