@uniai-fe/uds-templates 0.12.2 → 0.12.3

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/README.md CHANGED
@@ -45,13 +45,19 @@ import { Frame, Modal } from "@uniai-fe/uds-templates";
45
45
  화면 방향 API를 지원하면 전체화면 동안 `landscape`를 요청하며, 종료·대상 제거 시
46
46
  고정을 해제해 기본 방향 정책으로 돌아간다. 세로 방향 복귀를 강제하지 않는다.
47
47
  방향 API 미지원·요청 거부는 전체화면 시청을 차단하지 않는다.
48
- 진입 요청 대기 중 대상이 교체·제거되면 늦게 열린 이전 대상의 전체화면을 종료하고
49
- `false`를 반환한다. 다른 대상이 소유한 전체화면은 유지한다.
50
-
51
- `useCctvFullscreen({ fallback: "viewport" })`는 표준 Fullscreen API 미지원 시
52
- 페이지 안 전체창 확대를 허용한다. `isFullscreenSupported`와 `isFullscreen`은
48
+ 표준 API 요청 성공 여부를 반환하며, 진입 요청 대기 중 대상이 교체·제거되면
49
+ 해당 대상의 가로 방향 고정을 추가로 요청하지 않는다.
50
+
51
+ `useCctvFullscreen({ fallback: "viewport" })`는 iPhone·iPad에서 표준 Fullscreen API를
52
+ 호출하지 않고 페이지 안 전체창 확대로 바로 진입한다. Mac·Windows·Android와 그 외 환경은
53
+ 표준 API를 사용하며, 미지원·요청 거부 시 전체창 확대로 전환하지 않는다.
54
+ 기기 판정은 기존 `checkAppleDevice`와 iPhone·iPad UA를 사용하고, Macintosh UA의 iPad는
55
+ `maxTouchPoints > 1`로 보완한다. UA·터치 정보에 기반한 판정이므로 위장된 UA까지 보장하지 않는다.
56
+ `isFullscreenSupported`와 `isFullscreen`은
53
57
  표준 API의 의미를 유지하며, 전체창 확대는 `isViewportExpanded`로 구분한다.
54
- 옵션을 생략하면 기존 동작을 유지하고, 표준 API 요청이 거부된 경우에는 확대하지 않는다.
58
+ 옵션을 생략하면 iPhone·iPad도 표준 API만 사용한다.
59
+ 전체창 확대는 현재 연결된 대상만 허용하며, 다른 viewer가 전체화면·전체창 확대를 소유하면
60
+ 진입하지 않는다.
55
61
 
56
62
  서비스는 같은 `targetRef` container에 `isViewportExpanded`에 따른 viewport 고정,
57
63
  크기·안전 영역·반응형 스타일을 적용한다. 브라우저 UI를 숨기거나 기기 방향을 강제하지 않는다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-templates",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "description": "UNIAI Design System; UI Templates Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -67,17 +67,17 @@
67
67
  "react-hook-form": "^7.84.0",
68
68
  "sass": "^1.101.7",
69
69
  "typescript": "6.0.3",
70
+ "@uniai-fe/next-devkit": "0.4.0",
70
71
  "@uniai-fe/react-hooks": "0.3.0",
71
- "@uniai-fe/eslint-config": "0.4.2",
72
72
  "@uniai-fe/tsconfig": "0.2.0",
73
+ "@uniai-fe/eslint-config": "0.4.2",
73
74
  "@uniai-fe/uds-foundation": "0.6.0",
74
75
  "@uniai-fe/uds-primitives": "0.12.5",
75
- "@uniai-fe/util-jotai": "0.3.0",
76
- "@uniai-fe/util-api": "0.2.1",
77
76
  "@uniai-fe/util-next": "0.5.0",
78
- "@uniai-fe/next-devkit": "0.4.0",
79
- "@uniai-fe/util-functions": "0.4.3",
80
- "@uniai-fe/util-rtc": "0.2.1"
77
+ "@uniai-fe/util-api": "0.2.1",
78
+ "@uniai-fe/util-jotai": "0.3.0",
79
+ "@uniai-fe/util-rtc": "0.2.1",
80
+ "@uniai-fe/util-functions": "0.4.3"
81
81
  },
