@ahnseungchan/webviewkit 1.3.0 → 2.1.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 +68 -61
- package/dist/index.d.mts +454 -0
- package/dist/index.d.ts +454 -11
- package/dist/index.js +1659 -45
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1574 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +26 -6
- package/dist/bridge/WebViewBridgeProvider.d.ts +0 -18
- package/dist/bridge/WebViewBridgeProvider.d.ts.map +0 -1
- package/dist/bridge/WebViewBridgeProvider.js +0 -26
- package/dist/bridge/client.d.ts +0 -20
- package/dist/bridge/client.d.ts.map +0 -1
- package/dist/bridge/client.js +0 -48
- package/dist/bridge/detect-host.d.ts +0 -4
- package/dist/bridge/detect-host.d.ts.map +0 -1
- package/dist/bridge/detect-host.js +0 -19
- package/dist/bridge/host.d.ts +0 -31
- package/dist/bridge/host.d.ts.map +0 -1
- package/dist/bridge/host.js +0 -77
- package/dist/bridge/index.d.ts +0 -10
- package/dist/bridge/index.d.ts.map +0 -1
- package/dist/bridge/index.js +0 -33
- package/dist/bridge/protocol.d.ts +0 -51
- package/dist/bridge/protocol.d.ts.map +0 -1
- package/dist/bridge/protocol.js +0 -65
- package/dist/bridge/stack-router-bridge.d.ts +0 -17
- package/dist/bridge/stack-router-bridge.d.ts.map +0 -1
- package/dist/bridge/stack-router-bridge.js +0 -38
- package/dist/bridge/transport.d.ts +0 -11
- package/dist/bridge/transport.d.ts.map +0 -1
- package/dist/bridge/transport.js +0 -65
- package/dist/constants.d.ts +0 -3
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -5
- package/dist/history.d.ts +0 -11
- package/dist/history.d.ts.map +0 -1
- package/dist/history.js +0 -91
- package/dist/index.d.ts.map +0 -1
- package/dist/stack-dev/stack-dev-roadmap.d.ts +0 -15
- package/dist/stack-dev/stack-dev-roadmap.d.ts.map +0 -1
- package/dist/stack-dev/stack-dev-roadmap.js +0 -66
- package/dist/stack-router/_components/back-router.d.ts +0 -3
- package/dist/stack-router/_components/back-router.d.ts.map +0 -1
- package/dist/stack-router/_components/back-router.js +0 -39
- package/dist/stack-router/_components/header.d.ts +0 -3
- package/dist/stack-router/_components/header.d.ts.map +0 -1
- package/dist/stack-router/_components/header.js +0 -42
- package/dist/stack-router/stack-router-provider.d.ts +0 -27
- package/dist/stack-router/stack-router-provider.d.ts.map +0 -1
- package/dist/stack-router/stack-router-provider.js +0 -227
- package/dist/type.d.ts +0 -37
- package/dist/type.d.ts.map +0 -1
- package/dist/type.js +0 -14
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1574 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var STACK_TRANSITION_MS = 500;
|
|
3
|
+
var TRANSITION_END_GRACE_MS = 50;
|
|
4
|
+
var BACK_TIMEOUT_MS = 1e3;
|
|
5
|
+
var BRIDGE_TIMEOUT_MS = 5e3;
|
|
6
|
+
var BRIDGE_HOST_WAIT_MS = 3e3;
|
|
7
|
+
var BRIDGE_HOST_POLL_MS = 100;
|
|
8
|
+
|
|
9
|
+
// src/bridge/detect-host.ts
|
|
10
|
+
var FLUTTER_READY_EVENT = "flutterInAppWebViewPlatformReady";
|
|
11
|
+
function detectBridgeHost() {
|
|
12
|
+
var _a, _b;
|
|
13
|
+
if (typeof window === "undefined") return "unknown";
|
|
14
|
+
if ((_a = window.ReactNativeWebView) == null ? void 0 : _a.postMessage) {
|
|
15
|
+
return "react-native";
|
|
16
|
+
}
|
|
17
|
+
if ((_b = window.flutter_inappwebview) == null ? void 0 : _b.callHandler) {
|
|
18
|
+
return "flutter";
|
|
19
|
+
}
|
|
20
|
+
return "browser";
|
|
21
|
+
}
|
|
22
|
+
function isNativeBridgeHost(host) {
|
|
23
|
+
return host === "react-native" || host === "flutter";
|
|
24
|
+
}
|
|
25
|
+
function waitForBridgeHost({
|
|
26
|
+
timeoutMs = BRIDGE_HOST_WAIT_MS,
|
|
27
|
+
signal
|
|
28
|
+
} = {}) {
|
|
29
|
+
const host = detectBridgeHost();
|
|
30
|
+
if (isNativeBridgeHost(host) || host === "unknown") {
|
|
31
|
+
return Promise.resolve(host);
|
|
32
|
+
}
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const finish = () => {
|
|
35
|
+
clearInterval(poll);
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
window.removeEventListener(FLUTTER_READY_EVENT, check);
|
|
38
|
+
signal == null ? void 0 : signal.removeEventListener("abort", finish);
|
|
39
|
+
resolve(detectBridgeHost());
|
|
40
|
+
};
|
|
41
|
+
const check = () => {
|
|
42
|
+
if (isNativeBridgeHost(detectBridgeHost())) finish();
|
|
43
|
+
};
|
|
44
|
+
const poll = setInterval(check, BRIDGE_HOST_POLL_MS);
|
|
45
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
46
|
+
window.addEventListener(FLUTTER_READY_EVENT, check);
|
|
47
|
+
signal == null ? void 0 : signal.addEventListener("abort", finish);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/bridge/errors.ts
|
|
52
|
+
var BRIDGE_ERROR_CODES = {
|
|
53
|
+
TIMEOUT: "TIMEOUT",
|
|
54
|
+
BRIDGE_UNAVAILABLE: "BRIDGE_UNAVAILABLE",
|
|
55
|
+
INVALID_PAYLOAD: "INVALID_PAYLOAD",
|
|
56
|
+
URL_NOT_ALLOWED: "URL_NOT_ALLOWED",
|
|
57
|
+
UNKNOWN_METHOD: "UNKNOWN_METHOD",
|
|
58
|
+
HANDLER_ERROR: "HANDLER_ERROR"
|
|
59
|
+
};
|
|
60
|
+
var BridgeError = class extends Error {
|
|
61
|
+
constructor(code, message, cause) {
|
|
62
|
+
super(message);
|
|
63
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
64
|
+
this.name = "BridgeError";
|
|
65
|
+
this.code = code;
|
|
66
|
+
if (cause !== void 0) {
|
|
67
|
+
this.cause = cause;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
toBody() {
|
|
71
|
+
return { code: this.code, message: this.message };
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
function toBridgeError(error, fallbackCode) {
|
|
75
|
+
if (error instanceof BridgeError) return error;
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
return new BridgeError(fallbackCode, message, error);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/bridge/pending-responses.ts
|
|
81
|
+
var pending = /* @__PURE__ */ new Map();
|
|
82
|
+
function waitForResponse(requestId, timeoutMs) {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
pending.delete(requestId);
|
|
86
|
+
reject(
|
|
87
|
+
new BridgeError(
|
|
88
|
+
BRIDGE_ERROR_CODES.TIMEOUT,
|
|
89
|
+
`No response for ${requestId} within ${timeoutMs}ms`
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
}, timeoutMs);
|
|
93
|
+
pending.set(requestId, { resolve, reject, timer });
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function settleResponse(envelope) {
|
|
97
|
+
if (!envelope.replyTo) return false;
|
|
98
|
+
const request = pending.get(envelope.replyTo);
|
|
99
|
+
if (!request) return false;
|
|
100
|
+
clearTimeout(request.timer);
|
|
101
|
+
pending.delete(envelope.replyTo);
|
|
102
|
+
if (envelope.error) {
|
|
103
|
+
request.reject(
|
|
104
|
+
new BridgeError(envelope.error.code, envelope.error.message)
|
|
105
|
+
);
|
|
106
|
+
} else {
|
|
107
|
+
request.resolve(envelope);
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
function cancelResponse(requestId) {
|
|
112
|
+
const request = pending.get(requestId);
|
|
113
|
+
if (!request) return;
|
|
114
|
+
clearTimeout(request.timer);
|
|
115
|
+
pending.delete(requestId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/bridge/protocol.ts
|
|
119
|
+
var BRIDGE_PROTOCOL_VERSION = 1;
|
|
120
|
+
var BRIDGE_NAMESPACE = "webviewkit";
|
|
121
|
+
var STACK_BRIDGE_METHODS = {
|
|
122
|
+
PUSH: "stack.push",
|
|
123
|
+
REPLACE: "stack.replace",
|
|
124
|
+
POP: "stack.pop",
|
|
125
|
+
RESET: "stack.reset",
|
|
126
|
+
/** web → native: 웹 스택 깊이 변경 알림 */
|
|
127
|
+
CHANGED: "stack.changed"
|
|
128
|
+
};
|
|
129
|
+
var BACK_REQUEST_METHOD = "back.request";
|
|
130
|
+
var BRIDGE_RESPONSE_METHOD = "bridge.response";
|
|
131
|
+
function createBridgeId() {
|
|
132
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
133
|
+
return crypto.randomUUID();
|
|
134
|
+
}
|
|
135
|
+
return `wv-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
136
|
+
}
|
|
137
|
+
function createBridgeEnvelope(method, payload, id) {
|
|
138
|
+
return {
|
|
139
|
+
v: BRIDGE_PROTOCOL_VERSION,
|
|
140
|
+
ns: BRIDGE_NAMESPACE,
|
|
141
|
+
id: id != null ? id : createBridgeId(),
|
|
142
|
+
method,
|
|
143
|
+
...payload !== void 0 ? { payload } : {}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function createBridgeResponse(request, result) {
|
|
147
|
+
return {
|
|
148
|
+
...createBridgeEnvelope(BRIDGE_RESPONSE_METHOD, result.payload),
|
|
149
|
+
replyTo: request.id,
|
|
150
|
+
...result.error ? { error: result.error } : {}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function isBridgeEnvelope(value) {
|
|
154
|
+
if (!value || typeof value !== "object") return false;
|
|
155
|
+
const o = value;
|
|
156
|
+
return o.v === BRIDGE_PROTOCOL_VERSION && o.ns === BRIDGE_NAMESPACE && typeof o.id === "string" && typeof o.method === "string";
|
|
157
|
+
}
|
|
158
|
+
function parseBridgeMessage(raw) {
|
|
159
|
+
try {
|
|
160
|
+
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
161
|
+
return isBridgeEnvelope(parsed) ? parsed : null;
|
|
162
|
+
} catch (e) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function serializeBridgeMessage(msg) {
|
|
167
|
+
return JSON.stringify(msg);
|
|
168
|
+
}
|
|
169
|
+
var FLUTTER_LEGACY_STACK_HANDLERS = {
|
|
170
|
+
push: "stackPush",
|
|
171
|
+
replace: "stackReplace",
|
|
172
|
+
pop: "stackPop",
|
|
173
|
+
reset: "stackReset"
|
|
174
|
+
};
|
|
175
|
+
var FLUTTER_UNIFIED_HANDLER = "webviewkitBridge";
|
|
176
|
+
var RN_BRIDGE_HANDLER = "webviewkitBridge";
|
|
177
|
+
|
|
178
|
+
// src/bridge/transport.ts
|
|
179
|
+
var LEGACY_HANDLER_BY_METHOD = {
|
|
180
|
+
[STACK_BRIDGE_METHODS.PUSH]: FLUTTER_LEGACY_STACK_HANDLERS.push,
|
|
181
|
+
[STACK_BRIDGE_METHODS.REPLACE]: FLUTTER_LEGACY_STACK_HANDLERS.replace,
|
|
182
|
+
[STACK_BRIDGE_METHODS.POP]: FLUTTER_LEGACY_STACK_HANDLERS.pop,
|
|
183
|
+
[STACK_BRIDGE_METHODS.RESET]: FLUTTER_LEGACY_STACK_HANDLERS.reset
|
|
184
|
+
};
|
|
185
|
+
function unavailable(host) {
|
|
186
|
+
throw new Error(`${host} bridge is not available`);
|
|
187
|
+
}
|
|
188
|
+
function createReactNativeTransport() {
|
|
189
|
+
return {
|
|
190
|
+
host: "react-native",
|
|
191
|
+
async send(envelope) {
|
|
192
|
+
var _a;
|
|
193
|
+
try {
|
|
194
|
+
const bridge = (_a = window.ReactNativeWebView) != null ? _a : unavailable("react-native");
|
|
195
|
+
bridge.postMessage(serializeBridgeMessage(envelope));
|
|
196
|
+
return void 0;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
throw toBridgeError(error, BRIDGE_ERROR_CODES.BRIDGE_UNAVAILABLE);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function createFlutterTransport(options) {
|
|
204
|
+
const useLegacy = options.flutterLegacyHandlers !== false;
|
|
205
|
+
return {
|
|
206
|
+
host: "flutter",
|
|
207
|
+
async send(envelope) {
|
|
208
|
+
var _a, _b;
|
|
209
|
+
const bridge = window.flutter_inappwebview;
|
|
210
|
+
if (!(bridge == null ? void 0 : bridge.callHandler)) {
|
|
211
|
+
throw toBridgeError(
|
|
212
|
+
new Error("flutter bridge is not available"),
|
|
213
|
+
BRIDGE_ERROR_CODES.BRIDGE_UNAVAILABLE
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const call = bridge.callHandler.bind(bridge);
|
|
217
|
+
const handler = useLegacy ? LEGACY_HANDLER_BY_METHOD[envelope.method] : null;
|
|
218
|
+
if (!handler) {
|
|
219
|
+
return call(FLUTTER_UNIFIED_HANDLER, serializeBridgeMessage(envelope));
|
|
220
|
+
}
|
|
221
|
+
if (envelope.method === STACK_BRIDGE_METHODS.POP) {
|
|
222
|
+
return call(handler);
|
|
223
|
+
}
|
|
224
|
+
const payload = envelope.payload;
|
|
225
|
+
return call(handler, (_a = payload == null ? void 0 : payload.url) != null ? _a : "", (_b = payload == null ? void 0 : payload.meta) != null ? _b : null);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
var browserTransport = {
|
|
230
|
+
host: "browser",
|
|
231
|
+
async send() {
|
|
232
|
+
return void 0;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
function createBridgeTransport(host = detectBridgeHost(), options = {}) {
|
|
236
|
+
if (host === "react-native") return createReactNativeTransport();
|
|
237
|
+
if (host === "flutter") return createFlutterTransport(options);
|
|
238
|
+
return browserTransport;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/bridge/url-policy.ts
|
|
242
|
+
function isSafeStackUrl(url) {
|
|
243
|
+
if (url.startsWith("/"))
|
|
244
|
+
return !url.startsWith("//") && !url.startsWith("/\\");
|
|
245
|
+
if (typeof window === "undefined") return false;
|
|
246
|
+
try {
|
|
247
|
+
const parsed = new URL(url);
|
|
248
|
+
return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.origin === window.location.origin;
|
|
249
|
+
} catch (e) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function toStackPath(url) {
|
|
254
|
+
if (url.startsWith("/") || typeof window === "undefined") return url;
|
|
255
|
+
try {
|
|
256
|
+
const parsed = new URL(url);
|
|
257
|
+
if (parsed.origin !== window.location.origin) return url;
|
|
258
|
+
return parsed.pathname + parsed.search + parsed.hash;
|
|
259
|
+
} catch (e) {
|
|
260
|
+
return url;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function assertStackUrl(payload, allowUrl = isSafeStackUrl) {
|
|
264
|
+
const url = payload == null ? void 0 : payload.url;
|
|
265
|
+
if (typeof url !== "string" || url.trim().length === 0) {
|
|
266
|
+
throw new BridgeError(
|
|
267
|
+
BRIDGE_ERROR_CODES.INVALID_PAYLOAD,
|
|
268
|
+
"payload.url must be a non-empty string"
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (!allowUrl(url)) {
|
|
272
|
+
throw new BridgeError(
|
|
273
|
+
BRIDGE_ERROR_CODES.URL_NOT_ALLOWED,
|
|
274
|
+
`url is not allowed: ${url}`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return toStackPath(url);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/bridge/client.ts
|
|
281
|
+
function withTimeout(promise, timeoutMs, method) {
|
|
282
|
+
let timer;
|
|
283
|
+
const timeout = new Promise((_, reject) => {
|
|
284
|
+
timer = setTimeout(
|
|
285
|
+
() => reject(
|
|
286
|
+
new BridgeError(
|
|
287
|
+
BRIDGE_ERROR_CODES.TIMEOUT,
|
|
288
|
+
`${method} timed out after ${timeoutMs}ms`
|
|
289
|
+
)
|
|
290
|
+
),
|
|
291
|
+
timeoutMs
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
295
|
+
}
|
|
296
|
+
var WebViewBridgeClient = class {
|
|
297
|
+
constructor(options = {}) {
|
|
298
|
+
var _a, _b, _c, _d;
|
|
299
|
+
this.host = (_b = (_a = options.transport) == null ? void 0 : _a.host) != null ? _b : detectBridgeHost();
|
|
300
|
+
this.transport = (_c = options.transport) != null ? _c : createBridgeTransport(this.host, options);
|
|
301
|
+
this.allowUrl = options.allowUrl;
|
|
302
|
+
this.timeoutMs = (_d = options.timeoutMs) != null ? _d : BRIDGE_TIMEOUT_MS;
|
|
303
|
+
}
|
|
304
|
+
/** 응답을 기다리지 않는 단방향 전송 */
|
|
305
|
+
async send(envelope) {
|
|
306
|
+
return this.transport.send(envelope);
|
|
307
|
+
}
|
|
308
|
+
async request(method, payload, options = {}) {
|
|
309
|
+
var _a;
|
|
310
|
+
const envelope = createBridgeEnvelope(method, payload);
|
|
311
|
+
const timeoutMs = (_a = options.timeoutMs) != null ? _a : this.timeoutMs;
|
|
312
|
+
const waitsForReply = options.expectResponse === true && this.host === "react-native";
|
|
313
|
+
const reply2 = waitsForReply ? waitForResponse(envelope.id, timeoutMs) : null;
|
|
314
|
+
try {
|
|
315
|
+
const sent = await withTimeout(this.send(envelope), timeoutMs, method);
|
|
316
|
+
if (!reply2) return { envelope, result: sent };
|
|
317
|
+
const response = await reply2;
|
|
318
|
+
return { envelope, result: response.payload };
|
|
319
|
+
} catch (error) {
|
|
320
|
+
cancelResponse(envelope.id);
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async stackRequest(method, url, meta, options) {
|
|
325
|
+
const path = assertStackUrl({ url }, this.allowUrl);
|
|
326
|
+
return this.request(method, { url: path, meta }, options);
|
|
327
|
+
}
|
|
328
|
+
stackPush(url, meta = null, options) {
|
|
329
|
+
return this.stackRequest(STACK_BRIDGE_METHODS.PUSH, url, meta, options);
|
|
330
|
+
}
|
|
331
|
+
stackReplace(url, meta = null, options) {
|
|
332
|
+
return this.stackRequest(STACK_BRIDGE_METHODS.REPLACE, url, meta, options);
|
|
333
|
+
}
|
|
334
|
+
stackPop(options) {
|
|
335
|
+
return this.request(STACK_BRIDGE_METHODS.POP, void 0, options);
|
|
336
|
+
}
|
|
337
|
+
async stackReset(url, meta = null, options) {
|
|
338
|
+
const path = assertStackUrl({ url }, this.allowUrl);
|
|
339
|
+
return this.request(
|
|
340
|
+
STACK_BRIDGE_METHODS.RESET,
|
|
341
|
+
{ url: path, meta },
|
|
342
|
+
options
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
/** web → native 웹 스택 변경 알림 (응답 없음) */
|
|
346
|
+
notifyStackChanged(payload) {
|
|
347
|
+
return this.send(
|
|
348
|
+
createBridgeEnvelope(STACK_BRIDGE_METHODS.CHANGED, payload)
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
function createWebViewBridgeClient(options) {
|
|
353
|
+
return new WebViewBridgeClient(options);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/bridge/stack-router-bridge.ts
|
|
357
|
+
var cachedBridge = null;
|
|
358
|
+
function createStackRouterBridge(options) {
|
|
359
|
+
const host = detectBridgeHost();
|
|
360
|
+
if (!isNativeBridgeHost(host)) return null;
|
|
361
|
+
const client = createWebViewBridgeClient(options);
|
|
362
|
+
return {
|
|
363
|
+
push: (url, meta, requestOptions) => client.stackPush(url, meta != null ? meta : null, requestOptions),
|
|
364
|
+
replace: (url, meta, requestOptions) => client.stackReplace(url, meta != null ? meta : null, requestOptions),
|
|
365
|
+
pop: (requestOptions) => client.stackPop(requestOptions),
|
|
366
|
+
reset: (url, meta, requestOptions) => client.stackReset(url, meta != null ? meta : null, requestOptions)
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function getStackRouterBridge() {
|
|
370
|
+
if (typeof window === "undefined") return null;
|
|
371
|
+
if (window.stackRouterBridge) return window.stackRouterBridge;
|
|
372
|
+
if (!cachedBridge) cachedBridge = createStackRouterBridge();
|
|
373
|
+
return cachedBridge;
|
|
374
|
+
}
|
|
375
|
+
function installStackRouterBridge(options) {
|
|
376
|
+
if (typeof window === "undefined") return null;
|
|
377
|
+
const bridge = createStackRouterBridge(options);
|
|
378
|
+
if (bridge) {
|
|
379
|
+
window.stackRouterBridge = bridge;
|
|
380
|
+
}
|
|
381
|
+
return bridge;
|
|
382
|
+
}
|
|
383
|
+
function resetStackRouterBridgeCache() {
|
|
384
|
+
cachedBridge = null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/bridge/host.ts
|
|
388
|
+
var STACK_URL_METHODS = {
|
|
389
|
+
[STACK_BRIDGE_METHODS.PUSH]: "push",
|
|
390
|
+
[STACK_BRIDGE_METHODS.REPLACE]: "replace",
|
|
391
|
+
[STACK_BRIDGE_METHODS.RESET]: "reset"
|
|
392
|
+
};
|
|
393
|
+
async function route(envelope, options) {
|
|
394
|
+
var _a, _b, _c;
|
|
395
|
+
const custom = (_a = options.handlers) == null ? void 0 : _a[envelope.method];
|
|
396
|
+
if (custom) return Boolean(await custom(envelope.payload, envelope));
|
|
397
|
+
if (envelope.method === BACK_REQUEST_METHOD) {
|
|
398
|
+
return Boolean(await ((_b = options.onBackRequest) == null ? void 0 : _b.call(options)));
|
|
399
|
+
}
|
|
400
|
+
const stack = options.stack;
|
|
401
|
+
const urlMethod = STACK_URL_METHODS[envelope.method];
|
|
402
|
+
if (stack && urlMethod) {
|
|
403
|
+
const url = assertStackUrl(envelope.payload, options.allowUrl);
|
|
404
|
+
const meta = (_c = envelope.payload.meta) != null ? _c : null;
|
|
405
|
+
return Boolean(await stack[urlMethod](url, meta));
|
|
406
|
+
}
|
|
407
|
+
if (stack && envelope.method === STACK_BRIDGE_METHODS.POP) {
|
|
408
|
+
return Boolean(await stack.pop());
|
|
409
|
+
}
|
|
410
|
+
throw new BridgeError(
|
|
411
|
+
BRIDGE_ERROR_CODES.UNKNOWN_METHOD,
|
|
412
|
+
`No handler for ${envelope.method}`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
async function reply(request, result, options) {
|
|
416
|
+
var _a, _b;
|
|
417
|
+
const host = detectBridgeHost();
|
|
418
|
+
if (!isNativeBridgeHost(host)) return;
|
|
419
|
+
const response = createBridgeResponse(request, {
|
|
420
|
+
payload: { handled: result.handled },
|
|
421
|
+
error: (_a = result.error) == null ? void 0 : _a.toBody()
|
|
422
|
+
});
|
|
423
|
+
try {
|
|
424
|
+
await createBridgeTransport(host, options.transport).send(response);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
(_b = options.onError) == null ? void 0 : _b.call(
|
|
427
|
+
options,
|
|
428
|
+
toBridgeError(error, BRIDGE_ERROR_CODES.BRIDGE_UNAVAILABLE),
|
|
429
|
+
response
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
async function dispatchEnvelope(envelope, options) {
|
|
434
|
+
var _a, _b;
|
|
435
|
+
if (envelope.method === BRIDGE_RESPONSE_METHOD) {
|
|
436
|
+
return settleResponse(envelope);
|
|
437
|
+
}
|
|
438
|
+
(_a = options.onMessage) == null ? void 0 : _a.call(options, envelope);
|
|
439
|
+
let handled = false;
|
|
440
|
+
let failure;
|
|
441
|
+
try {
|
|
442
|
+
handled = await route(envelope, options);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
failure = toBridgeError(error, BRIDGE_ERROR_CODES.HANDLER_ERROR);
|
|
445
|
+
(_b = options.onError) == null ? void 0 : _b.call(options, failure, envelope);
|
|
446
|
+
}
|
|
447
|
+
if (options.replyToNative || envelope.method === BACK_REQUEST_METHOD) {
|
|
448
|
+
await reply(envelope, { handled, error: failure }, options);
|
|
449
|
+
}
|
|
450
|
+
return handled;
|
|
451
|
+
}
|
|
452
|
+
function handleBridgeMessage(raw, options) {
|
|
453
|
+
const envelope = parseBridgeMessage(raw);
|
|
454
|
+
if (!envelope) return Promise.resolve(false);
|
|
455
|
+
return dispatchEnvelope(envelope, options);
|
|
456
|
+
}
|
|
457
|
+
var isDev = () => {
|
|
458
|
+
var _a;
|
|
459
|
+
return typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.NODE_ENV) !== "production";
|
|
460
|
+
};
|
|
461
|
+
function legacyCall(method, payload, options) {
|
|
462
|
+
return dispatchEnvelope(createBridgeEnvelope(method, payload), {
|
|
463
|
+
...options,
|
|
464
|
+
replyToNative: false
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function installBridgeHost(options) {
|
|
468
|
+
if (typeof window === "undefined") {
|
|
469
|
+
return () => void 0;
|
|
470
|
+
}
|
|
471
|
+
const installed = {};
|
|
472
|
+
const target = window;
|
|
473
|
+
const install = (key, value) => {
|
|
474
|
+
if (isDev() && target[key] !== void 0) {
|
|
475
|
+
console.warn(`[webviewkit] window.${key} is being overwritten`);
|
|
476
|
+
}
|
|
477
|
+
target[key] = value;
|
|
478
|
+
installed[key] = value;
|
|
479
|
+
};
|
|
480
|
+
install("webviewkitBridge", {
|
|
481
|
+
postMessage: (raw) => {
|
|
482
|
+
void handleBridgeMessage(raw, options);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
if (options.legacyInboundHandlers !== false && options.stack) {
|
|
486
|
+
install(
|
|
487
|
+
"stackPush",
|
|
488
|
+
(url, meta) => legacyCall(STACK_BRIDGE_METHODS.PUSH, { url, meta }, options)
|
|
489
|
+
);
|
|
490
|
+
install(
|
|
491
|
+
"stackReplace",
|
|
492
|
+
(url, meta) => legacyCall(STACK_BRIDGE_METHODS.REPLACE, { url, meta }, options)
|
|
493
|
+
);
|
|
494
|
+
install(
|
|
495
|
+
"stackPop",
|
|
496
|
+
() => legacyCall(STACK_BRIDGE_METHODS.POP, void 0, options)
|
|
497
|
+
);
|
|
498
|
+
install(
|
|
499
|
+
"stackReset",
|
|
500
|
+
(url, meta) => legacyCall(STACK_BRIDGE_METHODS.RESET, { url, meta }, options)
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
const outbound = options.exposeStackRouterBridge === true ? createStackRouterBridge(options.transport) : options.exposeStackRouterBridge || null;
|
|
504
|
+
if (outbound) install("stackRouterBridge", outbound);
|
|
505
|
+
return () => {
|
|
506
|
+
Object.keys(installed).forEach((key) => {
|
|
507
|
+
if (target[key] === installed[key]) delete target[key];
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function emitBridgeEnvelope(method, payload) {
|
|
512
|
+
return createBridgeEnvelope(method, payload);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/bridge/WebViewBridgeProvider.tsx
|
|
516
|
+
import { useEffect, useRef } from "react";
|
|
517
|
+
import { Fragment, jsx } from "@emotion/react/jsx-runtime";
|
|
518
|
+
function WebViewBridgeProvider({
|
|
519
|
+
children = null,
|
|
520
|
+
stack,
|
|
521
|
+
transport,
|
|
522
|
+
enableOutboundBridge = true,
|
|
523
|
+
hostOptions
|
|
524
|
+
}) {
|
|
525
|
+
const latest = useRef({ stack, transport, hostOptions });
|
|
526
|
+
latest.current = { stack, transport, hostOptions };
|
|
527
|
+
const hasStack = Boolean(stack);
|
|
528
|
+
useEffect(() => {
|
|
529
|
+
const call = (key) => (...args) => {
|
|
530
|
+
var _a;
|
|
531
|
+
const handler = (_a = latest.current.stack) == null ? void 0 : _a[key];
|
|
532
|
+
return handler ? handler(...args) : false;
|
|
533
|
+
};
|
|
534
|
+
const options = latest.current.hostOptions;
|
|
535
|
+
const cleanups = [];
|
|
536
|
+
cleanups.push(
|
|
537
|
+
installBridgeHost({
|
|
538
|
+
...options,
|
|
539
|
+
onMessage: (envelope) => {
|
|
540
|
+
var _a, _b;
|
|
541
|
+
return (_b = (_a = latest.current.hostOptions) == null ? void 0 : _a.onMessage) == null ? void 0 : _b.call(_a, envelope);
|
|
542
|
+
},
|
|
543
|
+
onError: (error, envelope) => {
|
|
544
|
+
var _a, _b;
|
|
545
|
+
return (_b = (_a = latest.current.hostOptions) == null ? void 0 : _a.onError) == null ? void 0 : _b.call(_a, error, envelope);
|
|
546
|
+
},
|
|
547
|
+
onBackRequest: () => {
|
|
548
|
+
var _a, _b, _c;
|
|
549
|
+
return (_c = (_b = (_a = latest.current.hostOptions) == null ? void 0 : _a.onBackRequest) == null ? void 0 : _b.call(_a)) != null ? _c : false;
|
|
550
|
+
},
|
|
551
|
+
stack: hasStack ? {
|
|
552
|
+
push: call("push"),
|
|
553
|
+
replace: call("replace"),
|
|
554
|
+
pop: call("pop"),
|
|
555
|
+
reset: call("reset")
|
|
556
|
+
} : void 0,
|
|
557
|
+
legacyInboundHandlers: hasStack
|
|
558
|
+
})
|
|
559
|
+
);
|
|
560
|
+
const abort = new AbortController();
|
|
561
|
+
if (enableOutboundBridge) {
|
|
562
|
+
void waitForBridgeHost({ signal: abort.signal }).then(() => {
|
|
563
|
+
if (abort.signal.aborted) return;
|
|
564
|
+
const outbound = createStackRouterBridge(latest.current.transport);
|
|
565
|
+
if (!outbound) return;
|
|
566
|
+
window.stackRouterBridge = outbound;
|
|
567
|
+
cleanups.push(() => {
|
|
568
|
+
if (window.stackRouterBridge === outbound)
|
|
569
|
+
delete window.stackRouterBridge;
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
return () => {
|
|
574
|
+
abort.abort();
|
|
575
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
576
|
+
};
|
|
577
|
+
}, [enableOutboundBridge, hasStack]);
|
|
578
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/type.ts
|
|
582
|
+
var Direction = /* @__PURE__ */ ((Direction2) => {
|
|
583
|
+
Direction2["FORWARD"] = "forward";
|
|
584
|
+
Direction2["BACKWARD"] = "backward";
|
|
585
|
+
return Direction2;
|
|
586
|
+
})(Direction || {});
|
|
587
|
+
var HistoryAction = /* @__PURE__ */ ((HistoryAction2) => {
|
|
588
|
+
HistoryAction2["PUSH"] = "PUSH";
|
|
589
|
+
HistoryAction2["POP"] = "POP";
|
|
590
|
+
HistoryAction2["REPLACE"] = "REPLACE";
|
|
591
|
+
return HistoryAction2;
|
|
592
|
+
})(HistoryAction || {});
|
|
593
|
+
|
|
594
|
+
// src/history.ts
|
|
595
|
+
var isBrowser = () => typeof window !== "undefined";
|
|
596
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
597
|
+
var currentPath = () => window.location.pathname + window.location.search;
|
|
598
|
+
var onPopState = (event) => {
|
|
599
|
+
var _a;
|
|
600
|
+
const state = {
|
|
601
|
+
path: currentPath(),
|
|
602
|
+
...(_a = event.state) != null ? _a : {},
|
|
603
|
+
action: "POP" /* POP */
|
|
604
|
+
};
|
|
605
|
+
listeners.forEach((listener) => listener(state));
|
|
606
|
+
};
|
|
607
|
+
var history = {
|
|
608
|
+
push: (path, state = {}) => {
|
|
609
|
+
if (!isBrowser()) return;
|
|
610
|
+
window.history.pushState(
|
|
611
|
+
{ ...state, path, action: "PUSH" /* PUSH */ },
|
|
612
|
+
"",
|
|
613
|
+
path
|
|
614
|
+
);
|
|
615
|
+
},
|
|
616
|
+
replace: (path, state = {}) => {
|
|
617
|
+
if (!isBrowser()) return;
|
|
618
|
+
window.history.replaceState(
|
|
619
|
+
{ ...state, path, action: "REPLACE" /* REPLACE */ },
|
|
620
|
+
"",
|
|
621
|
+
path
|
|
622
|
+
);
|
|
623
|
+
},
|
|
624
|
+
/** `history.go(delta)` 후 popstate 가 오면 true, BACK_TIMEOUT_MS 안에 오지 않으면 false */
|
|
625
|
+
travel: (delta) => {
|
|
626
|
+
if (!isBrowser()) return Promise.resolve(false);
|
|
627
|
+
return new Promise((resolve) => {
|
|
628
|
+
const finish = (popped) => {
|
|
629
|
+
window.clearTimeout(timer);
|
|
630
|
+
window.removeEventListener("popstate", onPop);
|
|
631
|
+
resolve(popped);
|
|
632
|
+
};
|
|
633
|
+
const onPop = () => finish(true);
|
|
634
|
+
const timer = window.setTimeout(() => finish(false), BACK_TIMEOUT_MS);
|
|
635
|
+
window.addEventListener("popstate", onPop);
|
|
636
|
+
window.history.go(delta);
|
|
637
|
+
});
|
|
638
|
+
},
|
|
639
|
+
back: () => history.travel(-1),
|
|
640
|
+
forward: () => {
|
|
641
|
+
if (!isBrowser()) return;
|
|
642
|
+
window.history.forward();
|
|
643
|
+
},
|
|
644
|
+
go: (delta) => {
|
|
645
|
+
if (!isBrowser()) return;
|
|
646
|
+
window.history.go(delta);
|
|
647
|
+
},
|
|
648
|
+
getCurrentState: () => {
|
|
649
|
+
if (!isBrowser()) return { path: "/" };
|
|
650
|
+
const state = window.history.state;
|
|
651
|
+
return (state == null ? void 0 : state.path) ? state : { path: currentPath() };
|
|
652
|
+
},
|
|
653
|
+
addListener: (listener) => {
|
|
654
|
+
if (!isBrowser()) return () => false;
|
|
655
|
+
if (listeners.size === 0) {
|
|
656
|
+
window.addEventListener("popstate", onPopState);
|
|
657
|
+
}
|
|
658
|
+
listeners.add(listener);
|
|
659
|
+
return () => {
|
|
660
|
+
const removed = listeners.delete(listener);
|
|
661
|
+
if (listeners.size === 0) {
|
|
662
|
+
window.removeEventListener("popstate", onPopState);
|
|
663
|
+
}
|
|
664
|
+
return removed;
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// src/stack-dev/stack-dev-roadmap.tsx
|
|
670
|
+
import React2 from "react";
|
|
671
|
+
import { jsx as jsx2, jsxs } from "@emotion/react/jsx-runtime";
|
|
672
|
+
function StackDevRoadmap({
|
|
673
|
+
pages,
|
|
674
|
+
currentPath: currentPath2,
|
|
675
|
+
visible = true
|
|
676
|
+
}) {
|
|
677
|
+
if (!visible || pages.length === 0) return null;
|
|
678
|
+
const depth = pages.length;
|
|
679
|
+
return /* @__PURE__ */ jsxs(
|
|
680
|
+
"div",
|
|
681
|
+
{
|
|
682
|
+
role: "status",
|
|
683
|
+
"aria-live": "polite",
|
|
684
|
+
"aria-label": `WebViewKit stack depth ${depth}`,
|
|
685
|
+
style: {
|
|
686
|
+
position: "fixed",
|
|
687
|
+
bottom: 12,
|
|
688
|
+
right: 12,
|
|
689
|
+
zIndex: 2147483646,
|
|
690
|
+
minWidth: 200,
|
|
691
|
+
maxWidth: 320,
|
|
692
|
+
padding: "10px 12px",
|
|
693
|
+
borderRadius: 10,
|
|
694
|
+
background: "rgba(7, 11, 18, 0.92)",
|
|
695
|
+
border: "1px solid rgba(0, 232, 184, 0.35)",
|
|
696
|
+
color: "#e8eef4",
|
|
697
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',
|
|
698
|
+
fontSize: 11,
|
|
699
|
+
lineHeight: 1.45,
|
|
700
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.35)",
|
|
701
|
+
pointerEvents: "none"
|
|
702
|
+
},
|
|
703
|
+
children: [
|
|
704
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: 8 }, children: [
|
|
705
|
+
/* @__PURE__ */ jsx2("span", { style: { color: "#00e8b8", fontWeight: 700 }, children: "WebViewKit" }),
|
|
706
|
+
/* @__PURE__ */ jsxs("span", { style: { color: "#94a3b8" }, children: [
|
|
707
|
+
"stack \xD7 ",
|
|
708
|
+
depth
|
|
709
|
+
] })
|
|
710
|
+
] }),
|
|
711
|
+
/* @__PURE__ */ jsx2(
|
|
712
|
+
"div",
|
|
713
|
+
{
|
|
714
|
+
style: {
|
|
715
|
+
display: "flex",
|
|
716
|
+
alignItems: "center",
|
|
717
|
+
gap: 0,
|
|
718
|
+
marginTop: 10,
|
|
719
|
+
padding: "4px 0"
|
|
720
|
+
},
|
|
721
|
+
children: pages.map((page, index) => {
|
|
722
|
+
const isCurrent = page.path === currentPath2;
|
|
723
|
+
const isLast = index === pages.length - 1;
|
|
724
|
+
return /* @__PURE__ */ jsxs(React2.Fragment, { children: [
|
|
725
|
+
index > 0 && /* @__PURE__ */ jsx2(
|
|
726
|
+
"span",
|
|
727
|
+
{
|
|
728
|
+
style: {
|
|
729
|
+
width: 14,
|
|
730
|
+
height: 2,
|
|
731
|
+
background: isLast ? "linear-gradient(90deg,#334155,#00e8b8)" : "#334155",
|
|
732
|
+
flexShrink: 0
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
),
|
|
736
|
+
/* @__PURE__ */ jsx2(
|
|
737
|
+
"span",
|
|
738
|
+
{
|
|
739
|
+
title: page.path,
|
|
740
|
+
style: {
|
|
741
|
+
width: isCurrent ? 12 : 8,
|
|
742
|
+
height: isCurrent ? 12 : 8,
|
|
743
|
+
borderRadius: "50%",
|
|
744
|
+
flexShrink: 0,
|
|
745
|
+
background: isCurrent ? "#00e8b8" : "#475569",
|
|
746
|
+
boxShadow: isCurrent ? "0 0 10px rgba(0,232,184,0.85)" : "none",
|
|
747
|
+
transition: "all 0.2s ease"
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
)
|
|
751
|
+
] }, `${page.path}-${index}`);
|
|
752
|
+
})
|
|
753
|
+
}
|
|
754
|
+
),
|
|
755
|
+
/* @__PURE__ */ jsx2("ol", { style: { margin: "8px 0 0", paddingLeft: 18, color: "#cbd5e1" }, children: pages.map((page, index) => {
|
|
756
|
+
const isCurrent = index === pages.length - 1;
|
|
757
|
+
return /* @__PURE__ */ jsxs(
|
|
758
|
+
"li",
|
|
759
|
+
{
|
|
760
|
+
style: {
|
|
761
|
+
fontWeight: isCurrent ? 600 : 400,
|
|
762
|
+
color: isCurrent ? "#00e8b8" : "#94a3b8"
|
|
763
|
+
},
|
|
764
|
+
children: [
|
|
765
|
+
page.path,
|
|
766
|
+
isCurrent ? " \u2190 top" : ""
|
|
767
|
+
]
|
|
768
|
+
},
|
|
769
|
+
`${page.path}-${index}`
|
|
770
|
+
);
|
|
771
|
+
}) })
|
|
772
|
+
]
|
|
773
|
+
}
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/stack-router/_components/back-router.tsx
|
|
778
|
+
import styled4 from "@emotion/styled";
|
|
779
|
+
|
|
780
|
+
// src/stack-router/stack-router-provider.tsx
|
|
781
|
+
import styled3 from "@emotion/styled";
|
|
782
|
+
import { createContext as createContext2, useContext as useContext2, useMemo } from "react";
|
|
783
|
+
|
|
784
|
+
// src/stack-router/_components/header.tsx
|
|
785
|
+
import styled from "@emotion/styled";
|
|
786
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "@emotion/react/jsx-runtime";
|
|
787
|
+
var Header = ({ title }) => {
|
|
788
|
+
const { currentPath: currentPath2, canGoBack } = useStackRouter();
|
|
789
|
+
return /* @__PURE__ */ jsxs2(StyledHeader, { children: [
|
|
790
|
+
canGoBack && /* @__PURE__ */ jsx3(back_router_default, {}),
|
|
791
|
+
/* @__PURE__ */ jsx3(StyledTitle, { children: title != null ? title : currentPath2 })
|
|
792
|
+
] });
|
|
793
|
+
};
|
|
794
|
+
var StyledHeader = styled.header`
|
|
795
|
+
width: 100%;
|
|
796
|
+
height: 64px;
|
|
797
|
+
flex-shrink: 0;
|
|
798
|
+
box-sizing: border-box;
|
|
799
|
+
background-color: var(--wvk-header-bg, #ffffff);
|
|
800
|
+
border-bottom: 1px solid var(--wvk-header-border, #e5e7eb);
|
|
801
|
+
display: flex;
|
|
802
|
+
align-items: center;
|
|
803
|
+
padding: 0 1rem;
|
|
804
|
+
`;
|
|
805
|
+
var StyledTitle = styled.h1`
|
|
806
|
+
margin: 0;
|
|
807
|
+
font-size: 1.25rem;
|
|
808
|
+
font-weight: 600;
|
|
809
|
+
color: var(--wvk-header-color, #111827);
|
|
810
|
+
`;
|
|
811
|
+
var header_default = Header;
|
|
812
|
+
|
|
813
|
+
// src/stack-router/_components/stack-page.tsx
|
|
814
|
+
import { css, keyframes } from "@emotion/react";
|
|
815
|
+
import styled2 from "@emotion/styled";
|
|
816
|
+
import { useLayoutEffect, useRef as useRef2 } from "react";
|
|
817
|
+
import { jsx as jsx4 } from "@emotion/react/jsx-runtime";
|
|
818
|
+
var IOS_EASING = "cubic-bezier(0.32, 0.72, 0, 1)";
|
|
819
|
+
var FADE_EASING = "ease";
|
|
820
|
+
var PARALLAX_OFFSET = "-30%";
|
|
821
|
+
var coverIn = keyframes`
|
|
822
|
+
from { transform: translate3d(100%, 0, 0); }
|
|
823
|
+
to { transform: translate3d(0, 0, 0); }
|
|
824
|
+
`;
|
|
825
|
+
var coverOut = keyframes`
|
|
826
|
+
from { transform: translate3d(0, 0, 0); }
|
|
827
|
+
to { transform: translate3d(100%, 0, 0); }
|
|
828
|
+
`;
|
|
829
|
+
var parallaxOut = keyframes`
|
|
830
|
+
from { transform: translate3d(0, 0, 0); }
|
|
831
|
+
to { transform: translate3d(${PARALLAX_OFFSET}, 0, 0); }
|
|
832
|
+
`;
|
|
833
|
+
var parallaxIn = keyframes`
|
|
834
|
+
from { transform: translate3d(${PARALLAX_OFFSET}, 0, 0); }
|
|
835
|
+
to { transform: translate3d(0, 0, 0); }
|
|
836
|
+
`;
|
|
837
|
+
var dimIn = keyframes`
|
|
838
|
+
from { opacity: 0; }
|
|
839
|
+
to { opacity: 1; }
|
|
840
|
+
`;
|
|
841
|
+
var dimOut = keyframes`
|
|
842
|
+
from { opacity: 1; }
|
|
843
|
+
to { opacity: 0; }
|
|
844
|
+
`;
|
|
845
|
+
var fadeIn = keyframes`
|
|
846
|
+
from { opacity: 0; }
|
|
847
|
+
to { opacity: 1; }
|
|
848
|
+
`;
|
|
849
|
+
var fadeOut = keyframes`
|
|
850
|
+
from { opacity: 1; }
|
|
851
|
+
to { opacity: 0; }
|
|
852
|
+
`;
|
|
853
|
+
var layerOf = (direction, pageState) => direction === "push" === (pageState === "enter") ? "upper" : "lower";
|
|
854
|
+
function slideStyles(direction, pageState, duration) {
|
|
855
|
+
const timing = `${duration}ms ${IOS_EASING} forwards`;
|
|
856
|
+
if (layerOf(direction, pageState) === "upper") {
|
|
857
|
+
return css`
|
|
858
|
+
animation: ${direction === "push" ? coverIn : coverOut} ${timing};
|
|
859
|
+
box-shadow: var(--wvk-edge-shadow, -6px 0 24px rgba(0, 0, 0, 0.12));
|
|
860
|
+
`;
|
|
861
|
+
}
|
|
862
|
+
return css`
|
|
863
|
+
animation: ${direction === "push" ? parallaxOut : parallaxIn} ${timing};
|
|
864
|
+
&::after {
|
|
865
|
+
content: "";
|
|
866
|
+
/* transform 중인 페이지 기준 fixed = 스크롤과 무관하게 페이지 전체를 덮음 */
|
|
867
|
+
position: fixed;
|
|
868
|
+
inset: 0;
|
|
869
|
+
pointer-events: none;
|
|
870
|
+
background: var(--wvk-dim-color, rgba(0, 0, 0, 0.12));
|
|
871
|
+
animation: ${direction === "push" ? dimIn : dimOut} ${timing};
|
|
872
|
+
}
|
|
873
|
+
`;
|
|
874
|
+
}
|
|
875
|
+
var PageWrapper = styled2.div`
|
|
876
|
+
position: absolute;
|
|
877
|
+
inset: 0;
|
|
878
|
+
overflow: auto;
|
|
879
|
+
-webkit-overflow-scrolling: touch;
|
|
880
|
+
background: var(--wvk-page-bg, #ffffff);
|
|
881
|
+
z-index: ${({ zIndex }) => zIndex};
|
|
882
|
+
visibility: ${({ pageState }) => pageState === "covered" ? "hidden" : "visible"};
|
|
883
|
+
${({ pageState, direction, transitionType, duration }) => {
|
|
884
|
+
if (pageState !== "enter" && pageState !== "exit") return "";
|
|
885
|
+
if (transitionType === "none" || duration === 0) return "";
|
|
886
|
+
if (transitionType === "fade") {
|
|
887
|
+
return css`
|
|
888
|
+
will-change: opacity;
|
|
889
|
+
animation: ${pageState === "enter" ? fadeIn : fadeOut} ${duration}ms
|
|
890
|
+
${FADE_EASING} forwards;
|
|
891
|
+
`;
|
|
892
|
+
}
|
|
893
|
+
return css`
|
|
894
|
+
will-change: transform;
|
|
895
|
+
${slideStyles(direction, pageState, duration)}
|
|
896
|
+
`;
|
|
897
|
+
}}
|
|
898
|
+
`;
|
|
899
|
+
function StackPage({
|
|
900
|
+
isTop,
|
|
901
|
+
onAnimationEnd,
|
|
902
|
+
children,
|
|
903
|
+
...wrapperProps
|
|
904
|
+
}) {
|
|
905
|
+
const ref = useRef2(null);
|
|
906
|
+
useLayoutEffect(() => {
|
|
907
|
+
var _a;
|
|
908
|
+
(_a = ref.current) == null ? void 0 : _a.toggleAttribute("inert", !isTop);
|
|
909
|
+
}, [isTop]);
|
|
910
|
+
return /* @__PURE__ */ jsx4(
|
|
911
|
+
PageWrapper,
|
|
912
|
+
{
|
|
913
|
+
ref,
|
|
914
|
+
...wrapperProps,
|
|
915
|
+
"data-state": wrapperProps.pageState,
|
|
916
|
+
"aria-hidden": isTop ? void 0 : true,
|
|
917
|
+
onAnimationEnd: (event) => {
|
|
918
|
+
if (event.target === event.currentTarget && !event.pseudoElement) {
|
|
919
|
+
onAnimationEnd == null ? void 0 : onAnimationEnd();
|
|
920
|
+
}
|
|
921
|
+
},
|
|
922
|
+
children
|
|
923
|
+
}
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// src/stack-router/activity-context.tsx
|
|
928
|
+
import { createContext, useContext } from "react";
|
|
929
|
+
var ActivityContext = createContext(null);
|
|
930
|
+
function useActivity() {
|
|
931
|
+
const context = useContext(ActivityContext);
|
|
932
|
+
if (!context) {
|
|
933
|
+
throw new Error(
|
|
934
|
+
"useActivity must be used inside a StackRouterProvider page"
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
return context;
|
|
938
|
+
}
|
|
939
|
+
function useParams() {
|
|
940
|
+
return useActivity().params;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/stack-router/stack-reducer.ts
|
|
944
|
+
var topOf = (entries) => entries[entries.length - 1];
|
|
945
|
+
function createStackState(entry) {
|
|
946
|
+
return { entries: [entry], leaving: [], transition: null };
|
|
947
|
+
}
|
|
948
|
+
function stackReducer(state, action) {
|
|
949
|
+
switch (action.type) {
|
|
950
|
+
case "PUSH": {
|
|
951
|
+
const previousTop = topOf(state.entries);
|
|
952
|
+
return {
|
|
953
|
+
entries: [...state.entries, action.entry],
|
|
954
|
+
leaving: [],
|
|
955
|
+
transition: action.animate ? {
|
|
956
|
+
type: "push",
|
|
957
|
+
enterKey: action.entry.key,
|
|
958
|
+
exitKey: previousTop.key
|
|
959
|
+
} : null
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
case "REPLACE":
|
|
963
|
+
return {
|
|
964
|
+
...state,
|
|
965
|
+
entries: [...state.entries.slice(0, -1), action.entry]
|
|
966
|
+
};
|
|
967
|
+
case "POP_TO": {
|
|
968
|
+
const index = Math.max(0, action.index);
|
|
969
|
+
if (index >= state.entries.length - 1) return state;
|
|
970
|
+
const entries = state.entries.slice(0, index + 1);
|
|
971
|
+
const leaving = state.entries.slice(index + 1);
|
|
972
|
+
if (!action.animate) {
|
|
973
|
+
return { entries, leaving: [], transition: null };
|
|
974
|
+
}
|
|
975
|
+
return {
|
|
976
|
+
entries,
|
|
977
|
+
leaving,
|
|
978
|
+
transition: {
|
|
979
|
+
type: "pop",
|
|
980
|
+
enterKey: topOf(entries).key,
|
|
981
|
+
exitKeys: leaving.map((entry) => entry.key)
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
case "RESET":
|
|
986
|
+
return createStackState(action.entry);
|
|
987
|
+
case "RESTORE":
|
|
988
|
+
return { entries: action.entries, leaving: [], transition: null };
|
|
989
|
+
case "END_TRANSITION":
|
|
990
|
+
if (!state.transition && state.leaving.length === 0) return state;
|
|
991
|
+
return { ...state, leaving: [], transition: null };
|
|
992
|
+
default:
|
|
993
|
+
return state;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function getRenderedPages(state) {
|
|
997
|
+
const { entries, leaving, transition } = state;
|
|
998
|
+
const top = topOf(entries);
|
|
999
|
+
return [...entries, ...leaving].map((entry) => {
|
|
1000
|
+
const isTop = entry.key === top.key;
|
|
1001
|
+
let pageState = isTop ? "idle" : "covered";
|
|
1002
|
+
if ((transition == null ? void 0 : transition.type) === "push") {
|
|
1003
|
+
if (entry.key === transition.enterKey) pageState = "enter";
|
|
1004
|
+
if (entry.key === transition.exitKey) pageState = "exit";
|
|
1005
|
+
}
|
|
1006
|
+
if ((transition == null ? void 0 : transition.type) === "pop") {
|
|
1007
|
+
if (entry.key === transition.enterKey) pageState = "enter";
|
|
1008
|
+
if (transition.exitKeys.includes(entry.key)) pageState = "exit";
|
|
1009
|
+
}
|
|
1010
|
+
return { entry, pageState, isTop };
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/stack-router/use-prefers-reduced-motion.ts
|
|
1015
|
+
import { useEffect as useEffect2, useState } from "react";
|
|
1016
|
+
var QUERY = "(prefers-reduced-motion: reduce)";
|
|
1017
|
+
var getMediaQuery = () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(QUERY) : null;
|
|
1018
|
+
function usePrefersReducedMotion() {
|
|
1019
|
+
const [reduced, setReduced] = useState(
|
|
1020
|
+
() => {
|
|
1021
|
+
var _a, _b;
|
|
1022
|
+
return (_b = (_a = getMediaQuery()) == null ? void 0 : _a.matches) != null ? _b : false;
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
useEffect2(() => {
|
|
1026
|
+
var _a;
|
|
1027
|
+
const mediaQuery = getMediaQuery();
|
|
1028
|
+
if (!mediaQuery) return;
|
|
1029
|
+
const onChange = () => setReduced(mediaQuery.matches);
|
|
1030
|
+
(_a = mediaQuery.addEventListener) == null ? void 0 : _a.call(mediaQuery, "change", onChange);
|
|
1031
|
+
return () => {
|
|
1032
|
+
var _a2;
|
|
1033
|
+
return (_a2 = mediaQuery.removeEventListener) == null ? void 0 : _a2.call(mediaQuery, "change", onChange);
|
|
1034
|
+
};
|
|
1035
|
+
}, []);
|
|
1036
|
+
return reduced;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// src/stack-router/use-stack-bridge-sync.ts
|
|
1040
|
+
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
1041
|
+
function useStackBridgeSync(bridge, router, onBackAtRoot) {
|
|
1042
|
+
const enabled = Boolean(bridge);
|
|
1043
|
+
const options = typeof bridge === "object" ? bridge : {};
|
|
1044
|
+
const latest = useRef3({ router, options, onBackAtRoot });
|
|
1045
|
+
latest.current = { router, options, onBackAtRoot };
|
|
1046
|
+
useEffect3(() => {
|
|
1047
|
+
if (!enabled) return;
|
|
1048
|
+
return installBridgeHost({
|
|
1049
|
+
allowUrl: latest.current.options.allowUrl,
|
|
1050
|
+
onError: (error, envelope) => {
|
|
1051
|
+
var _a, _b;
|
|
1052
|
+
return (_b = (_a = latest.current.options).onError) == null ? void 0 : _b.call(_a, error, envelope);
|
|
1053
|
+
},
|
|
1054
|
+
stack: {
|
|
1055
|
+
push: (url) => latest.current.router.push(url),
|
|
1056
|
+
replace: (url) => latest.current.router.replace(url),
|
|
1057
|
+
reset: (url) => latest.current.router.reset(url),
|
|
1058
|
+
pop: () => latest.current.router.back()
|
|
1059
|
+
},
|
|
1060
|
+
onBackRequest: () => {
|
|
1061
|
+
var _a;
|
|
1062
|
+
const current = latest.current;
|
|
1063
|
+
if (current.router.canGoBack) return current.router.back();
|
|
1064
|
+
(_a = current.onBackAtRoot) == null ? void 0 : _a.call(current);
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
}, [enabled]);
|
|
1069
|
+
const { depth, currentPath: currentPath2 } = router;
|
|
1070
|
+
const emit = enabled && options.emitStackChanged !== false;
|
|
1071
|
+
useEffect3(() => {
|
|
1072
|
+
if (!emit || !isNativeBridgeHost(detectBridgeHost())) return;
|
|
1073
|
+
createWebViewBridgeClient().notifyStackChanged({ depth, path: currentPath2 }).catch((error) => {
|
|
1074
|
+
var _a, _b;
|
|
1075
|
+
return (_b = (_a = latest.current.options).onError) == null ? void 0 : _b.call(_a, error);
|
|
1076
|
+
});
|
|
1077
|
+
}, [emit, depth, currentPath2]);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/stack-router/use-stack-navigation.ts
|
|
1081
|
+
import { useCallback, useEffect as useEffect4, useReducer, useRef as useRef4 } from "react";
|
|
1082
|
+
|
|
1083
|
+
// src/stack-router/match-path.ts
|
|
1084
|
+
var splitSegments = (pathname) => pathname.split("/").filter((segment) => segment.length > 0);
|
|
1085
|
+
var safeDecode = (value) => {
|
|
1086
|
+
try {
|
|
1087
|
+
return decodeURIComponent(value);
|
|
1088
|
+
} catch (e) {
|
|
1089
|
+
return value;
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
function parseQuery(search) {
|
|
1093
|
+
const query = {};
|
|
1094
|
+
new URLSearchParams(search).forEach((value, key) => {
|
|
1095
|
+
query[key] = value;
|
|
1096
|
+
});
|
|
1097
|
+
return query;
|
|
1098
|
+
}
|
|
1099
|
+
function matchPath(pattern, url) {
|
|
1100
|
+
const [withoutHash] = url.split("#");
|
|
1101
|
+
const queryIndex = withoutHash.indexOf("?");
|
|
1102
|
+
const pathname = queryIndex === -1 ? withoutHash : withoutHash.slice(0, queryIndex);
|
|
1103
|
+
const search = queryIndex === -1 ? "" : withoutHash.slice(queryIndex);
|
|
1104
|
+
const patternSegments = splitSegments(pattern);
|
|
1105
|
+
const pathSegments = splitSegments(pathname);
|
|
1106
|
+
if (patternSegments.length !== pathSegments.length) return null;
|
|
1107
|
+
const params = {};
|
|
1108
|
+
for (let i = 0; i < patternSegments.length; i += 1) {
|
|
1109
|
+
const expected = patternSegments[i];
|
|
1110
|
+
const actual = pathSegments[i];
|
|
1111
|
+
if (expected.startsWith(":")) {
|
|
1112
|
+
params[expected.slice(1)] = safeDecode(actual);
|
|
1113
|
+
} else if (expected !== actual) {
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return { params, query: parseQuery(search) };
|
|
1118
|
+
}
|
|
1119
|
+
function findActivity(activities, url) {
|
|
1120
|
+
for (const activity of activities) {
|
|
1121
|
+
const match = matchPath(activity.path, url);
|
|
1122
|
+
if (match) return { activity, match };
|
|
1123
|
+
}
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// src/stack-router/use-stack-navigation.ts
|
|
1128
|
+
var isDev2 = () => {
|
|
1129
|
+
var _a;
|
|
1130
|
+
return typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.NODE_ENV) !== "production";
|
|
1131
|
+
};
|
|
1132
|
+
function createEntry(hit, path, data, key = createBridgeId()) {
|
|
1133
|
+
return {
|
|
1134
|
+
key,
|
|
1135
|
+
path,
|
|
1136
|
+
pattern: hit.activity.path,
|
|
1137
|
+
params: hit.match.params,
|
|
1138
|
+
query: hit.match.query,
|
|
1139
|
+
data
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
var currentUrl = () => window.location.pathname + window.location.search;
|
|
1143
|
+
function useStackNavigation(options) {
|
|
1144
|
+
const optionsRef = useRef4(options);
|
|
1145
|
+
optionsRef.current = options;
|
|
1146
|
+
const [state, dispatch] = useReducer(stackReducer, void 0, () => {
|
|
1147
|
+
const { initialActivity } = options;
|
|
1148
|
+
return createStackState(
|
|
1149
|
+
createEntry(
|
|
1150
|
+
{ activity: initialActivity, match: { params: {}, query: {} } },
|
|
1151
|
+
initialActivity.path
|
|
1152
|
+
)
|
|
1153
|
+
);
|
|
1154
|
+
});
|
|
1155
|
+
const stateRef = useRef4(state);
|
|
1156
|
+
stateRef.current = state;
|
|
1157
|
+
const busyRef = useRef4(false);
|
|
1158
|
+
const resolve = useCallback((path) => {
|
|
1159
|
+
const { activities, initialActivity } = optionsRef.current;
|
|
1160
|
+
return findActivity([...activities, initialActivity], path);
|
|
1161
|
+
}, []);
|
|
1162
|
+
const resolveOrNotify = useCallback(
|
|
1163
|
+
(path) => {
|
|
1164
|
+
var _a, _b;
|
|
1165
|
+
const hit = resolve(path);
|
|
1166
|
+
if (!hit) {
|
|
1167
|
+
if (isDev2()) {
|
|
1168
|
+
console.warn(`[webviewkit] No activity matches "${path}"`);
|
|
1169
|
+
}
|
|
1170
|
+
(_b = (_a = optionsRef.current).onNotFound) == null ? void 0 : _b.call(_a, path);
|
|
1171
|
+
}
|
|
1172
|
+
return hit;
|
|
1173
|
+
},
|
|
1174
|
+
[resolve]
|
|
1175
|
+
);
|
|
1176
|
+
const animate = () => optionsRef.current.duration > 0;
|
|
1177
|
+
const push = useCallback(
|
|
1178
|
+
(path, data) => {
|
|
1179
|
+
if (busyRef.current) return false;
|
|
1180
|
+
const hit = resolveOrNotify(path);
|
|
1181
|
+
if (!hit) return false;
|
|
1182
|
+
const entry = createEntry(hit, path, data);
|
|
1183
|
+
busyRef.current = true;
|
|
1184
|
+
history.push(path, {
|
|
1185
|
+
key: entry.key,
|
|
1186
|
+
index: stateRef.current.entries.length,
|
|
1187
|
+
data
|
|
1188
|
+
});
|
|
1189
|
+
dispatch({ type: "PUSH", entry, animate: animate() });
|
|
1190
|
+
return true;
|
|
1191
|
+
},
|
|
1192
|
+
[resolveOrNotify]
|
|
1193
|
+
);
|
|
1194
|
+
const replace = useCallback(
|
|
1195
|
+
(path, data) => {
|
|
1196
|
+
if (busyRef.current) return false;
|
|
1197
|
+
const hit = resolveOrNotify(path);
|
|
1198
|
+
if (!hit) return false;
|
|
1199
|
+
const entry = createEntry(hit, path, data);
|
|
1200
|
+
history.replace(path, {
|
|
1201
|
+
key: entry.key,
|
|
1202
|
+
index: stateRef.current.entries.length - 1,
|
|
1203
|
+
data
|
|
1204
|
+
});
|
|
1205
|
+
dispatch({ type: "REPLACE", entry });
|
|
1206
|
+
return true;
|
|
1207
|
+
},
|
|
1208
|
+
[resolveOrNotify]
|
|
1209
|
+
);
|
|
1210
|
+
const reset = useCallback(
|
|
1211
|
+
(path, data) => {
|
|
1212
|
+
if (busyRef.current) return false;
|
|
1213
|
+
const hit = resolveOrNotify(path);
|
|
1214
|
+
if (!hit) return false;
|
|
1215
|
+
const entry = createEntry(hit, path, data);
|
|
1216
|
+
history.replace(path, { key: entry.key, index: 0, data });
|
|
1217
|
+
dispatch({ type: "RESET", entry });
|
|
1218
|
+
return true;
|
|
1219
|
+
},
|
|
1220
|
+
[resolveOrNotify]
|
|
1221
|
+
);
|
|
1222
|
+
const pop = useCallback((count = 1) => {
|
|
1223
|
+
var _a, _b;
|
|
1224
|
+
if (busyRef.current) return false;
|
|
1225
|
+
const { entries } = stateRef.current;
|
|
1226
|
+
if (entries.length <= 1) {
|
|
1227
|
+
(_b = (_a = optionsRef.current).onBackAtRoot) == null ? void 0 : _b.call(_a);
|
|
1228
|
+
return false;
|
|
1229
|
+
}
|
|
1230
|
+
const delta = Math.min(Math.max(1, count), entries.length - 1);
|
|
1231
|
+
const targetIndex = entries.length - 1 - delta;
|
|
1232
|
+
busyRef.current = true;
|
|
1233
|
+
dispatch({ type: "POP_TO", index: targetIndex, animate: animate() });
|
|
1234
|
+
void history.travel(-delta);
|
|
1235
|
+
return true;
|
|
1236
|
+
}, []);
|
|
1237
|
+
const back = useCallback(() => pop(1), [pop]);
|
|
1238
|
+
const popTo = useCallback(
|
|
1239
|
+
(path) => {
|
|
1240
|
+
const { entries } = stateRef.current;
|
|
1241
|
+
for (let i = entries.length - 2; i >= 0; i -= 1) {
|
|
1242
|
+
if (entries[i].path === path || entries[i].pattern === path) {
|
|
1243
|
+
return pop(entries.length - 1 - i);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return false;
|
|
1247
|
+
},
|
|
1248
|
+
[pop]
|
|
1249
|
+
);
|
|
1250
|
+
const endTransition = useCallback(() => {
|
|
1251
|
+
dispatch({ type: "END_TRANSITION" });
|
|
1252
|
+
}, []);
|
|
1253
|
+
useEffect4(() => {
|
|
1254
|
+
const onPop = (historyState) => {
|
|
1255
|
+
const { entries } = stateRef.current;
|
|
1256
|
+
const index = historyState.key ? entries.findIndex((entry2) => entry2.key === historyState.key) : -1;
|
|
1257
|
+
if (index !== -1) {
|
|
1258
|
+
if (index < entries.length - 1) {
|
|
1259
|
+
busyRef.current = true;
|
|
1260
|
+
dispatch({ type: "POP_TO", index, animate: animate() });
|
|
1261
|
+
}
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const hit = resolveOrNotify(historyState.path);
|
|
1265
|
+
if (!hit) {
|
|
1266
|
+
busyRef.current = false;
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
const entry = createEntry(
|
|
1270
|
+
hit,
|
|
1271
|
+
historyState.path,
|
|
1272
|
+
historyState.data,
|
|
1273
|
+
historyState.key
|
|
1274
|
+
);
|
|
1275
|
+
if (typeof historyState.index === "number" && historyState.index === entries.length) {
|
|
1276
|
+
busyRef.current = true;
|
|
1277
|
+
dispatch({ type: "PUSH", entry, animate: animate() });
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
dispatch({ type: "RESET", entry });
|
|
1281
|
+
};
|
|
1282
|
+
const removeListener = history.addListener(onPop);
|
|
1283
|
+
return () => {
|
|
1284
|
+
removeListener();
|
|
1285
|
+
};
|
|
1286
|
+
}, [resolveOrNotify]);
|
|
1287
|
+
const restoredRef = useRef4(false);
|
|
1288
|
+
useEffect4(() => {
|
|
1289
|
+
if (restoredRef.current) return;
|
|
1290
|
+
restoredRef.current = true;
|
|
1291
|
+
const root = stateRef.current.entries[0];
|
|
1292
|
+
const url = currentUrl();
|
|
1293
|
+
const hit = url === root.path ? null : resolve(url);
|
|
1294
|
+
if (!hit || hit.activity === optionsRef.current.initialActivity) {
|
|
1295
|
+
history.replace(root.path, { key: root.key, index: 0 });
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
const savedData = history.getCurrentState().data;
|
|
1299
|
+
const top = createEntry(hit, url, savedData);
|
|
1300
|
+
if (optionsRef.current.restoreStack === "top-only") {
|
|
1301
|
+
history.replace(url, { key: top.key, index: 0, data: savedData });
|
|
1302
|
+
dispatch({ type: "RESTORE", entries: [top] });
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
history.replace(root.path, { key: root.key, index: 0 });
|
|
1306
|
+
history.push(url, { key: top.key, index: 1, data: savedData });
|
|
1307
|
+
dispatch({ type: "RESTORE", entries: [root, top] });
|
|
1308
|
+
}, [resolve]);
|
|
1309
|
+
useEffect4(() => {
|
|
1310
|
+
if (!state.transition) {
|
|
1311
|
+
busyRef.current = false;
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
const timer = setTimeout(
|
|
1315
|
+
endTransition,
|
|
1316
|
+
optionsRef.current.duration + TRANSITION_END_GRACE_MS
|
|
1317
|
+
);
|
|
1318
|
+
return () => clearTimeout(timer);
|
|
1319
|
+
}, [state, endTransition]);
|
|
1320
|
+
return { state, push, replace, reset, back, pop, popTo, endTransition };
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// src/stack-router/stack-router-provider.tsx
|
|
1324
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "@emotion/react/jsx-runtime";
|
|
1325
|
+
var StackRouterContext = createContext2(null);
|
|
1326
|
+
var useStackRouter = () => {
|
|
1327
|
+
const context = useContext2(StackRouterContext);
|
|
1328
|
+
if (!context) {
|
|
1329
|
+
throw new Error("useStackRouter must be used within a StackRouterProvider");
|
|
1330
|
+
}
|
|
1331
|
+
return context;
|
|
1332
|
+
};
|
|
1333
|
+
var isDevEnv = () => {
|
|
1334
|
+
var _a;
|
|
1335
|
+
return typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.NODE_ENV) === "development";
|
|
1336
|
+
};
|
|
1337
|
+
var StackRouterProvider = ({
|
|
1338
|
+
initialActivity,
|
|
1339
|
+
Activities = [],
|
|
1340
|
+
maxWidth = "600px",
|
|
1341
|
+
isShowHeader = false,
|
|
1342
|
+
enableDevTools,
|
|
1343
|
+
transition = "slide",
|
|
1344
|
+
duration = STACK_TRANSITION_MS,
|
|
1345
|
+
restoreStack = "with-root",
|
|
1346
|
+
safeArea = false,
|
|
1347
|
+
onBackAtRoot,
|
|
1348
|
+
onNotFound,
|
|
1349
|
+
bridge
|
|
1350
|
+
}) => {
|
|
1351
|
+
var _a, _b;
|
|
1352
|
+
const reducedMotion = usePrefersReducedMotion();
|
|
1353
|
+
const effectiveDuration = transition === "none" || reducedMotion ? 0 : duration;
|
|
1354
|
+
const navigation = useStackNavigation({
|
|
1355
|
+
initialActivity,
|
|
1356
|
+
activities: Activities,
|
|
1357
|
+
duration: effectiveDuration,
|
|
1358
|
+
restoreStack,
|
|
1359
|
+
onBackAtRoot,
|
|
1360
|
+
onNotFound
|
|
1361
|
+
});
|
|
1362
|
+
const { state, endTransition } = navigation;
|
|
1363
|
+
const elementFor = (pattern) => {
|
|
1364
|
+
var _a2, _b2;
|
|
1365
|
+
return (_b2 = (_a2 = Activities.find((activity) => activity.path === pattern)) == null ? void 0 : _a2.element) != null ? _b2 : initialActivity.path === pattern ? initialActivity.element : null;
|
|
1366
|
+
};
|
|
1367
|
+
const top = state.entries[state.entries.length - 1];
|
|
1368
|
+
const value = useMemo(
|
|
1369
|
+
() => ({
|
|
1370
|
+
currentPath: top.path,
|
|
1371
|
+
currentState: {
|
|
1372
|
+
path: top.path,
|
|
1373
|
+
data: top.data,
|
|
1374
|
+
key: top.key,
|
|
1375
|
+
index: state.entries.length - 1
|
|
1376
|
+
},
|
|
1377
|
+
depth: state.entries.length,
|
|
1378
|
+
canGoBack: state.entries.length > 1,
|
|
1379
|
+
isTransitioning: state.transition !== null,
|
|
1380
|
+
entries: state.entries,
|
|
1381
|
+
visiblePages: state.entries.map((entry) => ({
|
|
1382
|
+
path: entry.path,
|
|
1383
|
+
element: elementFor(entry.pattern)
|
|
1384
|
+
})),
|
|
1385
|
+
Activities,
|
|
1386
|
+
initialActivity,
|
|
1387
|
+
push: navigation.push,
|
|
1388
|
+
replace: navigation.replace,
|
|
1389
|
+
reset: navigation.reset,
|
|
1390
|
+
back: navigation.back,
|
|
1391
|
+
pop: navigation.pop,
|
|
1392
|
+
popTo: navigation.popTo,
|
|
1393
|
+
forward: () => window.history.forward()
|
|
1394
|
+
}),
|
|
1395
|
+
[state, Activities, initialActivity, navigation]
|
|
1396
|
+
);
|
|
1397
|
+
useStackBridgeSync(bridge, value, onBackAtRoot);
|
|
1398
|
+
const direction = (_b = (_a = state.transition) == null ? void 0 : _a.type) != null ? _b : "push";
|
|
1399
|
+
const pages = getRenderedPages(state);
|
|
1400
|
+
const showDevTools = enableDevTools != null ? enableDevTools : isDevEnv();
|
|
1401
|
+
return /* @__PURE__ */ jsxs3(StackRouterContext.Provider, { value, children: [
|
|
1402
|
+
/* @__PURE__ */ jsx5(LayoutContainer, { safeArea, children: /* @__PURE__ */ jsxs3(Layout, { maxWidth, "data-testid": "layout", children: [
|
|
1403
|
+
isShowHeader && /* @__PURE__ */ jsx5(header_default, {}),
|
|
1404
|
+
/* @__PURE__ */ jsx5(PageContainer, { children: pages.map(({ entry, pageState, isTop }, index) => /* @__PURE__ */ jsx5(
|
|
1405
|
+
ActivityContext.Provider,
|
|
1406
|
+
{
|
|
1407
|
+
value: {
|
|
1408
|
+
key: entry.key,
|
|
1409
|
+
path: entry.path,
|
|
1410
|
+
pattern: entry.pattern,
|
|
1411
|
+
params: entry.params,
|
|
1412
|
+
query: entry.query,
|
|
1413
|
+
data: entry.data,
|
|
1414
|
+
index,
|
|
1415
|
+
isTop
|
|
1416
|
+
},
|
|
1417
|
+
children: /* @__PURE__ */ jsx5(
|
|
1418
|
+
StackPage,
|
|
1419
|
+
{
|
|
1420
|
+
pageState,
|
|
1421
|
+
direction,
|
|
1422
|
+
transitionType: transition,
|
|
1423
|
+
duration: effectiveDuration,
|
|
1424
|
+
zIndex: index,
|
|
1425
|
+
isTop,
|
|
1426
|
+
onAnimationEnd: pageState === "enter" ? endTransition : void 0,
|
|
1427
|
+
children: elementFor(entry.pattern)
|
|
1428
|
+
}
|
|
1429
|
+
)
|
|
1430
|
+
},
|
|
1431
|
+
entry.key
|
|
1432
|
+
)) })
|
|
1433
|
+
] }) }),
|
|
1434
|
+
/* @__PURE__ */ jsx5(
|
|
1435
|
+
StackDevRoadmap,
|
|
1436
|
+
{
|
|
1437
|
+
visible: showDevTools,
|
|
1438
|
+
pages: state.entries.map((entry) => ({ path: entry.path })),
|
|
1439
|
+
currentPath: top.path
|
|
1440
|
+
}
|
|
1441
|
+
)
|
|
1442
|
+
] });
|
|
1443
|
+
};
|
|
1444
|
+
var stack_router_provider_default = StackRouterProvider;
|
|
1445
|
+
var LayoutContainer = styled3.div`
|
|
1446
|
+
width: 100%;
|
|
1447
|
+
height: 100vh;
|
|
1448
|
+
height: 100dvh;
|
|
1449
|
+
display: flex;
|
|
1450
|
+
justify-content: center;
|
|
1451
|
+
background: var(--wvk-page-bg, #ffffff);
|
|
1452
|
+
box-sizing: border-box;
|
|
1453
|
+
${({ safeArea }) => safeArea ? `padding: env(safe-area-inset-top) env(safe-area-inset-right)
|
|
1454
|
+
env(safe-area-inset-bottom) env(safe-area-inset-left);` : ""}
|
|
1455
|
+
`;
|
|
1456
|
+
var Layout = styled3.div`
|
|
1457
|
+
width: 100%;
|
|
1458
|
+
max-width: ${({ maxWidth }) => maxWidth};
|
|
1459
|
+
height: 100%;
|
|
1460
|
+
display: flex;
|
|
1461
|
+
flex-direction: column;
|
|
1462
|
+
position: relative;
|
|
1463
|
+
overflow: hidden;
|
|
1464
|
+
`;
|
|
1465
|
+
var PageContainer = styled3.div`
|
|
1466
|
+
position: relative;
|
|
1467
|
+
flex: 1;
|
|
1468
|
+
width: 100%;
|
|
1469
|
+
overflow: hidden;
|
|
1470
|
+
`;
|
|
1471
|
+
|
|
1472
|
+
// src/stack-router/_components/back-router.tsx
|
|
1473
|
+
import { jsx as jsx6 } from "@emotion/react/jsx-runtime";
|
|
1474
|
+
var BackRouter = ({ label = "\uB4A4\uB85C \uAC00\uAE30" }) => {
|
|
1475
|
+
const { back } = useStackRouter();
|
|
1476
|
+
return /* @__PURE__ */ jsx6(BackButton, { type: "button", "aria-label": label, onClick: () => back(), children: /* @__PURE__ */ jsx6(BackIcon, {}) });
|
|
1477
|
+
};
|
|
1478
|
+
var back_router_default = BackRouter;
|
|
1479
|
+
var BackButton = styled4.button`
|
|
1480
|
+
display: flex;
|
|
1481
|
+
align-items: center;
|
|
1482
|
+
justify-content: center;
|
|
1483
|
+
width: 40px;
|
|
1484
|
+
height: 40px;
|
|
1485
|
+
border: none;
|
|
1486
|
+
background: none;
|
|
1487
|
+
cursor: pointer;
|
|
1488
|
+
padding: 8px;
|
|
1489
|
+
border-radius: 50%;
|
|
1490
|
+
transition: all 0.2s;
|
|
1491
|
+
color: var(--wvk-back-color, #000000);
|
|
1492
|
+
|
|
1493
|
+
&:hover {
|
|
1494
|
+
color: var(--wvk-back-hover-color, #666666);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
&:active {
|
|
1498
|
+
color: var(--wvk-back-active-color, #999999);
|
|
1499
|
+
}
|
|
1500
|
+
`;
|
|
1501
|
+
var BackIcon = () => /* @__PURE__ */ jsx6(
|
|
1502
|
+
"svg",
|
|
1503
|
+
{
|
|
1504
|
+
width: "32",
|
|
1505
|
+
height: "32",
|
|
1506
|
+
viewBox: "0 0 24 24",
|
|
1507
|
+
fill: "none",
|
|
1508
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1509
|
+
"aria-hidden": "true",
|
|
1510
|
+
children: /* @__PURE__ */ jsx6(
|
|
1511
|
+
"path",
|
|
1512
|
+
{
|
|
1513
|
+
d: "M12 20L4 12L12 4",
|
|
1514
|
+
stroke: "currentColor",
|
|
1515
|
+
strokeWidth: "2.5",
|
|
1516
|
+
strokeLinecap: "round",
|
|
1517
|
+
strokeLinejoin: "round"
|
|
1518
|
+
}
|
|
1519
|
+
)
|
|
1520
|
+
}
|
|
1521
|
+
);
|
|
1522
|
+
export {
|
|
1523
|
+
BACK_REQUEST_METHOD,
|
|
1524
|
+
BACK_TIMEOUT_MS,
|
|
1525
|
+
BRIDGE_ERROR_CODES,
|
|
1526
|
+
BRIDGE_NAMESPACE,
|
|
1527
|
+
BRIDGE_PROTOCOL_VERSION,
|
|
1528
|
+
BRIDGE_RESPONSE_METHOD,
|
|
1529
|
+
BRIDGE_TIMEOUT_MS,
|
|
1530
|
+
back_router_default as BackRouter,
|
|
1531
|
+
BridgeError,
|
|
1532
|
+
Direction,
|
|
1533
|
+
FLUTTER_LEGACY_STACK_HANDLERS,
|
|
1534
|
+
FLUTTER_READY_EVENT,
|
|
1535
|
+
FLUTTER_UNIFIED_HANDLER,
|
|
1536
|
+
HistoryAction,
|
|
1537
|
+
RN_BRIDGE_HANDLER,
|
|
1538
|
+
STACK_BRIDGE_METHODS,
|
|
1539
|
+
STACK_TRANSITION_MS,
|
|
1540
|
+
StackDevRoadmap,
|
|
1541
|
+
header_default as StackHeader,
|
|
1542
|
+
stack_router_provider_default as StackRouterProvider,
|
|
1543
|
+
WebViewBridgeClient,
|
|
1544
|
+
WebViewBridgeProvider,
|
|
1545
|
+
assertStackUrl,
|
|
1546
|
+
createBridgeEnvelope,
|
|
1547
|
+
createBridgeId,
|
|
1548
|
+
createBridgeResponse,
|
|
1549
|
+
createBridgeTransport,
|
|
1550
|
+
createStackRouterBridge,
|
|
1551
|
+
createWebViewBridgeClient,
|
|
1552
|
+
detectBridgeHost,
|
|
1553
|
+
emitBridgeEnvelope,
|
|
1554
|
+
findActivity,
|
|
1555
|
+
getStackRouterBridge,
|
|
1556
|
+
handleBridgeMessage,
|
|
1557
|
+
history,
|
|
1558
|
+
installBridgeHost,
|
|
1559
|
+
installStackRouterBridge,
|
|
1560
|
+
isBridgeEnvelope,
|
|
1561
|
+
isNativeBridgeHost,
|
|
1562
|
+
isSafeStackUrl,
|
|
1563
|
+
matchPath,
|
|
1564
|
+
parseBridgeMessage,
|
|
1565
|
+
parseQuery,
|
|
1566
|
+
resetStackRouterBridgeCache,
|
|
1567
|
+
serializeBridgeMessage,
|
|
1568
|
+
toStackPath,
|
|
1569
|
+
useActivity,
|
|
1570
|
+
useParams,
|
|
1571
|
+
useStackRouter,
|
|
1572
|
+
waitForBridgeHost
|
|
1573
|
+
};
|
|
1574
|
+
//# sourceMappingURL=index.mjs.map
|