@ahnseungchan/webviewkit 1.2.0 → 2.0.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/README.md CHANGED
@@ -1,93 +1,115 @@
1
1
  # @ahnseungchan/webviewkit
2
2
 
3
- React **WebView·하이브리드 앱**용 스택(push/back) 네비게이션 라이브러리입니다.
3
+ React **WebView·하이브리드 앱**용 스택(push/back) 네비게이션 + 네이티브 브릿지(RN·Flutter) 라이브러리입니다.
4
4
  `history.pushState`와 CSS 슬라이드 전환으로 네이티브 앱에 가까운 화면 이동을 구현합니다.
5
5
 
6
6
  ## 설치
7
7
 
8
8
  ```bash
9
9
  npm install @ahnseungchan/webviewkit
10
- ```
11
-
12
- **Peer dependencies** (프로젝트에 직접 설치):
13
-
14
- ```bash
15
- npm install react react-dom @emotion/react @emotion/styled
10
+ npm install react react-dom @emotion/react @emotion/styled # peer (react >= 18)
16
11
  ```
17
12
 
18
13
  ## Quick Start
19
14
 
20
15
  ```tsx
21
- import { StackRouterProvider, useStackRouter } from "@ahnseungchan/webviewkit";
16
+ import {
17
+ StackRouterProvider,
18
+ useParams,
19
+ useStackRouter,
20
+ } from "@ahnseungchan/webviewkit";
22
21
 
23
22
  function Home() {
24
23
  const { push } = useStackRouter();
25
- return <button onClick={() => push("/about")}>About</button>;
24
+ return <button onClick={() => push("/detail/1?tab=info")}>Detail</button>;
26
25
  }
27
26
 
28
- function About() {
27
+ function Detail() {
28
+ const { id } = useParams<{ id: string }>();
29
29
  const { back } = useStackRouter();
30
- return <button onClick={back}>Back</button>;
30
+ return <button onClick={back}>Back from {id}</button>;
31
31
  }
32
32
 
33
33
  export default function App() {
34
34
  return (
35
35
  <StackRouterProvider
36
- maxWidth="600px"
37
- enableDevTools
38
36
  Activities={[
39
37
  { path: "/", element: <Home /> },
40
- { path: "/about", element: <About /> },
38
+ { path: "/detail/:id", element: <Detail /> },
41
39
  ]}
42
40
  initialActivity={{ path: "/", element: <Home /> }}
41
+ onBackAtRoot={() => window.close()}
43
42
  />
44
43
  );
45
44
  }
46
45
  ```
47
46
 
48
- ## Dev Stack Roadmap (1.2.0)
47
+ ## 동작 규칙
49
48
 
50
- 개발 모드에서 스택이 몇 장 쌓였는지 **눈으로** 확인할 수 있습니다.
51
-
52
- | 옵션 | 동작 |
49
+ | 상황 | 동작 |
50
+ |------|------|
51
+ | `push(path)` | 항상 새 엔트리. 전환 중이면 무시(`false`) |
52
+ | 같은 path 다시 push | 새 인스턴스가 쌓인다. 기존 페이지로 가려면 `popTo(path)` |
53
+ | 브라우저·하드웨어 back | popstate 로 스택 pop (URL·화면 항상 일치) |
54
+ | 루트에서 `back()` | history 를 건드리지 않고 `onBackAtRoot()` |
55
+ | 미등록 path | URL 변경 없음, dev 경고 + `onNotFound(path)` |
56
+ | 새로고침·딥링크 | `restoreStack="with-root"`(기본) → `[root, 대상]`, `"top-only"` → `[대상]` |
57
+ | 아래 깔린 페이지 | `visibility: hidden` + `inert` + `aria-hidden` |
58
+ | `prefers-reduced-motion` | 전환 0ms |
59
+
60
+ ## `StackRouterProvider` props
61
+
62
+ | prop | 기본 | 설명 |
63
+ |------|------|------|
64
+ | `initialActivity` | — | 루트 페이지. 마운트 시 1회만 읽음 |
65
+ | `Activities` | `[]` | `{ path, element }[]`. path 에 `:param` 사용 가능 |
66
+ | `transition` | `"slide"` | `"slide"` · `"fade"` · `"none"` |
67
+ | `duration` | `300` | 전환 ms |
68
+ | `restoreStack` | `"with-root"` | 딥링크 복원 정책 |
69
+ | `onBackAtRoot` / `onNotFound` | — | 콜백 |
70
+ | `isShowHeader` | `false` | `StackHeader` 렌더 |
71
+ | `safeArea` | `false` | `env(safe-area-inset-*)` padding |
72
+ | `maxWidth` | `"600px"` | 본문 최대 폭 |
73
+ | `enableDevTools` | dev 에서 true | 스택 로드맵 오버레이 |
74
+ | `bridge` | — | `true` 또는 `{ allowUrl, onError, emitStackChanged }` — 네이티브 연동 |
75
+
76
+ ## Hooks
77
+
78
+ | hook | 반환 |
53
79
  |------|------|