82
82
  "scripts": {
83
83
  "check:pre-commit": "pnpm --dir ../../.. run check:pre-commit",
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { checkAppleDevice } from "@uniai-fe/util-functions/runtime-env";
4
5
  import type {
5
6
  UseCctvFullscreenParams,
6
7
  UseCctvFullscreenReturn,
@@ -13,14 +14,14 @@ const viewportOwners = new WeakMap<Document, HTMLElement>();
13
14
  * CCTV; service-owned viewer container fullscreen state
14
15
  * @hook
15
16
  * @desc 대상 element의 표준 Fullscreen API 지원 여부와 실제 document 상태를 동기화한다.
16
- * 요청 실패는 기존 viewer를 유지하며, 외부 종료 후에는 진입 전 focus를 복원한다.
17
+ * 외부 종료 후에는 진입 전 focus를 복원하며, 표준 API 요청 실패는 기존 viewer를 유지한다.
17
18
  * 지원 환경에서는 진입 후 가로 방향을 요청하고 종료·대상 제거 시 기본 방향 정책으로 돌려준다.
18
19
  * 방향 요청 실패는 전체화면 성공에 영향을 주지 않으며 종료 후 세로 방향을 강제하지 않는다.
19
- * fallback이 "viewport"이면 표준 API 미지원 시 전체창 확대 상태와 배경 scroll·focus를 관리한다.
20
+ * fallback이 "viewport"이면 iPhone·iPad에서 표준 API 대신 전체창 확대와 배경 scroll·focus를 관리한다.
21
+ * 그 외 환경과 옵션을 생략한 호출은 표준 API를 사용한다.
20
22
  * 실제 확대 layout은 서비스가 isViewportExpanded로 적용하며 영상 DOM을 이동하거나 다시 만들지 않는다.
21
- * 표준 API 요청 거부는 전체창 확대로 전환하지 않는다.
22
- * @param {UseCctvFullscreenParams} options 미지원 환경의 확대 정책
23
- * @property {"viewport"} [options.fallback] "viewport"는 전체창 확대를 허용한다. 생략하면 표준 API만 사용한다.
23
+ * @param {UseCctvFullscreenParams} options iPhone·iPad의 확대 정책
24
+ * @property {"viewport"} [options.fallback] "viewport"는 iPhone·iPad에서 전체창 확대를 사용한다. 생략하면 표준 API만 사용한다.
24
25
  * @return {UseCctvFullscreenReturn} 대상 ref callback과 전체화면 상태·action
25
26
  */
26
27
  export function useCctvFullscreen({
@@ -148,7 +149,7 @@ export function useCctvFullscreen({
148
149
  ]);
149
150
 
150
151
  /**
151
- * 표준 API를 우선하며 미지원일 때만 선택한 전체창 확대를 시작한다.
152
+ * iPhone·iPad의 선택한 전체창 확대와 나머지 환경의 표준 전체화면을 구분한다.
152
153
  */
153
154
  const enterFullscreen = useCallback(async () => {
154
155
  const target = targetElementRef.current;
@@ -163,18 +164,19 @@ export function useCctvFullscreen({
163
164
  }
164
165
 
165
166
  const ownerDocument = target.ownerDocument;
166
- if (viewportOwners.has(ownerDocument)) return false;
167
+ const deviceNavigator = ownerDocument.defaultView?.navigator;
168
+ const userAgent = deviceNavigator?.userAgent ?? "";
169
+ // iPad의 데스크톱 UA는 Mac과 같으므로 Macintosh UA에 한해 다중 터치 정보를 보완한다.
170
+ const isIPhoneOrIPad: boolean =
171
+ checkAppleDevice(userAgent) &&
172
+ (/\((?:iPhone|iPad)(?:;|\))/i.test(userAgent) ||
173
+ (/Macintosh/i.test(userAgent) &&
174
+ (deviceNavigator?.maxTouchPoints ?? 0) > 1));
175
+ const useViewport: boolean = fallback === "viewport" && isIPhoneOrIPad;
167
176
  const isNativeSupported =
168
177
  ownerDocument.fullscreenEnabled &&
169
178
  typeof target.requestFullscreen === "function";
170
- if (
171
- !isNativeSupported &&
172
- (fallback !== "viewport" ||
173
- !target.isConnected ||
174
- ownerDocument.fullscreenElement)
175
- ) {
176
- return false;
177
- }
179
+ if (!useViewport && !isNativeSupported) return false;
178
180
  const activeElement = ownerDocument.activeElement;
179
181
  const ElementConstructor = ownerDocument.defaultView?.HTMLElement;
180
182
  entryFocusRef.current =
@@ -182,7 +184,13 @@ export function useCctvFullscreen({
182
184
  ? activeElement
183
185
  : null;
184
186
 
185
- if (!isNativeSupported) {
187
+ if (useViewport) {
188
+ if (
189
+ !target.isConnected ||
190
+ ownerDocument.fullscreenElement ||
191
+ viewportOwners.has(ownerDocument)
192
+ )
193
+ return false;
186
194
  const restores: (() => void)[] = [];
187
195
  viewportOwners.set(ownerDocument, target);
188
196
 
@@ -279,15 +287,9 @@ export function useCctvFullscreen({
279
287
 
280
288
  try {
281
289
  await target.requestFullscreen();
282
- // 대상 정리 뒤 완료된 요청은 이전 화면을 남기지 않되 새 대상의 전체화면은 보존한다.
283
- if (targetElementRef.current !== target) {
284
- if (ownerDocument.fullscreenElement === target) {
285
- await ownerDocument.exitFullscreen().catch(() => undefined);
286
- }
287
- return false;
288
- }
289
290
  const orientation = target.ownerDocument.defaultView?.screen.orientation;
290
291
  if (
292
+ targetElementRef.current === target &&
291
293
  target.ownerDocument.fullscreenElement === target &&
292
294
  typeof orientation?.lock === "function" &&
293
295
  typeof orientation.unlock === "function"
@@ -14,12 +14,12 @@ import type { UseFormReturn } from "react-hook-form";
14
14
  import type { CctvBaseContext } from "./context";
15
15
 
16
16
  /**
17
- * CCTV; 전체화면 미지원 환경의 확대 정책
18
- * @property {"viewport"} [fallback] "viewport"는 표준 API 미지원 시 서비스의 전체창 확대 스타일을 사용한다. 생략하면 표준 API만 사용한다.
17
+ * CCTV; iPhone·iPad의 확대 정책
18
+ * @property {"viewport"} [fallback] "viewport"는 iPhone·iPad에서 표준 API 대신 서비스의 전체창 확대 스타일을 사용한다. 그 외 환경과 생략한 호출은 표준 API만 사용한다.
19
19
  */
20
20
  export interface UseCctvFullscreenParams {
21
21
  /**
22
- * "viewport"는 미지원 환경의 전체창 확대를 허용한다. 생략하면 기존 viewer를 유지한다.
22
+ * "viewport"는 iPhone·iPad에서 전체창 확대로 바로 진입한다. 그 외 환경과 생략한 호출은 표준 API만 사용한다.
23
23
  */
24
24
  fallback?: "viewport";
25
25
  }