@uniai-fe/uds-templates 0.12.1 → 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 +25 -0
- package/package.json +7 -7
- package/src/cctv/hooks/useFullscreen.ts +191 -35
- package/src/cctv/types/hook.ts +22 -6
package/README.md
CHANGED
|
@@ -45,6 +45,31 @@ import { Frame, Modal } from "@uniai-fe/uds-templates";
|
|
|
45
45
|
화면 방향 API를 지원하면 전체화면 동안 `landscape`를 요청하며, 종료·대상 제거 시
|
|
46
46
|
고정을 해제해 기본 방향 정책으로 돌아간다. 세로 방향 복귀를 강제하지 않는다.
|
|
47
47
|
방향 API 미지원·요청 거부는 전체화면 시청을 차단하지 않는다.
|
|
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`은
|
|
57
|
+
표준 API의 의미를 유지하며, 전체창 확대는 `isViewportExpanded`로 구분한다.
|
|
58
|
+
옵션을 생략하면 iPhone·iPad도 표준 API만 사용한다.
|
|
59
|
+
전체창 확대는 현재 연결된 대상만 허용하며, 다른 viewer가 전체화면·전체창 확대를 소유하면
|
|
60
|
+
진입하지 않는다.
|
|
61
|
+
|
|
62
|
+
서비스는 같은 `targetRef` container에 `isViewportExpanded`에 따른 viewport 고정,
|
|
63
|
+
크기·안전 영역·반응형 스타일을 적용한다. 브라우저 UI를 숨기거나 기기 방향을 강제하지 않는다.
|
|
64
|
+
진입·종료 action은 두 방식을 처리하므로 버튼의 활성 상태에는
|
|
65
|
+
`isFullscreen || isViewportExpanded`를 사용한다. 전체창 확대를 선택한 서비스는
|
|
66
|
+
표준 API 지원 여부로 해당 버튼을 숨기지 않는다.
|
|
67
|
+
|
|
68
|
+
Hook은 전체창 확대 동안 배경 scroll과 대상 바깥의 focus를 제한한다. 확대 종료 버튼,
|
|
69
|
+
Escape, 대상 교체·제거 또는 unmount로 종료하면 기존 overflow·inert·tabindex와
|
|
70
|
+
연결된 진입 control의 focus를 복원한다. 카메라 전환은 container를 유지하며, DOM을
|
|
71
|
+
다른 부모로 이동하거나 영상을 다시 만들지 않는다. 같은 document에서는 한 Hook만
|
|
72
|
+
전체창 확대를 소유한다.
|
|
48
73
|
|
|
49
74
|
## Styles
|
|
50
75
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniai-fe/uds-templates",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "UNIAI Design System; UI Templates Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -68,16 +68,16 @@
|
|
|
68
68
|
"sass": "^1.101.7",
|
|
69
69
|
"typescript": "6.0.3",
|
|
70
70
|
"@uniai-fe/next-devkit": "0.4.0",
|
|
71
|
+
"@uniai-fe/react-hooks": "0.3.0",
|
|
72
|
+
"@uniai-fe/tsconfig": "0.2.0",
|
|
71
73
|
"@uniai-fe/eslint-config": "0.4.2",
|
|
74
|
+
"@uniai-fe/uds-foundation": "0.6.0",
|
|
72
75
|
"@uniai-fe/uds-primitives": "0.12.5",
|
|
73
|
-
"@uniai-fe/util-
|
|
74
|
-
"@uniai-fe/react-hooks": "0.3.0",
|
|
76
|
+
"@uniai-fe/util-next": "0.5.0",
|
|
75
77
|
"@uniai-fe/util-api": "0.2.1",
|
|
76
|
-
"@uniai-fe/
|
|
78
|
+
"@uniai-fe/util-jotai": "0.3.0",
|
|
77
79
|
"@uniai-fe/util-rtc": "0.2.1",
|
|
78
|
-
"@uniai-fe/util-functions": "0.4.3"
|
|
79
|
-
"@uniai-fe/uds-foundation": "0.6.0",
|
|
80
|
-
"@uniai-fe/util-next": "0.5.0"
|
|
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,24 +1,43 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
-
import
|
|
4
|
+
import { checkAppleDevice } from "@uniai-fe/util-functions/runtime-env";
|
|
5
|
+
import type {
|
|
6
|
+
UseCctvFullscreenParams,
|
|
7
|
+
UseCctvFullscreenReturn,
|
|
8
|
+
} from "../types";
|
|
9
|
+
|
|
10
|
+
// 같은 document의 viewer들이 배경 잠금과 focus 복원 책임을 동시에 갖지 않도록 제한한다.
|
|
11
|
+
const viewportOwners = new WeakMap<Document, HTMLElement>();
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
14
|
* CCTV; service-owned viewer container fullscreen state
|
|
8
15
|
* @hook
|
|
9
16
|
* @desc 대상 element의 표준 Fullscreen API 지원 여부와 실제 document 상태를 동기화한다.
|
|
10
|
-
*
|
|
17
|
+
* 외부 종료 후에는 진입 전 focus를 복원하며, 표준 API 요청 실패는 기존 viewer를 유지한다.
|
|
11
18
|
* 지원 환경에서는 진입 후 가로 방향을 요청하고 종료·대상 제거 시 기본 방향 정책으로 돌려준다.
|
|
12
19
|
* 방향 요청 실패는 전체화면 성공에 영향을 주지 않으며 종료 후 세로 방향을 강제하지 않는다.
|
|
20
|
+
* fallback이 "viewport"이면 iPhone·iPad에서 표준 API 대신 전체창 확대와 배경 scroll·focus를 관리한다.
|
|
21
|
+
* 그 외 환경과 옵션을 생략한 호출은 표준 API를 사용한다.
|
|
22
|
+
* 실제 확대 layout은 서비스가 isViewportExpanded로 적용하며 영상 DOM을 이동하거나 다시 만들지 않는다.
|
|
23
|
+
* @param {UseCctvFullscreenParams} options iPhone·iPad의 확대 정책
|
|
24
|
+
* @property {"viewport"} [options.fallback] "viewport"는 iPhone·iPad에서 전체창 확대를 사용한다. 생략하면 표준 API만 사용한다.
|
|
13
25
|
* @return {UseCctvFullscreenReturn} 대상 ref callback과 전체화면 상태·action
|
|
14
26
|
*/
|
|
15
|
-
export function useCctvFullscreen(
|
|
27
|
+
export function useCctvFullscreen({
|
|
28
|
+
fallback,
|
|
29
|
+
}: UseCctvFullscreenParams = {}): UseCctvFullscreenReturn {
|
|
16
30
|
const [targetElement, setTargetElement] = useState<HTMLElement | null>(null);
|
|
17
31
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
32
|
+
const [isViewportExpanded, setIsViewportExpanded] = useState(false);
|
|
18
33
|
const targetElementRef = useRef<HTMLElement | null>(null);
|
|
19
34
|
const entryFocusRef = useRef<HTMLElement | null>(null);
|
|
20
35
|
const wasFullscreenRef = useRef(false);
|
|
21
36
|
const unlockOrientationRef = useRef<(() => void) | null>(null);
|
|
37
|
+
const viewportSessionRef = useRef<{
|
|
38
|
+
target: HTMLElement;
|
|
39
|
+
release: () => void;
|
|
40
|
+
} | null>(null);
|
|
22
41
|
|
|
23
42
|
const releaseOrientation = useCallback(() => {
|
|
24
43
|
const unlock = unlockOrientationRef.current;
|
|
@@ -30,22 +49,6 @@ export function useCctvFullscreen(): UseCctvFullscreenReturn {
|
|
|
30
49
|
}
|
|
31
50
|
}, []);
|
|
32
51
|
|
|
33
|
-
/**
|
|
34
|
-
* service가 선택한 전체화면 대상 element를 hook lifecycle에 연결한다.
|
|
35
|
-
*/
|
|
36
|
-
const targetRef = useCallback(
|
|
37
|
-
(element: HTMLElement | null) => {
|
|
38
|
-
if (targetElementRef.current !== element) releaseOrientation();
|
|
39
|
-
targetElementRef.current = element;
|
|
40
|
-
setTargetElement(element);
|
|
41
|
-
|
|
42
|
-
if (element === null) {
|
|
43
|
-
setIsFullscreen(false);
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
[releaseOrientation],
|
|
47
|
-
);
|
|
48
|
-
|
|
49
52
|
/**
|
|
50
53
|
* 외부 종료 후에도 사용자가 시작한 control로 keyboard focus를 돌려보낸다.
|
|
51
54
|
*/
|
|
@@ -54,10 +57,37 @@ export function useCctvFullscreen(): UseCctvFullscreenReturn {
|
|
|
54
57
|
entryFocusRef.current = null;
|
|
55
58
|
|
|
56
59
|
if (entryFocus?.isConnected) {
|
|
57
|
-
entryFocus.focus();
|
|
60
|
+
entryFocus.focus({ preventScroll: true });
|
|
58
61
|
}
|
|
59
62
|
}, []);
|
|
60
63
|
|
|
64
|
+
const exitViewportExpansion = useCallback(() => {
|
|
65
|
+
const session = viewportSessionRef.current;
|
|
66
|
+
if (!session) return false;
|
|
67
|
+
viewportSessionRef.current = null;
|
|
68
|
+
session.release();
|
|
69
|
+
setIsViewportExpanded(false);
|
|
70
|
+
restoreEntryFocus();
|
|
71
|
+
return true;
|
|
72
|
+
}, [restoreEntryFocus]);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* service가 선택한 전체화면 대상 element를 hook lifecycle에 연결한다.
|
|
76
|
+
*/
|
|
77
|
+
const targetRef = useCallback(
|
|
78
|
+
(element: HTMLElement | null) => {
|
|
79
|
+
if (targetElementRef.current !== element) {
|
|
80
|
+
releaseOrientation();
|
|
81
|
+
exitViewportExpansion();
|
|
82
|
+
}
|
|
83
|
+
targetElementRef.current = element;
|
|
84
|
+
setTargetElement(element);
|
|
85
|
+
|
|
86
|
+
if (element === null) setIsFullscreen(false);
|
|
87
|
+
},
|
|
88
|
+
[exitViewportExpansion, releaseOrientation],
|
|
89
|
+
);
|
|
90
|
+
|
|
61
91
|
useEffect(() => {
|
|
62
92
|
if (targetElement === null) return;
|
|
63
93
|
|
|
@@ -98,6 +128,9 @@ export function useCctvFullscreen(): UseCctvFullscreenReturn {
|
|
|
98
128
|
);
|
|
99
129
|
|
|
100
130
|
releaseOrientation();
|
|
131
|
+
if (viewportSessionRef.current?.target === targetElement) {
|
|
132
|
+
exitViewportExpansion();
|
|
133
|
+
}
|
|
101
134
|
if (ownerDocument.fullscreenElement === targetElement) {
|
|
102
135
|
void ownerDocument.exitFullscreen().catch(() => undefined);
|
|
103
136
|
}
|
|
@@ -108,32 +141,150 @@ export function useCctvFullscreen(): UseCctvFullscreenReturn {
|
|
|
108
141
|
|
|
109
142
|
wasFullscreenRef.current = false;
|
|
110
143
|
};
|
|
111
|
-
}, [
|
|
144
|
+
}, [
|
|
145
|
+
exitViewportExpansion,
|
|
146
|
+
releaseOrientation,
|
|
147
|
+
restoreEntryFocus,
|
|
148
|
+
targetElement,
|
|
149
|
+
]);
|
|
112
150
|
|
|
113
151
|
/**
|
|
114
|
-
*
|
|
152
|
+
* iPhone·iPad의 선택한 전체창 확대와 나머지 환경의 표준 전체화면을 구분한다.
|
|
115
153
|
*/
|
|
116
154
|
const enterFullscreen = useCallback(async () => {
|
|
117
155
|
const target = targetElementRef.current;
|
|
118
156
|
|
|
157
|
+
if (target === null) return false;
|
|
158
|
+
|
|
119
159
|
if (
|
|
120
|
-
target ===
|
|
121
|
-
|
|
122
|
-
typeof target.requestFullscreen !== "function"
|
|
160
|
+
target.ownerDocument.fullscreenElement === target ||
|
|
161
|
+
viewportSessionRef.current?.target === target
|
|
123
162
|
) {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (target.ownerDocument.fullscreenElement === target) {
|
|
128
163
|
return true;
|
|
129
164
|
}
|
|
130
165
|
|
|
131
|
-
const
|
|
166
|
+
const ownerDocument = target.ownerDocument;
|
|
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;
|
|
176
|
+
const isNativeSupported =
|
|
177
|
+
ownerDocument.fullscreenEnabled &&
|
|
178
|
+
typeof target.requestFullscreen === "function";
|
|
179
|
+
if (!useViewport && !isNativeSupported) return false;
|
|
180
|
+
const activeElement = ownerDocument.activeElement;
|
|
181
|
+
const ElementConstructor = ownerDocument.defaultView?.HTMLElement;
|
|
132
182
|
entryFocusRef.current =
|
|
133
|
-
|
|
183
|
+
ElementConstructor && activeElement instanceof ElementConstructor
|
|
134
184
|
? activeElement
|
|
135
185
|
: null;
|
|
136
186
|
|
|
187
|
+
if (useViewport) {
|
|
188
|
+
if (
|
|
189
|
+
!target.isConnected ||
|
|
190
|
+
ownerDocument.fullscreenElement ||
|
|
191
|
+
viewportOwners.has(ownerDocument)
|
|
192
|
+
)
|
|
193
|
+
return false;
|
|
194
|
+
const restores: (() => void)[] = [];
|
|
195
|
+
viewportOwners.set(ownerDocument, target);
|
|
196
|
+
|
|
197
|
+
// 기존 modal의 잠금과 inline style은 해당 속성만 보관해 종료 시 복원한다.
|
|
198
|
+
for (const element of [
|
|
199
|
+
ownerDocument.documentElement,
|
|
200
|
+
ownerDocument.body,
|
|
201
|
+
]) {
|
|
202
|
+
const overflow = element.style.overflow;
|
|
203
|
+
const priority = element.style.getPropertyPriority("overflow");
|
|
204
|
+
element.style.setProperty("overflow", "hidden");
|
|
205
|
+
restores.push(() =>
|
|
206
|
+
element.style.setProperty("overflow", overflow, priority),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
for (
|
|
210
|
+
let branch: HTMLElement = target;
|
|
211
|
+
branch.parentElement;
|
|
212
|
+
branch = branch.parentElement
|
|
213
|
+
) {
|
|
214
|
+
for (const sibling of branch.parentElement.children) {
|
|
215
|
+
if (
|
|
216
|
+
sibling === branch ||
|
|
217
|
+
!ElementConstructor ||
|
|
218
|
+
!(sibling instanceof ElementConstructor)
|
|
219
|
+
)
|
|
220
|
+
continue;
|
|
221
|
+
const inert = sibling.getAttribute("inert");
|
|
222
|
+
sibling.setAttribute("inert", "");
|
|
223
|
+
restores.push(() => {
|
|
224
|
+
if (inert === null) sibling.removeAttribute("inert");
|
|
225
|
+
else sibling.setAttribute("inert", inert);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const tabIndex = target.getAttribute("tabindex");
|
|
230
|
+
target.setAttribute("tabindex", "-1");
|
|
231
|
+
restores.push(() => {
|
|
232
|
+
if (tabIndex === null) target.removeAttribute("tabindex");
|
|
233
|
+
else target.setAttribute("tabindex", tabIndex);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const onFocus = () => {
|
|
237
|
+
if (!target.contains(ownerDocument.activeElement))
|
|
238
|
+
target.focus({ preventScroll: true });
|
|
239
|
+
};
|
|
240
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
241
|
+
if (event.key === "Escape") {
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
event.stopPropagation();
|
|
244
|
+
exitViewportExpansion();
|
|
245
|
+
} else if (event.key === "Tab") {
|
|
246
|
+
const controls = Array.from(
|
|
247
|
+
target.querySelectorAll<HTMLElement>(
|
|
248
|
+
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])',
|
|
249
|
+
),
|
|
250
|
+
).filter(
|
|
251
|
+
element =>
|
|
252
|
+
element.tabIndex >= 0 &&
|
|
253
|
+
element.getClientRects().length > 0 &&
|
|
254
|
+
!element.closest("[inert]"),
|
|
255
|
+
);
|
|
256
|
+
const first = controls[0];
|
|
257
|
+
const last = controls.at(-1);
|
|
258
|
+
if (
|
|
259
|
+
!first ||
|
|
260
|
+
ownerDocument.activeElement === target ||
|
|
261
|
+
(event.shiftKey
|
|
262
|
+
? ownerDocument.activeElement === first
|
|
263
|
+
: ownerDocument.activeElement === last)
|
|
264
|
+
) {
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
(event.shiftKey ? (last ?? target) : (first ?? target)).focus({
|
|
267
|
+
preventScroll: true,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
ownerDocument.addEventListener("keydown", onKeyDown, true);
|
|
273
|
+
ownerDocument.addEventListener("focusin", onFocus);
|
|
274
|
+
viewportSessionRef.current = {
|
|
275
|
+
target,
|
|
276
|
+
release: () => {
|
|
277
|
+
ownerDocument.removeEventListener("keydown", onKeyDown, true);
|
|
278
|
+
ownerDocument.removeEventListener("focusin", onFocus);
|
|
279
|
+
restores.reverse().forEach(restore => restore());
|
|
280
|
+
viewportOwners.delete(ownerDocument);
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
onFocus();
|
|
284
|
+
setIsViewportExpanded(true);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
|
|
137
288
|
try {
|
|
138
289
|
await target.requestFullscreen();
|
|
139
290
|
const orientation = target.ownerDocument.defaultView?.screen.orientation;
|
|
@@ -160,12 +311,13 @@ export function useCctvFullscreen(): UseCctvFullscreenReturn {
|
|
|
160
311
|
entryFocusRef.current = null;
|
|
161
312
|
return false;
|
|
162
313
|
}
|
|
163
|
-
}, []);
|
|
314
|
+
}, [exitViewportExpansion, fallback]);
|
|
164
315
|
|
|
165
316
|
/**
|
|
166
317
|
* 다른 element의 전체화면은 건드리지 않고 현재 target 소유 상태만 종료한다.
|
|
167
318
|
*/
|
|
168
319
|
const exitFullscreen = useCallback(async () => {
|
|
320
|
+
if (exitViewportExpansion()) return true;
|
|
169
321
|
const target = targetElementRef.current;
|
|
170
322
|
|
|
171
323
|
if (
|
|
@@ -183,7 +335,7 @@ export function useCctvFullscreen(): UseCctvFullscreenReturn {
|
|
|
183
335
|
} catch {
|
|
184
336
|
return false;
|
|
185
337
|
}
|
|
186
|
-
}, [releaseOrientation]);
|
|
338
|
+
}, [exitViewportExpansion, releaseOrientation]);
|
|
187
339
|
|
|
188
340
|
/**
|
|
189
341
|
* 현재 document 소유 상태에 따라 target의 진입 또는 종료 action을 선택한다.
|
|
@@ -191,7 +343,10 @@ export function useCctvFullscreen(): UseCctvFullscreenReturn {
|
|
|
191
343
|
const toggleFullscreen = useCallback(async () => {
|
|
192
344
|
const target = targetElementRef.current;
|
|
193
345
|
|
|
194
|
-
if (
|
|
346
|
+
if (
|
|
347
|
+
viewportSessionRef.current ||
|
|
348
|
+
target?.ownerDocument.fullscreenElement === target
|
|
349
|
+
) {
|
|
195
350
|
return exitFullscreen();
|
|
196
351
|
}
|
|
197
352
|
|
|
@@ -202,9 +357,10 @@ export function useCctvFullscreen(): UseCctvFullscreenReturn {
|
|
|
202
357
|
targetRef,
|
|
203
358
|
isFullscreenSupported:
|
|
204
359
|
targetElement !== null &&
|
|
205
|
-
targetElement.ownerDocument.fullscreenEnabled &&
|
|
360
|
+
targetElement.ownerDocument.fullscreenEnabled === true &&
|
|
206
361
|
typeof targetElement.requestFullscreen === "function",
|
|
207
362
|
isFullscreen,
|
|
363
|
+
isViewportExpanded,
|
|
208
364
|
enterFullscreen,
|
|
209
365
|
exitFullscreen,
|
|
210
366
|
toggleFullscreen,
|
package/src/cctv/types/hook.ts
CHANGED
|
@@ -13,14 +13,26 @@ import type {
|
|
|
13
13
|
import type { UseFormReturn } from "react-hook-form";
|
|
14
14
|
import type { CctvBaseContext } from "./context";
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* CCTV; iPhone·iPad의 확대 정책
|
|
18
|
+
* @property {"viewport"} [fallback] "viewport"는 iPhone·iPad에서 표준 API 대신 서비스의 전체창 확대 스타일을 사용한다. 그 외 환경과 생략한 호출은 표준 API만 사용한다.
|
|
19
|
+
*/
|
|
20
|
+
export interface UseCctvFullscreenParams {
|
|
21
|
+
/**
|
|
22
|
+
* "viewport"는 iPhone·iPad에서 전체창 확대로 바로 진입한다. 그 외 환경과 생략한 호출은 표준 API만 사용한다.
|
|
23
|
+
*/
|
|
24
|
+
fallback?: "viewport";
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
/**
|
|
17
28
|
* CCTV; useCctvFullscreen return
|
|
18
29
|
* @property {React.RefCallback<HTMLElement>} targetRef 전체화면 대상 viewer container ref callback
|
|
19
30
|
* @property {boolean} isFullscreenSupported 대상과 브라우저의 표준 Fullscreen API 지원 여부
|
|
20
31
|
* @property {boolean} isFullscreen 현재 대상이 실제 전체화면인지 여부
|
|
21
|
-
* @property {
|
|
22
|
-
* @property {() => Promise<boolean>}
|
|
23
|
-
* @property {() => Promise<boolean>}
|
|
32
|
+
* @property {boolean} isViewportExpanded 표준 전체화면 대신 서비스의 전체창 확대 스타일을 적용할 상태
|
|
33
|
+
* @property {() => Promise<boolean>} enterFullscreen 대상 전체화면 또는 허용한 전체창 확대 진입 action과 성공 여부
|
|
34
|
+
* @property {() => Promise<boolean>} exitFullscreen 현재 대상 소유 전체화면·전체창 확대 종료 action과 성공 여부
|
|
35
|
+
* @property {() => Promise<boolean>} toggleFullscreen 현재 대상의 전체화면·전체창 확대 진입·종료 action과 성공 여부
|
|
24
36
|
*/
|
|
25
37
|
export interface UseCctvFullscreenReturn {
|
|
26
38
|
/**
|
|
@@ -36,17 +48,21 @@ export interface UseCctvFullscreenReturn {
|
|
|
36
48
|
*/
|
|
37
49
|
isFullscreen: boolean;
|
|
38
50
|
/**
|
|
39
|
-
* 대상 전체화면
|
|
51
|
+
* 서비스가 대상 container에 전체창 확대 스타일을 적용할 상태. 브라우저 전체화면 상태와 구분한다.
|
|
52
|
+
*/
|
|
53
|
+
isViewportExpanded: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 대상 전체화면 또는 허용한 전체창 확대 진입 action
|
|
40
56
|
* @return 진입 요청 성공 여부
|
|
41
57
|
*/
|
|
42
58
|
enterFullscreen: () => Promise<boolean>;
|
|
43
59
|
/**
|
|
44
|
-
* 현재 대상 소유
|
|
60
|
+
* 현재 대상 소유 전체화면·전체창 확대 종료 action
|
|
45
61
|
* @return 종료 요청 성공 여부
|
|
46
62
|
*/
|
|
47
63
|
exitFullscreen: () => Promise<boolean>;
|
|
48
64
|
/**
|
|
49
|
-
* 현재 대상의
|
|
65
|
+
* 현재 대상의 전체화면·전체창 확대 진입·종료 action
|
|
50
66
|
* @return 요청 성공 여부
|
|
51
67
|
*/
|
|
52
68
|
toggleFullscreen: () => Promise<boolean>;
|