54
- | `enableDevTools={true}` | 우하단 로드맵 항상 표시 |
55
- | `enableDevTools={false}` | 숨김 |
56
- | 생략 | `development`에서만 표시, production에서는 숨김 |
80
+ | `useStackRouter()` | `push`, `replace`, `reset`, `back`, `pop(n)`, `popTo(path)`, `canGoBack`, `depth`, `currentPath`, `currentState`, `entries`, `isTransitioning` |
81
+ | `useActivity()` | 현재 페이지의 `{ key, path, pattern, params, query, data, index, isTop }` |
82
+ | `useParams()` | `params` |
57
83
 
58
- 로드맵에 표시되는 정보:
84
+ 테마는 CSS 변수로 바꿉니다: `--wvk-page-bg`, `--wvk-header-bg`, `--wvk-header-border`, `--wvk-header-color`, `--wvk-back-color`.
59
85
 
60
- - **stack × N** — 현재 스택 깊이
61
- - **점→선** — 쌓인 페이지 순서
62
- - **경로 목록** — 각 `path`, 최상단에 `← top`
86
+ ## Native Bridge
63
87
 
64
88
  ```tsx
65
- import { StackDevRoadmap } from "@ahnseungchan/webviewkit";
66
-
67
- // StackRouterProvider 없이 디버그 UI만 쓸 때
68
- <StackDevRoadmap
69
- pages={[{ path: "/" }, { path: "/detail" }]}
70
- currentPath="/detail"
71
- />
89
+ // 1) 스택 라우터와 네이티브를 바로 연결
90
+ <StackRouterProvider bridge {...props} />
91
+
92
+ // 2) Next.js 등 다른 라우터와 연결
93
+ <WebViewBridgeProvider stack={{ push, replace, pop, reset }} />
94
+
95
+ // 3) web → native 호출
96
+ import { BridgeError, getStackRouterBridge } from "@ahnseungchan/webviewkit";
97
+ try {
98
+ const { result } = (await getStackRouterBridge()?.push("/detail")) ?? {};
99
+ } catch (error) {
100
+ if (error instanceof BridgeError && error.code === "TIMEOUT") { /* ... */ }
101
+ }
72
102
  ```
73
103
 
74
- ## API
104
+ - inbound·outbound URL 은 기본적으로 **같은 origin 경로만** 허용합니다(`javascript:`·외부 URL 거부). `allowUrl` 로 교체할 수 있습니다.
105
+ - Flutter 브릿지가 늦게 주입돼도(`flutterInAppWebViewPlatformReady`) 감지 후 설치합니다.
106
+ - 하드웨어 back: 네이티브가 `back.request` 를 보내면 웹이 `{ handled }` 로 회신합니다. `handled: false` 면 앱이 종료·닫기를 판단합니다.
75
107
 
