@playruo/embed 1.2.9 → 1.3.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.
package/dist/index.cjs CHANGED
@@ -43,11 +43,18 @@ var import_react = require("react");
43
43
 
44
44
  // src/url.ts
45
45
  var PLAYRUO_ORIGIN = "https://playruo.live";
46
- var buildEmbedUrl = ({ operation = "iframe", storeUrl }) => {
46
+ var normalizeOperation = (operation) => {
47
+ const trimmedOperation = operation.trim();
48
+ if (trimmedOperation === "") {
49
+ throw new Error("`operation` is required to build a Playruo embed URL.");
50
+ }
51
+ return trimmedOperation.startsWith("/") ? trimmedOperation.slice(1) : trimmedOperation;
52
+ };
53
+ var buildEmbedUrl = ({ operation, storeUrl }) => {
47
54
  return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, { operation, storeUrl });
48
55
  };
49
- var buildEmbedUrlWithOrigin = (origin, { operation = "iframe", storeUrl }) => {
50
- const operationPath = operation.startsWith("/") ? operation.slice(1) : operation;
56
+ var buildEmbedUrlWithOrigin = (origin, { operation, storeUrl }) => {
57
+ const operationPath = normalizeOperation(operation);
51
58
  const url = new URL(`/${operationPath}`, origin);
52
59
  url.searchParams.set("iframe", "true");
53
60
  if (storeUrl) {
@@ -60,7 +67,7 @@ var buildEmbedUrlWithOrigin = (origin, { operation = "iframe", storeUrl }) => {
60
67
  var import_jsx_runtime = require("react/jsx-runtime");
61
68
  var PlayruoEmbed = ({
62
69
  baseUrl,
63
- operation = "iframe",
70
+ operation,
64
71
  storeUrl,
65
72
  showIframe = false,
66
73
  className,
@@ -75,6 +82,7 @@ var PlayruoEmbed = ({
75
82
  const internalIframeRef = (0, import_react.useRef)(null);
76
83
  const iframeRef = externalIframeRef || internalIframeRef;
77
84
  const wasEmbedFullscreenRef = (0, import_react.useRef)(false);
85
+ const wasEmbedPointerLockedRef = (0, import_react.useRef)(false);
78
86
  const [isIframeLoaded, setIsIframeLoaded] = (0, import_react.useState)(false);
79
87
  const [hasPlayedOnce, setHasPlayedOnce] = (0, import_react.useState)(false);
80
88
  const [showGameIframe, setShowGameIframe] = (0, import_react.useState)(false);
@@ -144,30 +152,79 @@ var PlayruoEmbed = ({
144
152
  console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
145
153
  }
146
154
  }, [getKeyboardApi]);
147
- const requestFullscreenWithEscapeLock = (0, import_react.useCallback)(
155
+ const lockEscapeKey = (0, import_react.useCallback)(async () => {
156
+ const keyboard = getKeyboardApi();
157
+ if (!keyboard?.lock) {
158
+ return;
159
+ }
160
+ try {
161
+ await keyboard.lock(["Escape"]);
162
+ console.log("[PlayruoEmbed] Escape key locked during fullscreen");
163
+ } catch (error) {
164
+ console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
165
+ }
166
+ }, [getKeyboardApi]);
167
+ const getPointerLockElement = (0, import_react.useCallback)(() => {
168
+ const doc = document;
169
+ return doc.pointerLockElement || doc.mozPointerLockElement || doc.webkitPointerLockElement || null;
170
+ }, []);
171
+ const releasePointerLock = (0, import_react.useCallback)(() => {
172
+ const doc = document;
173
+ const exitPointerLock = doc.exitPointerLock?.bind(doc) || doc.mozExitPointerLock?.bind(doc) || doc.webkitExitPointerLock?.bind(doc);
174
+ try {
175
+ exitPointerLock?.();
176
+ wasEmbedPointerLockedRef.current = false;
177
+ } catch (error) {
178
+ console.warn("[PlayruoEmbed] Failed to release pointer lock:", error);
179
+ }
180
+ }, []);
181
+ const requestImmersiveMode = (0, import_react.useCallback)(
148
182
  async (iframe) => {
149
- const fullscreenPromise = iframe.requestFullscreen();
150
- const keyboard = getKeyboardApi();
151
- if (keyboard?.lock && typeof window !== "undefined" && window.self === window.top) {
152
- void keyboard.lock(["Escape"]).then(() => {
153
- console.log("[PlayruoEmbed] Escape key locked during fullscreen");
154
- }).catch((error) => {
155
- const isTopLevelContextError = error instanceof DOMException && error.name === "InvalidStateError" && error.message.includes("top-level browsing context");
156
- if (!isTopLevelContextError) {
157
- console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
183
+ const pointerTarget = iframe;
184
+ if (pointerTarget.requestPointerLock) {
185
+ try {
186
+ const pointerLockResult = pointerTarget.requestPointerLock({ unadjustedMovement: true });
187
+ if (pointerLockResult && typeof pointerLockResult.then === "function") {
188
+ void pointerLockResult.then(() => {
189
+ wasEmbedPointerLockedRef.current = true;
190
+ console.log("[PlayruoEmbed] Pointer locked on iframe");
191
+ }).catch(async (error) => {
192
+ if (error instanceof DOMException && error.name === "NotSupportedError") {
193
+ try {
194
+ const fallbackResult = pointerTarget.requestPointerLock?.();
195
+ if (fallbackResult && typeof fallbackResult.then === "function") {
196
+ await fallbackResult;
197
+ }
198
+ wasEmbedPointerLockedRef.current = true;
199
+ console.log("[PlayruoEmbed] Pointer locked on iframe without raw input");
200
+ return;
201
+ } catch (fallbackError) {
202
+ console.warn("[PlayruoEmbed] Failed to lock pointer without raw input:", fallbackError);
203
+ return;
204
+ }
205
+ }
206
+ console.warn("[PlayruoEmbed] Failed to lock pointer:", error);
207
+ });
208
+ } else {
209
+ wasEmbedPointerLockedRef.current = true;
210
+ console.log("[PlayruoEmbed] Pointer lock requested on iframe");
158
211
  }
159
- });
212
+ } catch (error) {
213
+ console.warn("[PlayruoEmbed] Failed to request pointer lock:", error);
214
+ }
160
215
  }
216
+ const fullscreenPromise = iframe.requestFullscreen();
161
217
  try {
162
218
  await fullscreenPromise;
163
219
  wasEmbedFullscreenRef.current = true;
164
220
  console.log("[PlayruoEmbed] Fullscreen granted");
165
221
  } catch (error) {
166
222
  releaseEscapeKeyLock();
223
+ releasePointerLock();
167
224
  throw error;
168
225
  }
169
226
  },
170
- [getKeyboardApi, releaseEscapeKeyLock]
227
+ [releaseEscapeKeyLock, releasePointerLock]
171
228
  );
172
229
  const handleTryNow = (0, import_react.useCallback)(async () => {
173
230
  if (!isCompatible) {
@@ -182,7 +239,7 @@ var PlayruoEmbed = ({
182
239
  setShowGameIframe(true);
183
240
  try {
184
241
  try {
185
- await requestFullscreenWithEscapeLock(iframe);
242
+ await requestImmersiveMode(iframe);
186
243
  } catch (fsError) {
187
244
  console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
188
245
  }
@@ -190,6 +247,8 @@ var PlayruoEmbed = ({
190
247
  type: "playruo",
191
248
  action: "tryNowClick",
192
249
  fromEmbed: true,
250
+ isFullscreen: true,
251
+ parentWillHandleFullscreen: true,
193
252
  isReplay: hasPlayedOnce
194
253
  }, "*");
195
254
  console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
@@ -203,7 +262,7 @@ var PlayruoEmbed = ({
203
262
  setErrorMessage("Failed to start game");
204
263
  onError?.("Failed to start game");
205
264
  }
206
- }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestFullscreenWithEscapeLock]);
265
+ }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestImmersiveMode]);
207
266
  const handleBuyNow = (0, import_react.useCallback)(() => {
208
267
  if (!storeUrl) {
209
268
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -337,6 +396,7 @@ var PlayruoEmbed = ({
337
396
  const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
338
397
  if (isEmbedFullscreen) {
339
398
  wasEmbedFullscreenRef.current = true;
399
+ void lockEscapeKey();
340
400
  if (iframe) {
341
401
  setTimeout(() => {
342
402
  iframe.focus();
@@ -351,12 +411,18 @@ var PlayruoEmbed = ({
351
411
  releaseEscapeKeyLock();
352
412
  if (wasEmbedFullscreenRef.current) {
353
413
  wasEmbedFullscreenRef.current = false;
414
+ const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
415
+ if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
416
+ releasePointerLock();
417
+ }
354
418
  if (iframe) {
355
419
  try {
356
420
  iframe.contentWindow?.postMessage({
357
421
  type: "playruo",
358
422
  action: "exitFullscreen",
359
- fromEmbed: true
423
+ fromEmbed: true,
424
+ isFullscreen: false,
425
+ parentWillHandleFullscreen: true
360
426
  }, "*");
361
427
  } catch {
362
428
  console.error("[PlayruoEmbed] Failed to exit fullscreen");
@@ -380,12 +446,33 @@ var PlayruoEmbed = ({
380
446
  document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
381
447
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
382
448
  };
383
- }, [storeUrl, onDemoCompleted, iframeRef, releaseEscapeKeyLock]);
449
+ }, [storeUrl, onDemoCompleted, iframeRef, getPointerLockElement, lockEscapeKey, releaseEscapeKeyLock, releasePointerLock]);
450
+ (0, import_react.useEffect)(() => {
451
+ const handlePointerLockChange = () => {
452
+ const iframe = iframeRef.current;
453
+ wasEmbedPointerLockedRef.current = Boolean(iframe && getPointerLockElement() === iframe);
454
+ };
455
+ const handlePointerLockError = () => {
456
+ wasEmbedPointerLockedRef.current = false;
457
+ console.warn("[PlayruoEmbed] Pointer lock request was rejected");
458
+ };
459
+ document.addEventListener("pointerlockchange", handlePointerLockChange);
460
+ document.addEventListener("pointerlockerror", handlePointerLockError);
461
+ return () => {
462
+ document.removeEventListener("pointerlockchange", handlePointerLockChange);
463
+ document.removeEventListener("pointerlockerror", handlePointerLockError);
464
+ };
465
+ }, [getPointerLockElement, iframeRef]);
384
466
  (0, import_react.useEffect)(() => {
385
467
  return () => {
386
468
  releaseEscapeKeyLock();
469
+ const iframe = iframeRef.current;
470
+ const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
471
+ if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
472
+ releasePointerLock();
473
+ }
387
474
  };
388
- }, [releaseEscapeKeyLock]);
475
+ }, [getPointerLockElement, iframeRef, releaseEscapeKeyLock, releasePointerLock]);
389
476
  (0, import_react.useEffect)(() => {
390
477
  const logCrossOriginStatus = () => {
391
478
  const isIsolated = self.crossOriginIsolated;
package/dist/index.d.cts CHANGED
@@ -36,7 +36,8 @@ interface PlayruoEmbedRenderState {
36
36
  interface PlayruoEmbedProps {
37
37
  /** Game origin (scheme + host). Omit to use production (`https://playruo.live`). */
38
38
  baseUrl?: string;
39
- operation?: string;
39
+ /** Game operation slug used to build the iframe URL. */
40
+ operation: string;
40
41
  storeUrl?: string;
41
42
  showIframe?: boolean;
42
43
  className?: string;
@@ -108,6 +109,7 @@ interface PlayruoInternalMessage {
108
109
  action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
109
110
  fromEmbed: true;
110
111
  isReplay?: boolean;
112
+ isFullscreen?: boolean;
111
113
  parentWillHandleFullscreen?: boolean;
112
114
  }
113
115
  /**
@@ -123,7 +125,7 @@ declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMess
123
125
  declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
124
126
 
125
127
  interface BuildUrlParams {
126
- operation?: string;
128
+ operation: string;
127
129
  storeUrl?: string;
128
130
  }
129
131
  /**
package/dist/index.d.ts CHANGED
@@ -36,7 +36,8 @@ interface PlayruoEmbedRenderState {
36
36
  interface PlayruoEmbedProps {
37
37
  /** Game origin (scheme + host). Omit to use production (`https://playruo.live`). */
38
38
  baseUrl?: string;
39
- operation?: string;
39
+ /** Game operation slug used to build the iframe URL. */
40
+ operation: string;
40
41
  storeUrl?: string;
41
42
  showIframe?: boolean;
42
43
  className?: string;
@@ -108,6 +109,7 @@ interface PlayruoInternalMessage {
108
109
  action: "tryNowClick" | "exitFullscreen" | "mute" | "unmute";
109
110
  fromEmbed: true;
110
111
  isReplay?: boolean;
112
+ isFullscreen?: boolean;
111
113
  parentWillHandleFullscreen?: boolean;
112
114
  }
113
115
  /**
@@ -123,7 +125,7 @@ declare const isPlayruoEmbedMessage: (data: unknown) => data is PlayruoEmbedMess
123
125
  declare const isPlayruoInternalMessage: (data: unknown) => data is PlayruoInternalMessage;
124
126
 
125
127
  interface BuildUrlParams {
126
- operation?: string;
128
+ operation: string;
127
129
  storeUrl?: string;
128
130
  }
129
131
  /**
package/dist/index.js CHANGED
@@ -3,11 +3,18 @@ import { useEffect, useMemo, useRef, useState, useCallback } from "react";
3
3
 
4
4
  // src/url.ts
5
5
  var PLAYRUO_ORIGIN = "https://playruo.live";
6
- var buildEmbedUrl = ({ operation = "iframe", storeUrl }) => {
6
+ var normalizeOperation = (operation) => {
7
+ const trimmedOperation = operation.trim();
8
+ if (trimmedOperation === "") {
9
+ throw new Error("`operation` is required to build a Playruo embed URL.");
10
+ }
11
+ return trimmedOperation.startsWith("/") ? trimmedOperation.slice(1) : trimmedOperation;
12
+ };
13
+ var buildEmbedUrl = ({ operation, storeUrl }) => {
7
14
  return buildEmbedUrlWithOrigin(PLAYRUO_ORIGIN, { operation, storeUrl });
8
15
  };
9
- var buildEmbedUrlWithOrigin = (origin, { operation = "iframe", storeUrl }) => {
10
- const operationPath = operation.startsWith("/") ? operation.slice(1) : operation;
16
+ var buildEmbedUrlWithOrigin = (origin, { operation, storeUrl }) => {
17
+ const operationPath = normalizeOperation(operation);
11
18
  const url = new URL(`/${operationPath}`, origin);
12
19
  url.searchParams.set("iframe", "true");
13
20
  if (storeUrl) {
@@ -20,7 +27,7 @@ var buildEmbedUrlWithOrigin = (origin, { operation = "iframe", storeUrl }) => {
20
27
  import { jsx, jsxs } from "react/jsx-runtime";
21
28
  var PlayruoEmbed = ({
22
29
  baseUrl,
23
- operation = "iframe",
30
+ operation,
24
31
  storeUrl,
25
32
  showIframe = false,
26
33
  className,
@@ -35,6 +42,7 @@ var PlayruoEmbed = ({
35
42
  const internalIframeRef = useRef(null);
36
43
  const iframeRef = externalIframeRef || internalIframeRef;
37
44
  const wasEmbedFullscreenRef = useRef(false);
45
+ const wasEmbedPointerLockedRef = useRef(false);
38
46
  const [isIframeLoaded, setIsIframeLoaded] = useState(false);
39
47
  const [hasPlayedOnce, setHasPlayedOnce] = useState(false);
40
48
  const [showGameIframe, setShowGameIframe] = useState(false);
@@ -104,30 +112,79 @@ var PlayruoEmbed = ({
104
112
  console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
105
113
  }
106
114
  }, [getKeyboardApi]);
107
- const requestFullscreenWithEscapeLock = useCallback(
115
+ const lockEscapeKey = useCallback(async () => {
116
+ const keyboard = getKeyboardApi();
117
+ if (!keyboard?.lock) {
118
+ return;
119
+ }
120
+ try {
121
+ await keyboard.lock(["Escape"]);
122
+ console.log("[PlayruoEmbed] Escape key locked during fullscreen");
123
+ } catch (error) {
124
+ console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
125
+ }
126
+ }, [getKeyboardApi]);
127
+ const getPointerLockElement = useCallback(() => {
128
+ const doc = document;
129
+ return doc.pointerLockElement || doc.mozPointerLockElement || doc.webkitPointerLockElement || null;
130
+ }, []);
131
+ const releasePointerLock = useCallback(() => {
132
+ const doc = document;
133
+ const exitPointerLock = doc.exitPointerLock?.bind(doc) || doc.mozExitPointerLock?.bind(doc) || doc.webkitExitPointerLock?.bind(doc);
134
+ try {
135
+ exitPointerLock?.();
136
+ wasEmbedPointerLockedRef.current = false;
137
+ } catch (error) {
138
+ console.warn("[PlayruoEmbed] Failed to release pointer lock:", error);
139
+ }
140
+ }, []);
141
+ const requestImmersiveMode = useCallback(
108
142
  async (iframe) => {
109
- const fullscreenPromise = iframe.requestFullscreen();
110
- const keyboard = getKeyboardApi();
111
- if (keyboard?.lock && typeof window !== "undefined" && window.self === window.top) {
112
- void keyboard.lock(["Escape"]).then(() => {
113
- console.log("[PlayruoEmbed] Escape key locked during fullscreen");
114
- }).catch((error) => {
115
- const isTopLevelContextError = error instanceof DOMException && error.name === "InvalidStateError" && error.message.includes("top-level browsing context");
116
- if (!isTopLevelContextError) {
117
- console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
143
+ const pointerTarget = iframe;
144
+ if (pointerTarget.requestPointerLock) {
145
+ try {
146
+ const pointerLockResult = pointerTarget.requestPointerLock({ unadjustedMovement: true });
147
+ if (pointerLockResult && typeof pointerLockResult.then === "function") {
148
+ void pointerLockResult.then(() => {
149
+ wasEmbedPointerLockedRef.current = true;
150
+ console.log("[PlayruoEmbed] Pointer locked on iframe");
151
+ }).catch(async (error) => {
152
+ if (error instanceof DOMException && error.name === "NotSupportedError") {
153
+ try {
154
+ const fallbackResult = pointerTarget.requestPointerLock?.();
155
+ if (fallbackResult && typeof fallbackResult.then === "function") {
156
+ await fallbackResult;
157
+ }
158
+ wasEmbedPointerLockedRef.current = true;
159
+ console.log("[PlayruoEmbed] Pointer locked on iframe without raw input");
160
+ return;
161
+ } catch (fallbackError) {
162
+ console.warn("[PlayruoEmbed] Failed to lock pointer without raw input:", fallbackError);
163
+ return;
164
+ }
165
+ }
166
+ console.warn("[PlayruoEmbed] Failed to lock pointer:", error);
167
+ });
168
+ } else {
169
+ wasEmbedPointerLockedRef.current = true;
170
+ console.log("[PlayruoEmbed] Pointer lock requested on iframe");
118
171
  }
119
- });
172
+ } catch (error) {
173
+ console.warn("[PlayruoEmbed] Failed to request pointer lock:", error);
174
+ }
120
175
  }
176
+ const fullscreenPromise = iframe.requestFullscreen();
121
177
  try {
122
178
  await fullscreenPromise;
123
179
  wasEmbedFullscreenRef.current = true;
124
180
  console.log("[PlayruoEmbed] Fullscreen granted");
125
181
  } catch (error) {
126
182
  releaseEscapeKeyLock();
183
+ releasePointerLock();
127
184
  throw error;
128
185
  }
129
186
  },
130
- [getKeyboardApi, releaseEscapeKeyLock]
187
+ [releaseEscapeKeyLock, releasePointerLock]
131
188
  );
132
189
  const handleTryNow = useCallback(async () => {
133
190
  if (!isCompatible) {
@@ -142,7 +199,7 @@ var PlayruoEmbed = ({
142
199
  setShowGameIframe(true);
143
200
  try {
144
201
  try {
145
- await requestFullscreenWithEscapeLock(iframe);
202
+ await requestImmersiveMode(iframe);
146
203
  } catch (fsError) {
147
204
  console.warn("[PlayruoEmbed] Fullscreen denied, continuing without:", fsError);
148
205
  }
@@ -150,6 +207,8 @@ var PlayruoEmbed = ({
150
207
  type: "playruo",
151
208
  action: "tryNowClick",
152
209
  fromEmbed: true,
210
+ isFullscreen: true,
211
+ parentWillHandleFullscreen: true,
153
212
  isReplay: hasPlayedOnce
154
213
  }, "*");
155
214
  console.log("[PlayruoEmbed] Sent tryNowClick to game", hasPlayedOnce ? "(replay)" : "(first time)");
@@ -163,7 +222,7 @@ var PlayruoEmbed = ({
163
222
  setErrorMessage("Failed to start game");
164
223
  onError?.("Failed to start game");
165
224
  }
166
- }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestFullscreenWithEscapeLock]);
225
+ }, [isCompatible, hasPlayedOnce, onError, iframeRef, requestImmersiveMode]);
167
226
  const handleBuyNow = useCallback(() => {
168
227
  if (!storeUrl) {
169
228
  console.warn("[PlayruoEmbed] No store URL provided");
@@ -297,6 +356,7 @@ var PlayruoEmbed = ({
297
356
  const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
298
357
  if (isEmbedFullscreen) {
299
358
  wasEmbedFullscreenRef.current = true;
359
+ void lockEscapeKey();
300
360
  if (iframe) {
301
361
  setTimeout(() => {
302
362
  iframe.focus();
@@ -311,12 +371,18 @@ var PlayruoEmbed = ({
311
371
  releaseEscapeKeyLock();
312
372
  if (wasEmbedFullscreenRef.current) {
313
373
  wasEmbedFullscreenRef.current = false;
374
+ const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
375
+ if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
376
+ releasePointerLock();
377
+ }
314
378
  if (iframe) {
315
379
  try {
316
380
  iframe.contentWindow?.postMessage({
317
381
  type: "playruo",
318
382
  action: "exitFullscreen",
319
- fromEmbed: true
383
+ fromEmbed: true,
384
+ isFullscreen: false,
385
+ parentWillHandleFullscreen: true
320
386
  }, "*");
321
387
  } catch {
322
388
  console.error("[PlayruoEmbed] Failed to exit fullscreen");
@@ -340,12 +406,33 @@ var PlayruoEmbed = ({
340
406
  document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
341
407
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
342
408
  };
343
- }, [storeUrl, onDemoCompleted, iframeRef, releaseEscapeKeyLock]);
409
+ }, [storeUrl, onDemoCompleted, iframeRef, getPointerLockElement, lockEscapeKey, releaseEscapeKeyLock, releasePointerLock]);
410
+ useEffect(() => {
411
+ const handlePointerLockChange = () => {
412
+ const iframe = iframeRef.current;
413
+ wasEmbedPointerLockedRef.current = Boolean(iframe && getPointerLockElement() === iframe);
414
+ };
415
+ const handlePointerLockError = () => {
416
+ wasEmbedPointerLockedRef.current = false;
417
+ console.warn("[PlayruoEmbed] Pointer lock request was rejected");
418
+ };
419
+ document.addEventListener("pointerlockchange", handlePointerLockChange);
420
+ document.addEventListener("pointerlockerror", handlePointerLockError);
421
+ return () => {
422
+ document.removeEventListener("pointerlockchange", handlePointerLockChange);
423
+ document.removeEventListener("pointerlockerror", handlePointerLockError);
424
+ };
425
+ }, [getPointerLockElement, iframeRef]);
344
426
  useEffect(() => {
345
427
  return () => {
346
428
  releaseEscapeKeyLock();
429
+ const iframe = iframeRef.current;
430
+ const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
431
+ if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
432
+ releasePointerLock();
433
+ }
347
434
  };
348
- }, [releaseEscapeKeyLock]);
435
+ }, [getPointerLockElement, iframeRef, releaseEscapeKeyLock, releasePointerLock]);
349
436
  useEffect(() => {
350
437
  const logCrossOriginStatus = () => {
351
438
  const isIsolated = self.crossOriginIsolated;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playruo/embed",
3
- "version": "1.2.9",
3
+ "version": "1.3.0",
4
4
  "description": "React SDK to embed Playruo with postMessage API.",
5
5
  "license": "MIT",
6
6
  "type": "module",