76
- | Export | 설명 |
77
- |--------|------|
78
- | `StackRouterProvider` | 스택 라우터 루트 |
79
- | `useStackRouter` | `push`, `back`, `replace`, `forward`, `visiblePages` |
80
- | `history` | `pushState` / `popstate` 래퍼 (SSR-safe) |
81
- | `BackRouter` | 뒤로가기 버튼 |
82
- | `StackHeader` | 경로 표시 헤더 |
83
- | `StackDevRoadmap` | 스택 로드맵 오버레이 |
84
- | `STACK_TRANSITION_MS` | 전환 ms 상수 (`500`) |
85
- | `Direction`, `HistoryAction` | 타입·enum |
108
+ 프로토콜 상세: [docs/native-bridge-protocol.md](https://github.com/Ahnseungc/WebViewKit/blob/main/docs/native-bridge-protocol.md)
86
109
 
87
- ## 골디 앱 연동 참고
110
+ ## 1.x → 2.0
88
111
 
89
- B2C WebView에서는 앱 브릿지(`stackRouterBridge`)와 함께 쓰는 경우가 많습니다.
90
- 이 패키지는 **웹 단독 스택 UI**이며, RN 브릿지와는 별도 레이어입니다.
112
+ [CHANGELOG](https://github.com/Ahnseungc/WebViewKit/blob/main/CHANGELOG.md#200---2026-11-04) 의 Breaking · Migration 을 참고하세요.
91
113
 
92
114
  ## License
93
115
 
@@ -0,0 +1,451 @@
1
+ import * as _emotion_react_jsx_runtime from '@emotion/react/jsx-runtime';
2
+ import React, { ReactNode } from 'react';
3
+
4
+ declare const BRIDGE_ERROR_CODES: {
5
+ readonly TIMEOUT: "TIMEOUT";
6
+ readonly BRIDGE_UNAVAILABLE: "BRIDGE_UNAVAILABLE";
7
+ readonly INVALID_PAYLOAD: "INVALID_PAYLOAD";
8
+ readonly URL_NOT_ALLOWED: "URL_NOT_ALLOWED";
9
+ readonly UNKNOWN_METHOD: "UNKNOWN_METHOD";
10
+ readonly HANDLER_ERROR: "HANDLER_ERROR";
11
+ };
12
+ type BridgeErrorCode = (typeof BRIDGE_ERROR_CODES)[keyof typeof BRIDGE_ERROR_CODES];
13
+ declare class BridgeError extends Error {
14
+ readonly code: BridgeErrorCode | string;
15
+ constructor(code: BridgeErrorCode | string, message: string, cause?: unknown);
16
+ toBody(): {
17
+ code: string;
18
+ message: string;
19
+ };
20
+ }
21
+
22
+ /** WebViewKit native bridge protocol (v1) */
23
+ declare const BRIDGE_PROTOCOL_VERSION: 1;
24
+ declare const BRIDGE_NAMESPACE: "webviewkit";
25
+ type BridgeHostKind = "react-native" | "flutter" | "browser" | "unknown";
26
+ /** Stack navigation — maps to common `stackPush` / `stackPop` WebView handlers */
27
+ declare const STACK_BRIDGE_METHODS: {
28
+ readonly PUSH: "stack.push";
29
+ readonly REPLACE: "stack.replace";
30
+ readonly POP: "stack.pop";
31
+ readonly RESET: "stack.reset";
32
+ /** web → native: 웹 스택 깊이 변경 알림 */
33
+ readonly CHANGED: "stack.changed";
34
+ };
35
+ type StackBridgeMethod = (typeof STACK_BRIDGE_METHODS)[keyof typeof STACK_BRIDGE_METHODS];
36
+ /** native → web: 하드웨어 back. 응답 payload `{ handled }` */
37
+ declare const BACK_REQUEST_METHOD: "back.request";
38
+ /** 요청에 대한 응답 envelope method (`replyTo` 필수) */
39
+ declare const BRIDGE_RESPONSE_METHOD: "bridge.response";
40
+ type StackMeta = Record<string, unknown> | null;
41
+ type StackPushPayload = {
42
+ url: string;
43
+ meta?: StackMeta;
44
+ };
45
+ type StackReplacePayload = StackPushPayload;
46
+ type StackResetPayload = {
47
+ url: string;
48
+ meta?: StackMeta;
49
+ };
50
+ type StackChangedPayload = {
51
+ depth: number;
52
+ path: string;
53
+ };
54
+ type BackRequestResult = {
55
+ handled: boolean;
56
+ };
57
+ type BridgeErrorBody = {
58
+ code: BridgeErrorCode | string;
59
+ message: string;
60
+ };
61
+ type BridgeEnvelope<TPayload = unknown> = {
62
+ v: typeof BRIDGE_PROTOCOL_VERSION;
63
+ ns: typeof BRIDGE_NAMESPACE;
64
+ id: string;
65
+ method: string;
66
+ payload?: TPayload;
67
+ error?: BridgeErrorBody;
68
+ /** 응답일 때 원 요청 id */
69
+ replyTo?: string;
70
+ };
71
+ declare function createBridgeId(): string;
72
+ declare function createBridgeEnvelope<TPayload>(method: string, payload?: TPayload, id?: string): BridgeEnvelope<TPayload>;
73
+ declare function createBridgeResponse<TPayload>(request: Pick<BridgeEnvelope, "id">, result: {
74
+ payload?: TPayload;
75
+ error?: BridgeErrorBody;
76
+ }): BridgeEnvelope<TPayload>;
77
+ declare function isBridgeEnvelope(value: unknown): value is BridgeEnvelope;
78
+ declare function parseBridgeMessage(raw: string | unknown): BridgeEnvelope | null;
79
+ declare function serializeBridgeMessage(msg: BridgeEnvelope): string;
80
+ /** Flutter InAppWebView legacy handler names */
81
+ declare const FLUTTER_LEGACY_STACK_HANDLERS: {
82
+ readonly push: "stackPush";
83
+ readonly replace: "stackReplace";
84
+ readonly pop: "stackPop";
85
+ readonly reset: "stackReset";
86
+ };
87
+ /** Unified handler name when native listens on one channel */
88
+ declare const FLUTTER_UNIFIED_HANDLER = "webviewkitBridge";
89
+ /** React Native receives messages via this global handler name suggestion */
90
+ declare const RN_BRIDGE_HANDLER = "webviewkitBridge";
91
+
92
+ /** flutter_inappwebview 가 JS 브릿지를 붙인 뒤 window 에 dispatch 하는 이벤트 */
93
+ declare const FLUTTER_READY_EVENT = "flutterInAppWebViewPlatformReady";
94
+ declare function detectBridgeHost(): BridgeHostKind;
95
+ declare function isNativeBridgeHost(host: BridgeHostKind): boolean;
96
+ interface WaitForBridgeHostOptions {
97
+ timeoutMs?: number;
98
+ signal?: AbortSignal;
99
+ }
100
+ /**
101
+ * 네이티브 브릿지가 늦게 주입되는 경우(Flutter platformReady 이전)를 기다린다.
102
+ * 즉시 감지되면 바로, 아니면 ready 이벤트·폴링으로 재감지. 타임아웃·abort 시 현재 판정값.
103
+ */
104
+ declare function waitForBridgeHost({ timeoutMs, signal, }?: WaitForBridgeHostOptions): Promise<BridgeHostKind>;
105
+
106
+ type BridgeTransportOptions = {
107
+ /** Flutter: use stackPush/stackPop handlers (default true) */
108
+ flutterLegacyHandlers?: boolean;
109
+ };
110
+ interface BridgeTransport {
111
+ readonly host: BridgeHostKind;
112
+ /** Flutter 는 callHandler 반환값, RN·browser 는 undefined */
113
+ send(envelope: BridgeEnvelope): Promise<unknown>;
114
+ }
115
+ declare function createBridgeTransport(host?: BridgeHostKind, options?: BridgeTransportOptions): BridgeTransport;
116
+
117
+ type AllowUrl = (url: string) => boolean;
118
+ /**
119
+ * 기본 정책: 같은 앱 안의 경로만 허용.
120
+ * - `/a/b?x=1` 같은 상대 경로 (`//host` 프로토콜 상대 URL 제외)
121
+ * - 현재 origin 과 같은 절대 http(s) URL
122
+ * `javascript:`, `data:`, 외부 도메인은 거부한다.
123
+ */
124
+ declare function isSafeStackUrl(url: string): boolean;
125
+ /** 같은 origin 절대 URL → 경로로 정규화. 그 밖(상대 경로·커스텀 스킴)은 그대로 */
126
+ declare function toStackPath(url: string): string;
127
+ /** payload 에서 url 을 꺼내 형식·정책 검증 후 경로로 반환. 실패 시 BridgeError */
128
+ declare function assertStackUrl(payload: unknown, allowUrl?: AllowUrl): string;
129
+
130
+ type WebViewBridgeClientOptions = BridgeTransportOptions & {
131
+ transport?: BridgeTransport;
132
+ /** outbound stack URL 검증 (기본 isSafeStackUrl) */
133
+ allowUrl?: AllowUrl;
134
+ /** 요청 기본 타임아웃 ms */
135
+ timeoutMs?: number;
136
+ };
137
+ interface BridgeRequestOptions {
138
+ timeoutMs?: number;
139
+ /**
140
+ * RN: 네이티브가 `bridge.response`(replyTo) 로 회신할 때까지 기다린다.
141
+ * 기본 false — 전송 즉시 resolve (1.x 네이티브 호환).
142
+ */
143
+ expectResponse?: boolean;
144
+ }
145
+ interface BridgeResult<TResult = unknown> {
146
+ /** 전송한 요청 envelope */
147
+ envelope: BridgeEnvelope;
148
+ /** Flutter callHandler 반환값 또는 RN 응답 payload */
149
+ result?: TResult;
150
+ }
151
+ declare class WebViewBridgeClient {
152
+ readonly host: ReturnType<typeof detectBridgeHost>;
153
+ private readonly transport;
154
+ private readonly allowUrl?;
155
+ private readonly timeoutMs;
156
+ constructor(options?: WebViewBridgeClientOptions);
157
+ /** 응답을 기다리지 않는 단방향 전송 */
158
+ send(envelope: BridgeEnvelope): Promise<unknown>;
159
+ request<TPayload = unknown, TResult = unknown>(method: string, payload?: TPayload, options?: BridgeRequestOptions): Promise<BridgeResult<TResult>>;
160
+ private stackRequest;
161
+ stackPush(url: string, meta?: StackMeta, options?: BridgeRequestOptions): Promise<BridgeResult<unknown>>;
162
+ stackReplace(url: string, meta?: StackMeta, options?: BridgeRequestOptions): Promise<BridgeResult<unknown>>;
163
+ stackPop(options?: BridgeRequestOptions): Promise<BridgeResult<unknown>>;
164
+ stackReset(url: string, meta?: StackMeta, options?: BridgeRequestOptions): Promise<BridgeResult<unknown>>;
165
+ /** web → native 웹 스택 변경 알림 (응답 없음) */
166
+ notifyStackChanged(payload: StackChangedPayload): Promise<unknown>;
167
+ }
168
+ declare function createWebViewBridgeClient(options?: WebViewBridgeClientOptions): WebViewBridgeClient;
169
+
170
+ type StackRouterBridge = {
171
+ push: (url: string, meta?: StackMeta, options?: BridgeRequestOptions) => Promise<BridgeResult>;
172
+ replace: (url: string, meta?: StackMeta, options?: BridgeRequestOptions) => Promise<BridgeResult>;
173
+ pop: (options?: BridgeRequestOptions) => Promise<BridgeResult>;
174
+ reset: (url: string, meta?: StackMeta, options?: BridgeRequestOptions) => Promise<BridgeResult>;
175
+ };
176
+ type CreateStackRouterBridgeOptions = BridgeTransportOptions & {
177
+ allowUrl?: AllowUrl;
178
+ timeoutMs?: number;
179
+ };
180
+ /**
181
+ * Web → native stack navigation (Flutter `callHandler` / RN `postMessage`).
182
+ * Returns null in browser-only environments.
183
+ */
184
+ declare function createStackRouterBridge(options?: CreateStackRouterBridgeOptions): StackRouterBridge | null;
185
+ /** `window.stackRouterBridge` → 모듈 캐시 → 새로 생성 후 캐시 */
186
+ declare function getStackRouterBridge(): StackRouterBridge | null;
187
+ declare function installStackRouterBridge(options?: CreateStackRouterBridgeOptions): StackRouterBridge | null;
188
+ /** 테스트·호스트 전환용 캐시 초기화 */
189
+ declare function resetStackRouterBridgeCache(): void;
190
+
191
+ type FlutterInAppWebViewBridge = {
192
+ callHandler: (handlerName: string, ...args: unknown[]) => Promise<unknown>;
193
+ };
194
+ declare global {
195
+ interface Window {
196
+ ReactNativeWebView?: {
197
+ postMessage: (message: string) => void;
198
+ };
199
+ flutter_inappwebview?: FlutterInAppWebViewBridge;
200
+ /** Native → web: unified protocol ingress */
201
+ webviewkitBridge?: {
202
+ postMessage: (message: string | unknown) => void;
203
+ };
204
+ /** Web → native stack */
205
+ stackRouterBridge?: StackRouterBridge;
206
+ /** Native → web stack */
207
+ stackPush?: (url: string, meta?: StackMeta) => Promise<boolean>;
208
+ stackReplace?: (url: string, meta?: StackMeta) => Promise<boolean>;
209
+ stackPop?: () => Promise<boolean>;
210
+ stackReset?: (url: string, meta?: StackMeta) => Promise<boolean>;
211
+ }
212
+ }
213
+
214
+ type HandlerResult = boolean | Promise<boolean>;
215
+ type StackRouterHostHandlers = {
216
+ push: (url: string, meta?: StackMeta) => HandlerResult;
217
+ replace: (url: string, meta?: StackMeta) => HandlerResult;
218
+ pop: () => HandlerResult;
219
+ reset: (url: string, meta?: StackMeta) => HandlerResult;
220
+ };
221
+ type InstallBridgeHostOptions = {
222
+ /** Native → web stack navigation */
223
+ stack?: StackRouterHostHandlers;
224
+ /** Custom method handlers keyed by envelope `method` */
225
+ handlers?: Record<string, (payload: unknown, envelope: BridgeEnvelope) => HandlerResult>;
226
+ /** native → web `back.request` (하드웨어 back). true = 웹에서 처리함 */
227
+ onBackRequest?: () => HandlerResult;
228
+ /** Called for every valid envelope before routing */
229
+ onMessage?: (envelope: BridgeEnvelope) => void;
230
+ /** 검증 실패·핸들러 예외 (throw 대신 여기로 전달) */
231
+ onError?: (error: BridgeError, envelope?: BridgeEnvelope) => void;
232
+ /** inbound stack URL 검증 (기본 isSafeStackUrl) */
233
+ allowUrl?: AllowUrl;
234
+ /** 모든 inbound 요청에 `bridge.response` 회신 (back.request 는 항상 회신) */
235
+ replyToNative?: boolean;
236
+ /** 회신용 transport 옵션 */
237
+ transport?: BridgeTransportOptions;
238
+ /**
239
+ * `window.stackRouterBridge` 설치 (web → native).
240
+ * true 면 `createStackRouterBridge()` 로 생성한다.
241
+ */
242
+ exposeStackRouterBridge?: StackRouterBridge | boolean;
243
+ /** Register `window.stackPush` / `stackPop` for Flutter native→web (default true when `stack`) */
244
+ legacyInboundHandlers?: boolean;
245
+ };
246
+ /** inbound 메시지 처리. 절대 reject 하지 않는다 (실패는 false + onError) */
247
+ declare function handleBridgeMessage(raw: string | unknown, options: InstallBridgeHostOptions): Promise<boolean>;
248
+ type BridgeHostCleanup = () => void;
249
+ declare function installBridgeHost(options: InstallBridgeHostOptions): BridgeHostCleanup;
250
+ /** Emit a protocol envelope from web (e.g. for tests or custom native listeners) */
251
+ declare function emitBridgeEnvelope(method: string, payload?: unknown): BridgeEnvelope;
252
+
253
+ type WebViewBridgeProviderProps = {
254
+ children?: ReactNode;
255
+ /** Native → web stack handlers (e.g. Next.js `router.push`) */
256
+ stack?: StackRouterHostHandlers;
257
+ transport?: CreateStackRouterBridgeOptions;
258
+ /** Install `window.stackRouterBridge` for web → native */
259
+ enableOutboundBridge?: boolean;
260
+ hostOptions?: Omit<InstallBridgeHostOptions, "stack" | "exposeStackRouterBridge" | "legacyInboundHandlers">;
261
+ };
262
+ /**
263
+ * Mount once near the app root (Next.js `layout`, SPA `App`).
264
+ * Wires protocol ingress + optional legacy window stack handlers.
265
+ * `stack`·`hostOptions` 는 최신 값을 ref 로 읽으므로 인라인 객체여도 재설치하지 않는다.
266
+ */
267
+ declare function WebViewBridgeProvider({ children, stack, transport, enableOutboundBridge, hostOptions, }: WebViewBridgeProviderProps): _emotion_react_jsx_runtime.JSX.Element;
268
+
269
+ /** push/back 전환 기본 시간 (ms). `StackRouterProvider` `duration` 으로 조정 */
270
+ declare const STACK_TRANSITION_MS = 300;
271
+ /** `history.back()` 이 popstate 를 기다리는 최대 시간 */
272
+ declare const BACK_TIMEOUT_MS = 1000;
273
+ /** 브릿지 요청 응답 기본 타임아웃 */
274
+ declare const BRIDGE_TIMEOUT_MS = 5000;
275
+
276
+ interface StackBridgeSyncOptions {
277
+ allowUrl?: AllowUrl;
278
+ onError?: (error: BridgeError, envelope?: BridgeEnvelope) => void;
279
+ /** 웹 스택 변경 시 `stack.changed` 전송 (기본 true) */
280
+ emitStackChanged?: boolean;
281
+ }
282
+
283
+ interface Activity {
284
+ /** 정적 경로 또는 동적 세그먼트 패턴 (`/detail/:id`) */
285
+ path: string;
286
+ element: React.ReactNode;
287
+ }
288
+ type StackTransitionType = "slide" | "fade" | "none";
289
+ type RestoreStackPolicy = "with-root" | "top-only";
290
+ interface StackRouterProviderProps {
291
+ initialActivity: Activity;
292
+ Activities?: Activity[];
293
+ maxWidth?: string;
294
+ /** true면 상단에 `StackHeader` 렌더 */
295
+ isShowHeader?: boolean;
296
+ /** 개발 시 스택 로드맵 오버레이 (기본: NODE_ENV === "development") */
297
+ enableDevTools?: boolean;
298
+ transition?: StackTransitionType;
299
+ /** 전환 시간 ms (기본 STACK_TRANSITION_MS) */
300
+ duration?: number;
301
+ /** 새로고침·딥링크 진입 시 스택 복원 정책 (기본 "with-root") */
302
+ restoreStack?: RestoreStackPolicy;
303
+ /** env(safe-area-inset-*) padding 적용 */
304
+ safeArea?: boolean;
305
+ /** 루트에서 back 했을 때 (앱 종료·WebView 닫기 위임) */
306
+ onBackAtRoot?: () => void;
307
+ /** 등록되지 않은 경로로 이동하려 할 때 */
308
+ onNotFound?: (path: string) => void;
309
+ /** 네이티브 브릿지 연동 */
310
+ bridge?: boolean | StackBridgeSyncOptions;
311
+ }
312
+ interface VisiblePage {
313
+ path: string;
314
+ element: React.ReactNode;
315
+ }
316
+ declare enum Direction {
317
+ FORWARD = "forward",
318
+ BACKWARD = "backward"
319
+ }
320
+ type DirectionType = Direction.FORWARD | Direction.BACKWARD;
321
+ declare enum HistoryAction {
322
+ PUSH = "PUSH",
323
+ POP = "POP",
324
+ REPLACE = "REPLACE"
325
+ }
326
+ interface HistoryState {
327
+ /** pathname + search */
328
+ path: string;
329
+ data?: unknown;
330
+ action?: HistoryAction;
331
+ /** 스택 엔트리 고유 key */
332
+ key?: string;
333
+ /** 스택 내 위치 (0 = root) */
334
+ index?: number;
335
+ }
336
+ type HistoryListener = (state: HistoryState) => void;
337
+
338
+ type HistoryEntryState = Omit<HistoryState, "path" | "action">;
339
+ /**
340
+ * `window.history` 래퍼.
341
+ * - 리스너는 브라우저 popstate(뒤로·앞으로·`go`)에서만 호출된다.
342
+ * - push/replace 는 동기 실행이며 리스너를 호출하지 않는다 (호출자가 상태를 직접 갱신).
343
+ * - popstate 구독은 첫 리스너 등록 시점에 건다 (import 부작용 없음).
344
+ */
345
+ declare const history: {
346
+ push: (path: string, state?: HistoryEntryState) => void;
347
+ replace: (path: string, state?: HistoryEntryState) => void;
348
+ /** `history.go(delta)` 후 popstate 가 오면 true, BACK_TIMEOUT_MS 안에 오지 않으면 false */
349
+ travel: (delta: number) => Promise<boolean>;
350
+ back: () => Promise<boolean>;
351
+ forward: () => void;
352
+ go: (delta: number) => void;
353
+ getCurrentState: () => HistoryState;
354
+ addListener: (listener: HistoryListener) => () => boolean;
355
+ };
356
+
357
+ interface StackDevRoadmapProps {
358
+ /** 스택에 쌓인 페이지 경로 (아래 → 위 순) */
359
+ pages: {
360
+ path: string;
361
+ }[];
362
+ currentPath: string;
363
+ /** false면 렌더하지 않음 */
364
+ visible?: boolean;
365
+ }
366
+ /**
367
+ * 개발 환경에서 스택 깊이·경로를 눈으로 확인하는 로드맵 오버레이.
368
+ * StackRouterProvider `enableDevTools` 와 함께 사용합니다.
369
+ */
370
+ declare function StackDevRoadmap({ pages, currentPath, visible, }: StackDevRoadmapProps): _emotion_react_jsx_runtime.JSX.Element | null;
371
+
372
+ interface BackRouterProps {
373
+ /** 스크린리더 라벨 */
374
+ label?: string;
375
+ }
376
+ declare const BackRouter: ({ label }: BackRouterProps) => _emotion_react_jsx_runtime.JSX.Element;
377
+
378
+ interface StackHeaderProps {
379
+ /** 기본: 현재 path */
380
+ title?: React.ReactNode;
381
+ }
382
+ declare const Header: ({ title }: StackHeaderProps) => _emotion_react_jsx_runtime.JSX.Element;
383
+
384
+ interface ActivityContextValue {
385
+ key: string;
386
+ path: string;
387
+ pattern: string;
388
+ params: Record<string, string>;
389
+ query: Record<string, string>;
390
+ data?: unknown;
391
+ /** 스택 내 위치 (0 = root) */
392
+ index: number;
393
+ isTop: boolean;
394
+ }
395
+ /** 현재 페이지(Activity) 자신의 path·params·query·data */
396
+ declare function useActivity(): ActivityContextValue;
397
+ /** `/detail/:id` → `{ id }` */
398
+ declare function useParams<TParams extends Record<string, string> = Record<string, string>>(): TParams;
399
+
400
+ interface PathMatch {
401
+ params: Record<string, string>;
402
+ query: Record<string, string>;
403
+ }
404
+ declare function parseQuery(search: string): Record<string, string>;
405
+ /**
406
+ * `pattern`(`/detail/:id`)과 `url`(`/detail/3?tab=a#x`)을 비교한다.
407
+ * 끝의 `/`와 `#hash`는 무시한다. 불일치면 null.
408
+ */
409
+ declare function matchPath(pattern: string, url: string): PathMatch | null;
410
+ /** 등록 순서대로 첫 매칭 Activity */
411
+ declare function findActivity(activities: Activity[], url: string): {
412
+ activity: Activity;
413
+ match: PathMatch;
414
+ } | null;
415
+
416
+ interface StackEntry {
417
+ key: string;
418
+ /** 실제 URL (pathname + search) */
419
+ path: string;
420
+ /** 매칭된 Activity path 패턴 */
421
+ pattern: string;
422
+ params: Record<string, string>;
423
+ query: Record<string, string>;
424
+ data?: unknown;
425
+ }
426
+
427
+ interface StackRouterContextValue {
428
+ currentPath: string;
429
+ /** top 엔트리의 history state */
430
+ currentState: HistoryState;
431
+ depth: number;
432
+ canGoBack: boolean;
433
+ isTransitioning: boolean;
434
+ entries: StackEntry[];
435
+ /** 1.x 호환 */
436
+ visiblePages: VisiblePage[];
437
+ Activities: Activity[];
438
+ initialActivity: Activity;
439
+ push: (path: string, data?: unknown) => boolean;
440
+ replace: (path: string, data?: unknown) => boolean;
441
+ reset: (path: string, data?: unknown) => boolean;
442
+ back: () => boolean;
443
+ pop: (count?: number) => boolean;
444
+ popTo: (path: string) => boolean;
445
+ /** @deprecated 스택과 동기화되지 않는다. 브라우저 forward 는 popstate 로 처리된다 */
446
+ forward: () => void;
447
+ }
448
+ declare const useStackRouter: () => StackRouterContextValue;
449
+ declare const StackRouterProvider: ({ initialActivity, Activities, maxWidth, isShowHeader, enableDevTools, transition, duration, restoreStack, safeArea, onBackAtRoot, onNotFound, bridge, }: StackRouterProviderProps) => _emotion_react_jsx_runtime.JSX.Element;
450
+
451
+ export { type Activity, type ActivityContextValue, type AllowUrl, BACK_REQUEST_METHOD, BACK_TIMEOUT_MS, BRIDGE_ERROR_CODES, BRIDGE_NAMESPACE, BRIDGE_PROTOCOL_VERSION, BRIDGE_RESPONSE_METHOD, BRIDGE_TIMEOUT_MS, type BackRequestResult, BackRouter, type BridgeEnvelope, BridgeError, type BridgeErrorBody, type BridgeErrorCode, type BridgeHostCleanup, type BridgeHostKind, type BridgeRequestOptions, type BridgeResult, type BridgeTransport, type BridgeTransportOptions, type CreateStackRouterBridgeOptions, Direction, type DirectionType, FLUTTER_LEGACY_STACK_HANDLERS, FLUTTER_READY_EVENT, FLUTTER_UNIFIED_HANDLER, type FlutterInAppWebViewBridge, HistoryAction, type HistoryListener, type HistoryState, type InstallBridgeHostOptions, RN_BRIDGE_HANDLER, type RestoreStackPolicy, STACK_BRIDGE_METHODS, STACK_TRANSITION_MS, type StackBridgeMethod, type StackBridgeSyncOptions, type StackChangedPayload, StackDevRoadmap, type StackEntry, Header as StackHeader, type StackMeta, type StackPushPayload, type StackReplacePayload, type StackResetPayload, type StackRouterBridge, type StackRouterContextValue, type StackRouterHostHandlers, StackRouterProvider, type StackRouterProviderProps, type StackTransitionType, type VisiblePage, type WaitForBridgeHostOptions, WebViewBridgeClient, type WebViewBridgeClientOptions, WebViewBridgeProvider, type WebViewBridgeProviderProps, assertStackUrl, createBridgeEnvelope, createBridgeId, createBridgeResponse, createBridgeTransport, createStackRouterBridge, createWebViewBridgeClient, detectBridgeHost, emitBridgeEnvelope, findActivity, getStackRouterBridge, handleBridgeMessage, history, installBridgeHost, installStackRouterBridge, isBridgeEnvelope, isNativeBridgeHost, isSafeStackUrl, matchPath, parseBridgeMessage, parseQuery, resetStackRouterBridgeCache, serializeBridgeMessage, toStackPath, useActivity, useParams, useStackRouter, waitForBridgeHost };