@cloudparse/up-miniapps-sdk 0.1.6 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,3859 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ BridgeMocker: () => BridgeMocker,
34
+ DebugConsole: () => DebugConsole,
35
+ GeolocationEvent: () => GeolocationEvent,
36
+ GlobalEvent: () => GlobalEvent,
37
+ Map: () => Map2,
38
+ MapControls: () => MapControls,
39
+ MapControlsContext: () => MapControlsContext,
40
+ MapMenu: () => MapMenu,
41
+ MapMenuDivider: () => MapMenuDivider,
42
+ MapMenuFooter: () => MapMenuFooter,
43
+ MapMenuItem: () => MapMenuItem,
44
+ MapMenuTitle: () => MapMenuTitle,
45
+ MiniAppSDK: () => MiniAppSDK,
46
+ RouteLayer: () => RouteLayer,
47
+ SDKMapProvider: () => SDKMapProvider,
48
+ SDKProvider: () => SDKProvider,
49
+ UIEvent: () => UIEvent,
50
+ default: () => src_default,
51
+ handleClearRoute: () => handleClearRoute,
52
+ handleDrawRoute: () => handleDrawRoute,
53
+ handleRouteRequest: () => handleRouteRequest,
54
+ renderMap: () => renderMap,
55
+ sdk: () => sdk,
56
+ useIsMocked: () => useIsMocked,
57
+ useLiveRouting: () => useLiveRouting,
58
+ useMapControls: () => useMapControls,
59
+ useRoute: () => useRoute,
60
+ useTrimmedRoute: () => useTrimmedRoute,
61
+ useUserLocation: () => useUserLocation
62
+ });
63
+ module.exports = __toCommonJS(src_exports);
64
+
65
+ // src/bridge.ts
66
+ var Bridge = class {
67
+ callbacks = /* @__PURE__ */ new Map();
68
+ eventListeners = /* @__PURE__ */ new Map();
69
+ /**
70
+ * Registers a global listener for messages coming from the native shell.
71
+ * Also listens for standard browser `message` events to support multiple
72
+ * communication channels (e.g., React Native WebView postMessage).
73
+ */
74
+ constructor() {
75
+ if (typeof window !== "undefined") {
76
+ const bridgeReceiver = Object.freeze({
77
+ onNativeMessage: (envelope) => this.handleNativeMessage(envelope)
78
+ });
79
+ if (typeof window.__MiniAppBridge === "undefined") {
80
+ Object.defineProperty(window, "__MiniAppBridge", {
81
+ configurable: true,
82
+ // MUST be true for React StrictMode / HMR unmount/remount cycles
83
+ value: bridgeReceiver,
84
+ writable: false
85
+ });
86
+ }
87
+ window.addEventListener("message", (event) => {
88
+ if (!event.data) return;
89
+ try {
90
+ const data = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
91
+ if (data && typeof data === "object" && (data.callbackId || data.event || data.eventName)) {
92
+ this.handleNativeMessage(data);
93
+ }
94
+ } catch (_e) {
95
+ }
96
+ });
97
+ }
98
+ }
99
+ /**
100
+ * Subscribes to a pushed event from the native shell.
101
+ *
102
+ * @param event The event name to listen for.
103
+ * @param callback Function to execute when the event occurs.
104
+ * @returns An unsubscription function.
105
+ */
106
+ on(event, callback) {
107
+ let listeners = this.eventListeners.get(event);
108
+ if (!listeners) {
109
+ listeners = [];
110
+ this.eventListeners.set(event, listeners);
111
+ }
112
+ listeners.push(callback);
113
+ return () => this.off(event, callback);
114
+ }
115
+ /**
116
+ * Unsubscribes a specific callback from an event.
117
+ *
118
+ * @param event The event name.
119
+ * @param callback The callback function to remove.
120
+ */
121
+ off(event, callback) {
122
+ const listeners = this.eventListeners.get(event);
123
+ if (!listeners) return;
124
+ this.eventListeners.set(
125
+ event,
126
+ listeners.filter((cb) => cb !== callback)
127
+ );
128
+ }
129
+ /**
130
+ * Sends a message to the native shell and waits for a response.
131
+ *
132
+ * @template T The expected response type.
133
+ * @param type The action type (e.g., "GET_LOCATION").
134
+ * @param payload Data associated with the action.
135
+ * @param timeoutMs Time in milliseconds to wait for a response before rejecting.
136
+ * @returns A promise that resolves with the native response.
137
+ */
138
+ send(type, payload, timeoutMs = 3e4) {
139
+ if (!this.isNative()) {
140
+ return this.handleMockMessage(type, payload);
141
+ }
142
+ return new Promise((resolve, reject) => {
143
+ const callbackId = `${type}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
144
+ const timeout = setTimeout(() => {
145
+ if (this.callbacks.has(callbackId)) {
146
+ this.callbacks.delete(callbackId);
147
+ reject(new Error(`Bridge request timeout: ${type}`));
148
+ }
149
+ }, timeoutMs);
150
+ this.callbacks.set(callbackId, {
151
+ reject,
152
+ resolve,
153
+ timeout
154
+ });
155
+ const message = {
156
+ callbackId,
157
+ payload,
158
+ type
159
+ };
160
+ const win = window;
161
+ if (win.ReactNativeWebView?.postMessage) {
162
+ win.ReactNativeWebView.postMessage(JSON.stringify(message));
163
+ } else {
164
+ clearTimeout(timeout);
165
+ this.callbacks.delete(callbackId);
166
+ reject(new Error("Bridge not available"));
167
+ }
168
+ });
169
+ }
170
+ /**
171
+ * Internal handler for all incoming messages from the native shell.
172
+ * Matches incoming payloads to either pending callbacks or event listeners.
173
+ *
174
+ * @internal
175
+ * @param envelope The message envelope received from native code.
176
+ */
177
+ handleNativeMessage(envelope) {
178
+ if (!envelope || typeof envelope !== "object") return;
179
+ const callbackId = envelope.callbackId;
180
+ if (callbackId && typeof callbackId === "string") {
181
+ const callback = this.callbacks.get(callbackId);
182
+ if (callback) {
183
+ clearTimeout(callback.timeout);
184
+ const { resolve, reject } = callback;
185
+ if (envelope.success === false || envelope.error) {
186
+ reject(envelope.error || "Unknown error");
187
+ } else {
188
+ resolve(envelope.value ?? envelope.payload ?? envelope);
189
+ }
190
+ this.callbacks.delete(callbackId);
191
+ return;
192
+ }
193
+ }
194
+ const eventName = envelope.eventName || envelope.event;
195
+ if (eventName) {
196
+ if (envelope.type === "EVENT" || !envelope.type || envelope.event) {
197
+ const listeners = this.eventListeners.get(eventName);
198
+ if (listeners) {
199
+ for (const listener of listeners) {
200
+ try {
201
+ listener(envelope.payload || envelope.value);
202
+ } catch (e) {
203
+ console.error(`Error in event listener for ${eventName}:`, e);
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ /**
211
+ * Internal handler for mock message processing, overrideable in test environments
212
+ */
213
+ handleMockMessage(type, payload) {
214
+ console.warn(`[Bridge] Mock mode active: Unhandled message type ${type}`);
215
+ return Promise.resolve();
216
+ }
217
+ /**
218
+ * Determine environment. Check flag or cache result.
219
+ */
220
+ isNative() {
221
+ if (typeof window === "undefined") return false;
222
+ return window.__UP_REACT_NATIVE_APP__ === true || !!window.ReactNativeWebView;
223
+ }
224
+ };
225
+
226
+ // src/components/Map/components/MapDrawer.tsx
227
+ var import_react = require("react");
228
+ var import_jsx_runtime = require("react/jsx-runtime");
229
+ var resolveHeightToPixels = (height, containerHeight) => {
230
+ if (typeof height === "number") {
231
+ return height;
232
+ }
233
+ if (height.endsWith("%")) {
234
+ return parseFloat(height) / 100 * containerHeight;
235
+ }
236
+ return parseFloat(height);
237
+ };
238
+ var MapDrawer = (0, import_react.forwardRef)(
239
+ ({
240
+ menuItems,
241
+ state,
242
+ onChangeState,
243
+ side,
244
+ etaBarHeight = 0,
245
+ etaData,
246
+ touchStartY,
247
+ setTouchStartY,
248
+ currentTouchY,
249
+ setCurrentTouchY,
250
+ isDragging,
251
+ clampedDragOffset,
252
+ drawerPeekHeight,
253
+ drawerFullHeight,
254
+ containerHeight,
255
+ menuContent,
256
+ enableTouchDrag = false,
257
+ showScrollToTopButton = true,
258
+ scrollToTopThreshold = 300,
259
+ safeAreaInsets = { bottom: 0, left: 0, right: 0, top: 0 }
260
+ }, ref) => {
261
+ const scrollContainerRef = (0, import_react.useRef)(null);
262
+ const containerRef = (0, import_react.useRef)(null);
263
+ (0, import_react.useImperativeHandle)(
264
+ ref,
265
+ () => scrollContainerRef.current
266
+ );
267
+ const handleHandleClick = () => {
268
+ if (side === "left") {
269
+ onChangeState(state === "closed" ? "full" : "closed");
270
+ } else {
271
+ if (state === "closed") {
272
+ onChangeState("peek");
273
+ } else if (state === "peek") {
274
+ onChangeState("full");
275
+ } else {
276
+ onChangeState("peek");
277
+ }
278
+ }
279
+ };
280
+ const [showScrollTop, setShowScrollTop] = (0, import_react.useState)(false);
281
+ const handleScroll = (e) => {
282
+ const scrollTop = e.currentTarget.scrollTop;
283
+ setShowScrollTop(state !== "closed" && scrollTop > scrollToTopThreshold);
284
+ };
285
+ (0, import_react.useEffect)(() => {
286
+ if (state === "closed") {
287
+ setShowScrollTop(false);
288
+ } else {
289
+ const scrollTop = scrollContainerRef.current?.scrollTop || 0;
290
+ setShowScrollTop(scrollTop > scrollToTopThreshold);
291
+ }
292
+ }, [state, scrollToTopThreshold]);
293
+ const isDraggingSheet = (0, import_react.useRef)(false);
294
+ const hasDeterminedDirection = (0, import_react.useRef)(false);
295
+ const isTouchOnContent = (0, import_react.useRef)(false);
296
+ const stateRef = (0, import_react.useRef)(state);
297
+ const touchStartYRef = (0, import_react.useRef)(touchStartY);
298
+ const currentTouchYRef = (0, import_react.useRef)(currentTouchY);
299
+ (0, import_react.useEffect)(() => {
300
+ stateRef.current = state;
301
+ }, [state]);
302
+ (0, import_react.useEffect)(() => {
303
+ touchStartYRef.current = touchStartY;
304
+ }, [touchStartY]);
305
+ (0, import_react.useEffect)(() => {
306
+ currentTouchYRef.current = currentTouchY;
307
+ }, [currentTouchY]);
308
+ (0, import_react.useEffect)(() => {
309
+ const container = containerRef.current;
310
+ if (!container || side !== "bottom" || !enableTouchDrag) return;
311
+ const handleNativeTouchStart = (e) => {
312
+ const clientY = e.targetTouches[0].clientY;
313
+ setTouchStartY(clientY);
314
+ setCurrentTouchY(clientY);
315
+ isDraggingSheet.current = false;
316
+ hasDeterminedDirection.current = false;
317
+ const target = e.target;
318
+ isTouchOnContent.current = scrollContainerRef.current ? scrollContainerRef.current.contains(target) : false;
319
+ };
320
+ const handleNativeTouchMove = (e) => {
321
+ const startY = touchStartYRef.current;
322
+ if (startY === null) return;
323
+ const currentY = e.targetTouches[0].clientY;
324
+ const deltaY = startY - currentY;
325
+ const scrollTop = scrollContainerRef.current ? scrollContainerRef.current.scrollTop : 0;
326
+ const clientHeight = scrollContainerRef.current ? scrollContainerRef.current.clientHeight : 0;
327
+ const scrollHeight = scrollContainerRef.current ? scrollContainerRef.current.scrollHeight : 0;
328
+ const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
329
+ const currentState = stateRef.current;
330
+ if (!hasDeterminedDirection.current) {
331
+ if (!isTouchOnContent.current) {
332
+ isDraggingSheet.current = true;
333
+ hasDeterminedDirection.current = true;
334
+ } else if (currentState !== "full") {
335
+ if (deltaY > 0 && isAtBottom) {
336
+ isDraggingSheet.current = true;
337
+ hasDeterminedDirection.current = true;
338
+ } else if (deltaY < 0 && scrollTop <= 0) {
339
+ isDraggingSheet.current = true;
340
+ hasDeterminedDirection.current = true;
341
+ } else {
342
+ isDraggingSheet.current = false;
343
+ hasDeterminedDirection.current = true;
344
+ }
345
+ } else {
346
+ if (deltaY < 0 && scrollTop <= 0) {
347
+ isDraggingSheet.current = true;
348
+ hasDeterminedDirection.current = true;
349
+ } else {
350
+ isDraggingSheet.current = false;
351
+ hasDeterminedDirection.current = true;
352
+ }
353
+ }
354
+ } else if (!isDraggingSheet.current && isTouchOnContent.current) {
355
+ if (deltaY < 0 && scrollTop <= 0) {
356
+ isDraggingSheet.current = true;
357
+ setTouchStartY(currentY);
358
+ return;
359
+ }
360
+ if (deltaY > 0 && isAtBottom && currentState !== "full") {
361
+ isDraggingSheet.current = true;
362
+ setTouchStartY(currentY);
363
+ return;
364
+ }
365
+ }
366
+ if (isDraggingSheet.current) {
367
+ if (e.cancelable) {
368
+ e.preventDefault();
369
+ }
370
+ const parentContainer = container.parentElement;
371
+ if (parentContainer) {
372
+ const containerH2 = containerHeight || parentContainer.getBoundingClientRect().height;
373
+ const fullPx2 = resolveHeightToPixels(
374
+ drawerFullHeight || "85%",
375
+ containerH2
376
+ );
377
+ const peekPx2 = resolveHeightToPixels(
378
+ drawerPeekHeight || "15%",
379
+ containerH2
380
+ );
381
+ const closedPx = 32 + (currentState === "closed" ? safeAreaInsets.bottom : 0);
382
+ const dragOffset = currentY - startY;
383
+ let localClamped = dragOffset;
384
+ if (currentState === "full") {
385
+ localClamped = Math.max(
386
+ 0,
387
+ Math.min(fullPx2 - closedPx, dragOffset)
388
+ );
389
+ } else if (currentState === "peek") {
390
+ localClamped = Math.max(
391
+ -(fullPx2 - peekPx2),
392
+ Math.min(peekPx2 - closedPx, dragOffset)
393
+ );
394
+ } else if (currentState === "closed") {
395
+ localClamped = Math.min(
396
+ 0,
397
+ Math.max(-(fullPx2 - closedPx), dragOffset)
398
+ );
399
+ }
400
+ parentContainer.style.setProperty(
401
+ "--drag-offset",
402
+ `${localClamped}px`
403
+ );
404
+ }
405
+ }
406
+ };
407
+ const handleNativeTouchEnd = (e) => {
408
+ const startY = touchStartYRef.current;
409
+ const currentY = currentTouchYRef.current;
410
+ const parentContainer = container.parentElement;
411
+ if (startY === null || currentY === null) {
412
+ if (parentContainer) {
413
+ parentContainer.style.setProperty("--drag-offset", "0px");
414
+ }
415
+ return;
416
+ }
417
+ if (!isDraggingSheet.current) {
418
+ setTouchStartY(null);
419
+ setCurrentTouchY(null);
420
+ if (parentContainer) {
421
+ parentContainer.style.setProperty("--drag-offset", "0px");
422
+ }
423
+ return;
424
+ }
425
+ const touchEndY = e.changedTouches[0].clientY;
426
+ const dragDistance = Math.abs(touchEndY - startY);
427
+ if (dragDistance < 10) {
428
+ setTouchStartY(null);
429
+ setCurrentTouchY(null);
430
+ if (parentContainer) {
431
+ parentContainer.style.setProperty("--drag-offset", "0px");
432
+ }
433
+ return;
434
+ }
435
+ const containerH2 = containerHeight || (parentContainer ? parentContainer.getBoundingClientRect().height : window.innerHeight);
436
+ const containerTop = parentContainer ? parentContainer.getBoundingClientRect().top : 0;
437
+ const touchYInContainer = touchEndY - containerTop;
438
+ const drawerHeightFromBottom = containerH2 - touchYInContainer;
439
+ const fullPx2 = resolveHeightToPixels(
440
+ drawerFullHeight || "85%",
441
+ containerH2
442
+ );
443
+ const peekPx2 = resolveHeightToPixels(
444
+ drawerPeekHeight || "15%",
445
+ containerH2
446
+ );
447
+ const currentState = stateRef.current;
448
+ const closedPx = 32 + (currentState === "closed" ? safeAreaInsets.bottom : 0);
449
+ const distToFull = Math.abs(drawerHeightFromBottom - fullPx2);
450
+ const distToPeek = Math.abs(drawerHeightFromBottom - peekPx2);
451
+ const distToClosed = Math.abs(drawerHeightFromBottom - closedPx);
452
+ const minDist = Math.min(distToFull, distToPeek, distToClosed);
453
+ let targetState = "closed";
454
+ if (minDist === distToFull) {
455
+ targetState = "full";
456
+ } else if (minDist === distToPeek) {
457
+ targetState = "peek";
458
+ } else {
459
+ targetState = "closed";
460
+ }
461
+ setTouchStartY(null);
462
+ setCurrentTouchY(null);
463
+ onChangeState(targetState);
464
+ requestAnimationFrame(() => {
465
+ if (parentContainer) {
466
+ parentContainer.style.setProperty("--drag-offset", "0px");
467
+ }
468
+ });
469
+ };
470
+ container.addEventListener("touchstart", handleNativeTouchStart, {
471
+ passive: true
472
+ });
473
+ container.addEventListener("touchmove", handleNativeTouchMove, {
474
+ passive: false
475
+ });
476
+ container.addEventListener("touchend", handleNativeTouchEnd, {
477
+ passive: true
478
+ });
479
+ return () => {
480
+ container.removeEventListener("touchstart", handleNativeTouchStart);
481
+ container.removeEventListener("touchmove", handleNativeTouchMove);
482
+ container.removeEventListener("touchend", handleNativeTouchEnd);
483
+ };
484
+ }, [
485
+ side,
486
+ enableTouchDrag,
487
+ containerHeight,
488
+ drawerFullHeight,
489
+ drawerPeekHeight,
490
+ safeAreaInsets,
491
+ onChangeState,
492
+ setTouchStartY,
493
+ setCurrentTouchY
494
+ ]);
495
+ const isBackdropActive = state === "full";
496
+ const backdropStyles = isBackdropActive ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none";
497
+ const containerH = containerHeight || window.innerHeight;
498
+ const fullPx = resolveHeightToPixels(drawerFullHeight || "85%", containerH);
499
+ const peekPx = resolveHeightToPixels(drawerPeekHeight || "15%", containerH);
500
+ const shiftPx = fullPx - peekPx;
501
+ const getBottomDrawerTransform = () => {
502
+ if (state === "full") {
503
+ return "translateY(var(--drag-offset, 0px))";
504
+ }
505
+ if (state === "peek") {
506
+ return `translateY(calc(${shiftPx}px + var(--drag-offset, 0px)))`;
507
+ }
508
+ const closedVisibleHeight = 32;
509
+ const closedShiftPx = fullPx - closedVisibleHeight;
510
+ return `translateY(calc(${closedShiftPx}px + var(--drag-offset, 0px)))`;
511
+ };
512
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
514
+ .no-scrollbar {
515
+ -ms-overflow-style: none;
516
+ scrollbar-width: none;
517
+ }
518
+ .no-scrollbar::-webkit-scrollbar {
519
+ display: none;
520
+ }
521
+ ` }),
522
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
523
+ "div",
524
+ {
525
+ className: `absolute inset-0 bg-black/45 backdrop-blur-[1px] transition-opacity duration-300 z-30 ${backdropStyles}`,
526
+ onClick: () => onChangeState("closed")
527
+ }
528
+ ),
529
+ side === "left" ? (
530
+ /* Left Drawer Panel */
531
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
532
+ "div",
533
+ {
534
+ className: `absolute top-4 left-0 w-80 bg-zinc-950/85 backdrop-blur-md border-r border-y border-white/10 rounded-r-2xl shadow-2xl flex z-40 transition-transform duration-300 ease-out select-none ${state !== "closed" ? "translate-x-0" : "translate-x-[calc(-100%+24px)]"}`,
535
+ ref: containerRef,
536
+ style: {
537
+ bottom: `calc(1rem + ${etaBarHeight}px)`
538
+ },
539
+ children: [
540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 flex flex-col h-full overflow-hidden pl-4 py-4 pr-1 relative", children: [
541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-between pb-3 border-b border-white/10 mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-bold tracking-wider text-zinc-400 uppercase", children: "Menu" }) }),
542
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
543
+ "div",
544
+ {
545
+ className: "flex-1 overflow-y-auto no-scrollbar pr-2",
546
+ onScroll: handleScroll,
547
+ ref: scrollContainerRef,
548
+ children: menuContent ? menuContent : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-1.5", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
549
+ "button",
550
+ {
551
+ className: `flex items-center gap-3 w-full py-3 px-3.5 rounded-xl text-left cursor-pointer transition-all duration-150 ${item.disabled ? "opacity-45 cursor-not-allowed pointer-events-none" : "hover:bg-white/5 active:bg-white/10 text-white"}`,
552
+ disabled: item.disabled,
553
+ onClick: () => {
554
+ if (!item.disabled) {
555
+ item.onPress();
556
+ onChangeState("closed");
557
+ }
558
+ },
559
+ children: [
560
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 text-zinc-300", children: [
561
+ item.icon,
562
+ item.badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute -top-1 -right-1 bg-cyan-500 text-black text-[9px] font-extrabold min-w-[15px] h-3.5 rounded-full flex items-center justify-center px-1 border border-zinc-950 shadow", children: item.badge })
563
+ ] }),
564
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-medium tracking-wide flex-1", children: item.label })
565
+ ]
566
+ },
567
+ item.id
568
+ )) })
569
+ }
570
+ ),
571
+ showScrollToTopButton && showScrollTop && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
572
+ "button",
573
+ {
574
+ className: "absolute bottom-6 right-6 z-50 flex items-center justify-center w-10 h-10 rounded-full bg-cyan-500 hover:bg-cyan-400 active:scale-95 text-black shadow-lg shadow-cyan-500/25 transition-all duration-200 cursor-pointer",
575
+ onClick: () => {
576
+ scrollContainerRef.current?.scrollTo({
577
+ behavior: "smooth",
578
+ top: 0
579
+ });
580
+ },
581
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
582
+ "svg",
583
+ {
584
+ fill: "none",
585
+ height: "20",
586
+ stroke: "currentColor",
587
+ strokeLinecap: "round",
588
+ strokeLinejoin: "round",
589
+ strokeWidth: "2.5",
590
+ viewBox: "0 0 24 24",
591
+ width: "20",
592
+ xmlns: "http://www.w3.org/2000/svg",
593
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "18 15 12 9 6 15" })
594
+ }
595
+ )
596
+ }
597
+ )
598
+ ] }),
599
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
600
+ "div",
601
+ {
602
+ className: "w-6 flex items-center justify-center cursor-pointer hover:bg-white/5 active:bg-white/10 transition-colors border-l border-white/5 rounded-r-2xl",
603
+ onClick: handleHandleClick,
604
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-1.5 h-12 bg-white/20 rounded-full transition-colors group-hover:bg-white/40" })
605
+ }
606
+ )
607
+ ]
608
+ }
609
+ )
610
+ ) : (
611
+ /* Bottom Sheet Panel - Hardware accelerated translateY transitions */
612
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
613
+ "div",
614
+ {
615
+ className: `absolute w-full bg-zinc-950/85 backdrop-blur-md border border-white/10 rounded-t-2xl shadow-2xl flex flex-col z-40 select-none ${isDragging ? "transition-none" : "transition-transform duration-300 ease-out"}`,
616
+ ref: containerRef,
617
+ style: {
618
+ bottom: "0px",
619
+ height: typeof drawerFullHeight === "number" ? `${drawerFullHeight}px` : drawerFullHeight || "85%",
620
+ marginBottom: state === "closed" ? `${safeAreaInsets.bottom}px` : "0px",
621
+ transform: getBottomDrawerTransform(),
622
+ transition: isDragging ? "none" : "transform 300ms ease-out, margin-bottom 250ms ease-in-out"
623
+ },
624
+ children: [
625
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
626
+ "div",
627
+ {
628
+ className: "w-full h-8 flex flex-col items-center justify-center cursor-pointer hover:bg-white/5 rounded-t-2xl transition-colors border-b border-white/5",
629
+ onClick: handleHandleClick,
630
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-12 h-1 bg-white/25 rounded-full" })
631
+ }
632
+ ),
633
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
634
+ "div",
635
+ {
636
+ className: "flex-1 flex flex-col overflow-hidden px-4 pb-4 pt-2 transition-[margin-top] duration-250 ease-in-out relative",
637
+ style: {
638
+ marginTop: state === "closed" ? `${safeAreaInsets.bottom}px` : "0px"
639
+ },
640
+ children: [
641
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
642
+ "div",
643
+ {
644
+ className: "flex-1 overflow-y-auto no-scrollbar",
645
+ onScroll: handleScroll,
646
+ ref: scrollContainerRef,
647
+ style: {
648
+ paddingBottom: state === "peek" ? `${shiftPx}px` : "0px"
649
+ },
650
+ children: menuContent ? menuContent : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-1", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
651
+ "button",
652
+ {
653
+ className: `flex items-center gap-3 w-full py-3 px-3.5 rounded-xl text-left cursor-pointer transition-all duration-150 ${item.disabled ? "opacity-45 cursor-not-allowed pointer-events-none" : "hover:bg-white/5 active:bg-white/10 text-white"}`,
654
+ disabled: item.disabled,
655
+ onClick: () => {
656
+ if (!item.disabled) {
657
+ item.onPress();
658
+ onChangeState("closed");
659
+ }
660
+ },
661
+ children: [
662
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 text-zinc-300", children: [
663
+ item.icon,
664
+ item.badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute -top-1 -right-1 bg-cyan-500 text-black text-[9px] font-extrabold min-w-[15px] h-3.5 rounded-full flex items-center justify-center px-1 border border-zinc-950 shadow", children: item.badge })
665
+ ] }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-medium tracking-wide", children: item.label })
667
+ ]
668
+ },
669
+ item.id
670
+ )) })
671
+ }
672
+ ),
673
+ showScrollToTopButton && showScrollTop && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
674
+ "button",
675
+ {
676
+ className: "absolute right-6 z-50 flex items-center justify-center w-10 h-10 rounded-full bg-cyan-500 hover:bg-cyan-400 active:scale-95 text-black shadow-lg shadow-cyan-500/25 transition-all duration-200 cursor-pointer",
677
+ onClick: () => {
678
+ scrollContainerRef.current?.scrollTo({
679
+ behavior: "smooth",
680
+ top: 0
681
+ });
682
+ },
683
+ style: {
684
+ bottom: state === "peek" ? `calc(${24 + shiftPx}px + var(--drag-offset, 0px))` : `calc(24px + var(--drag-offset, 0px))`
685
+ },
686
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
687
+ "svg",
688
+ {
689
+ fill: "none",
690
+ height: "20",
691
+ stroke: "currentColor",
692
+ strokeLinecap: "round",
693
+ strokeLinejoin: "round",
694
+ strokeWidth: "2.5",
695
+ viewBox: "0 0 24 24",
696
+ width: "20",
697
+ xmlns: "http://www.w3.org/2000/svg",
698
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "18 15 12 9 6 15" })
699
+ }
700
+ )
701
+ }
702
+ )
703
+ ]
704
+ }
705
+ )
706
+ ]
707
+ }
708
+ )
709
+ )
710
+ ] });
711
+ }
712
+ );
713
+
714
+ // src/components/Map/components/MapEtaPanel.tsx
715
+ var import_jsx_runtime2 = require("react/jsx-runtime");
716
+ var MapEtaPanel = ({
717
+ etaData,
718
+ layoutMode,
719
+ onHeaderClick,
720
+ style,
721
+ isDragging = false
722
+ }) => {
723
+ if (layoutMode === "closed-banner") {
724
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full h-10 flex items-center justify-between px-4 pb-2 pt-1 border-b border-white/5 bg-zinc-950/15 relative overflow-hidden", children: [
725
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
726
+ "div",
727
+ {
728
+ className: "flex items-center gap-2 cursor-pointer flex-1",
729
+ onClick: onHeaderClick,
730
+ children: [
731
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-2 h-2 rounded-full bg-emerald-500 animate-pulse" }),
732
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-xs font-bold text-emerald-400", children: [
733
+ etaData.etaMins,
734
+ " min (",
735
+ etaData.distanceRemainingKm.toFixed(1),
736
+ " km)"
737
+ ] })
738
+ ]
739
+ }
740
+ ),
741
+ etaData.onAbort && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
742
+ "button",
743
+ {
744
+ className: "px-1 py-1 text-[10px] font-black bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/25 active:scale-95 rounded-md transition-all cursor-pointer pointer-events-auto",
745
+ onClick: (e) => {
746
+ e.stopPropagation();
747
+ etaData.onAbort?.();
748
+ },
749
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
750
+ "svg",
751
+ {
752
+ className: "lucide lucide-x",
753
+ fill: "none",
754
+ height: "18",
755
+ stroke: "currentColor",
756
+ "stroke-linecap": "round",
757
+ "stroke-linejoin": "round",
758
+ "stroke-width": "2.5",
759
+ viewBox: "0 0 24 24",
760
+ width: "18",
761
+ xmlns: "http://www.w3.org/2000/svg",
762
+ children: [
763
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6 6 18" }),
764
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m6 6 12 12" })
765
+ ]
766
+ }
767
+ )
768
+ }
769
+ ),
770
+ etaData.progress !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute bottom-0 left-0 right-0 h-[2px] bg-white/5", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
771
+ "div",
772
+ {
773
+ className: "h-full bg-cyan-400",
774
+ style: { width: `${etaData.progress}%` }
775
+ }
776
+ ) })
777
+ ] });
778
+ }
779
+ if (layoutMode === "open-card") {
780
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mx-4 mt-2 p-4 bg-white/5 border border-white/10 rounded-xl flex flex-col", children: [
781
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
782
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
783
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-2 h-2 rounded-full bg-emerald-500 animate-pulse" }),
784
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px] font-bold text-emerald-400 uppercase tracking-wider", children: etaData.label || "Journey Active" })
785
+ ] }),
786
+ etaData.onAbort && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
787
+ "button",
788
+ {
789
+ className: "px-1 px-1 text-xs font-bold bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/30 active:scale-95 rounded-lg transition-all cursor-pointer pointer-events-auto",
790
+ onClick: etaData.onAbort,
791
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
792
+ "svg",
793
+ {
794
+ className: "lucide lucide-x",
795
+ fill: "none",
796
+ height: "18",
797
+ stroke: "currentColor",
798
+ "stroke-linecap": "round",
799
+ "stroke-linejoin": "round",
800
+ "stroke-width": "2.5",
801
+ viewBox: "0 0 24 24",
802
+ width: "18",
803
+ xmlns: "http://www.w3.org/2000/svg",
804
+ children: [
805
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6 6 18" }),
806
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m6 6 12 12" })
807
+ ]
808
+ }
809
+ )
810
+ }
811
+ )
812
+ ] }),
813
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "grid grid-cols-2 divide-x divide-white/10 py-1.5 mb-2.5", children: [
814
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-center", children: [
815
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-xl font-black text-white leading-none", children: [
816
+ etaData.etaMins,
817
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs text-zinc-400 font-normal", children: " min" })
818
+ ] }),
819
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-[8px] text-zinc-500 font-bold uppercase tracking-wider mt-1", children: "Time Remaining" })
820
+ ] }),
821
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-center", children: [
822
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-xl font-black text-white leading-none", children: [
823
+ etaData.distanceRemainingKm.toFixed(1),
824
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs text-zinc-400 font-normal", children: " km" })
825
+ ] }),
826
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-[8px] text-zinc-500 font-bold uppercase tracking-wider mt-1", children: "Distance Remaining" })
827
+ ] })
828
+ ] }),
829
+ etaData.progress !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-3", children: [
830
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex justify-between text-[9px] font-bold text-zinc-450 mb-1", children: [
831
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Progress" }),
832
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
833
+ Math.round(etaData.progress),
834
+ "%"
835
+ ] })
836
+ ] }),
837
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-1 bg-white/10 rounded-full overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
838
+ "div",
839
+ {
840
+ className: "h-full bg-cyan-400 rounded-full",
841
+ style: { width: `${etaData.progress}%` }
842
+ }
843
+ ) })
844
+ ] }),
845
+ etaData.mode === "preview" && etaData.onStart && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
846
+ "button",
847
+ {
848
+ className: "w-full py-2.5 text-xs font-bold bg-cyan-500 text-black hover:bg-cyan-400 active:scale-95 rounded-lg transition-all cursor-pointer",
849
+ onClick: etaData.onStart,
850
+ children: "Start Guidance"
851
+ }
852
+ )
853
+ ] });
854
+ }
855
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
856
+ "div",
857
+ {
858
+ className: `absolute left-1/2 -translate-x-1/2 z-40 w-[calc(100%-2rem)] max-w-[320px] sm:max-w-[380px] bg-zinc-950/85 backdrop-blur-md border border-white/10 rounded-2xl shadow-2xl p-3 sm:p-4 flex flex-col pointer-events-auto select-none ${isDragging ? "transition-none" : "transition-all duration-300 ease-out"}`,
859
+ style,
860
+ children: [
861
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between mb-2 sm:mb-3", children: [
862
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
863
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-1.5 h-1.5 sm:w-2 sm:h-2 rounded-full bg-emerald-500 animate-pulse" }),
864
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[9px] sm:text-[10px] font-bold text-emerald-400 uppercase tracking-wider", children: etaData.label || "Guidance Active" })
865
+ ] }),
866
+ etaData.onAbort && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
867
+ "button",
868
+ {
869
+ className: "px-1 py-1 text-[10px] sm:text-xs font-bold bg-red-500/20 text-red-400 hover:bg-red-500/30 border border-red-500/30 active:scale-95 rounded-lg transition-all cursor-pointer",
870
+ onClick: etaData.onAbort,
871
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
872
+ "svg",
873
+ {
874
+ className: "lucide lucide-x",
875
+ fill: "none",
876
+ height: "18",
877
+ stroke: "currentColor",
878
+ "stroke-linecap": "round",
879
+ "stroke-linejoin": "round",
880
+ "stroke-width": "2.5",
881
+ viewBox: "0 0 24 24",
882
+ width: "18",
883
+ xmlns: "http://www.w3.org/2000/svg",
884
+ children: [
885
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6 6 18" }),
886
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m6 6 12 12" })
887
+ ]
888
+ }
889
+ )
890
+ }
891
+ )
892
+ ] }),
893
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "grid grid-cols-2 divide-x divide-white/10 py-0.5 sm:py-1 mb-2 sm:mb-3", children: [
894
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-base sm:text-lg font-black text-white leading-none", children: [
895
+ etaData.etaMins,
896
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] sm:text-xs text-zinc-400 font-normal", children: [
897
+ " ",
898
+ "min"
899
+ ] })
900
+ ] }) }),
901
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-base sm:text-lg font-black text-white leading-none", children: [
902
+ etaData.distanceRemainingKm.toFixed(1),
903
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] sm:text-xs text-zinc-400 font-normal", children: [
904
+ " ",
905
+ "km"
906
+ ] })
907
+ ] }) })
908
+ ] }),
909
+ etaData.progress !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
910
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex justify-between text-[8px] sm:text-[9px] font-bold text-zinc-400 mb-1", children: [
911
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Progress" }),
912
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
913
+ Math.round(etaData.progress),
914
+ "%"
915
+ ] })
916
+ ] }),
917
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-1 sm:h-1.5 bg-white/10 rounded-full overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
918
+ "div",
919
+ {
920
+ className: "h-full bg-cyan-400 rounded-full",
921
+ style: { width: `${etaData.progress}%` }
922
+ }
923
+ ) })
924
+ ] }),
925
+ etaData.mode === "preview" && etaData.onStart && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
926
+ "button",
927
+ {
928
+ className: "mt-2.5 sm:mt-3 py-1.5 sm:py-2 text-[11px] sm:text-xs font-bold bg-cyan-500 text-black hover:bg-cyan-400 active:scale-95 rounded-lg transition-all cursor-pointer",
929
+ onClick: etaData.onStart,
930
+ children: "Start Guidance"
931
+ }
932
+ )
933
+ ]
934
+ }
935
+ );
936
+ };
937
+
938
+ // src/components/Map/components/MapRightControls.tsx
939
+ var import_jsx_runtime3 = require("react/jsx-runtime");
940
+ var MapRightControls = ({
941
+ customControls = [],
942
+ etaBarHeight,
943
+ onRecenter,
944
+ isMapCentered,
945
+ mapBearing,
946
+ mapPitch,
947
+ onResetBearing,
948
+ initialViewState,
949
+ drawerState,
950
+ side,
951
+ etaData,
952
+ isDragging,
953
+ clampedDragOffset,
954
+ customRecenterButton,
955
+ customCompassButton,
956
+ drawerPeekHeight,
957
+ insets = { bottom: 0, left: 0, right: 0, top: 0 }
958
+ }) => {
959
+ const aboveControls = customControls.filter(
960
+ (c) => c.position === "above-recenter"
961
+ );
962
+ const belowControls = customControls.filter(
963
+ (c) => c.position === "below-recenter"
964
+ );
965
+ const isCompassVisible = Math.abs(mapBearing) >= 0.5 || Math.abs(mapPitch) >= 0.5;
966
+ const closedHeightOffset = "32px";
967
+ const activeEtaHeight = etaData ? etaBarHeight : drawerState === "closed" ? insets.bottom : 0;
968
+ const peekStyle = typeof drawerPeekHeight === "number" ? `${drawerPeekHeight}px` : drawerPeekHeight || "15%";
969
+ const controlsBottom = side === "bottom" ? drawerState === "closed" ? `calc(${closedHeightOffset} + 1rem + ${activeEtaHeight}px - var(--drag-offset, 0px))` : `calc(${peekStyle} + 1rem + ${etaData ? etaBarHeight : 0}px - var(--drag-offset, 0px))` : `calc(1rem + ${insets.bottom}px)`;
970
+ const isControlsVisible = side !== "bottom" || drawerState !== "full";
971
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
972
+ "div",
973
+ {
974
+ className: `absolute right-4 z-30 flex flex-col items-center gap-3 select-none pointer-events-none ${isDragging ? "transition-none" : "transition-all duration-300 ease-out"} ${isControlsVisible ? "opacity-100 scale-100 pointer-events-auto" : "opacity-0 scale-75 pointer-events-none"}`,
975
+ style: {
976
+ bottom: controlsBottom
977
+ },
978
+ children: [
979
+ aboveControls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex flex-col gap-2 pointer-events-auto", children: aboveControls.map((control) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative group flex items-center", children: [
980
+ control.tooltip && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute right-14 pointer-events-none opacity-0 scale-95 origin-right group-hover:opacity-100 group-hover:scale-100 transition-all duration-150 bg-zinc-900/95 border border-white/10 text-white text-xs px-2.5 py-1.5 rounded-lg shadow-xl whitespace-nowrap", children: control.tooltip }),
981
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
982
+ "button",
983
+ {
984
+ className: `w-12 h-12 rounded-full bg-zinc-950/80 backdrop-blur-md border border-white/10 shadow-lg flex items-center justify-center text-white active:scale-95 transition-all cursor-pointer ${control.activeState ? "ring-2 ring-cyan-400 ring-offset-2 ring-offset-zinc-950" : "hover:bg-zinc-900"}`,
985
+ onClick: control.onPress,
986
+ children: control.icon
987
+ }
988
+ )
989
+ ] }, control.id)) }),
990
+ isCompassVisible && (customCompassButton ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pointer-events-auto", children: customCompassButton }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
991
+ "button",
992
+ {
993
+ className: `w-12 h-12 rounded-full bg-zinc-950/80 backdrop-blur-md border border-white/10 shadow-lg flex items-center justify-center text-white active:scale-95 transition-all duration-300 ${isCompassVisible ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none h-0 w-0 border-none m-0 p-0 overflow-hidden"}`,
994
+ onClick: () => onResetBearing({ pitch: initialViewState?.pitch ?? 0 }),
995
+ style: {
996
+ transform: isCompassVisible ? "scale(1) rotate(0deg)" : "scale(0.7) rotate(0deg)"
997
+ },
998
+ title: "Reset bearing to North",
999
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1000
+ "svg",
1001
+ {
1002
+ className: "w-7 h-7 transition-transform duration-75 ease-out",
1003
+ fill: "none",
1004
+ style: {
1005
+ transform: `rotate(${-mapBearing}deg)`
1006
+ },
1007
+ viewBox: "0 0 24 24",
1008
+ children: [
1009
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1010
+ "circle",
1011
+ {
1012
+ cx: "12",
1013
+ cy: "12",
1014
+ r: "9.5",
1015
+ stroke: "white",
1016
+ strokeOpacity: "0.2",
1017
+ strokeWidth: "1"
1018
+ }
1019
+ ),
1020
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 4.5L15 12H9L12 4.5Z", fill: "#ef4444" }),
1021
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 19.5L9 12H15L12 19.5Z", fill: "#f4f4f5" }),
1022
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", fill: "#18181b", r: "1.5" })
1023
+ ]
1024
+ }
1025
+ )
1026
+ }
1027
+ ) })),
1028
+ customRecenterButton ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pointer-events-auto", children: customRecenterButton }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1029
+ "button",
1030
+ {
1031
+ className: `w-12 h-12 rounded-full bg-zinc-950/80 backdrop-blur-md border border-white/10 shadow-lg flex items-center justify-center text-white active:scale-95 transition-all cursor-pointer ${isMapCentered ? "ring-2 ring-cyan-400 ring-offset-2 ring-offset-zinc-950" : "hover:bg-zinc-900"}`,
1032
+ onClick: () => onRecenter(),
1033
+ title: "Recenter Map",
1034
+ children: isMapCentered ? (
1035
+ /* Filled active lock state */
1036
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1037
+ "svg",
1038
+ {
1039
+ className: "w-6 h-6 text-cyan-400",
1040
+ fill: "none",
1041
+ stroke: "currentColor",
1042
+ strokeWidth: "2.2",
1043
+ viewBox: "0 0 24 24",
1044
+ children: [
1045
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "8" }),
1046
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", fill: "currentColor", r: "3" }),
1047
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", x2: "12", y1: "2", y2: "4" }),
1048
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", x2: "12", y1: "20", y2: "22" }),
1049
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "2", x2: "4", y1: "12", y2: "12" }),
1050
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "20", x2: "22", y1: "12", y2: "12" })
1051
+ ]
1052
+ }
1053
+ )
1054
+ ) : (
1055
+ /* Outlined drifted state */
1056
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1057
+ "svg",
1058
+ {
1059
+ className: "w-6 h-6 text-zinc-300",
1060
+ fill: "none",
1061
+ stroke: "currentColor",
1062
+ strokeWidth: "2",
1063
+ viewBox: "0 0 24 24",
1064
+ children: [
1065
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "8" }),
1066
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", x2: "12", y1: "2", y2: "4" }),
1067
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", x2: "12", y1: "20", y2: "22" }),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "2", x2: "4", y1: "12", y2: "12" }),
1069
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "20", x2: "22", y1: "12", y2: "12" })
1070
+ ]
1071
+ }
1072
+ )
1073
+ )
1074
+ }
1075
+ ) }),
1076
+ belowControls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex flex-col gap-2 pointer-events-auto", children: belowControls.map((control) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative group flex items-center", children: [
1077
+ control.tooltip && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute right-14 pointer-events-none opacity-0 scale-95 origin-right group-hover:opacity-100 group-hover:scale-100 transition-all duration-150 bg-zinc-900/95 border border-white/10 text-white text-xs px-2.5 py-1.5 rounded-lg shadow-xl whitespace-nowrap", children: control.tooltip }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1079
+ "button",
1080
+ {
1081
+ className: `w-12 h-12 rounded-full bg-zinc-950/80 backdrop-blur-md border border-white/10 shadow-lg flex items-center justify-center text-white active:scale-95 transition-all cursor-pointer ${control.activeState ? "ring-2 ring-cyan-400 ring-offset-2 ring-offset-zinc-950" : "hover:bg-zinc-900"}`,
1082
+ onClick: control.onPress,
1083
+ children: control.icon
1084
+ }
1085
+ )
1086
+ ] }, control.id)) })
1087
+ ]
1088
+ }
1089
+ );
1090
+ };
1091
+
1092
+ // src/components/Map/MapContext.tsx
1093
+ var import_react2 = require("react");
1094
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1095
+ var MapControlsContext = (0, import_react2.createContext)(
1096
+ null
1097
+ );
1098
+ var SDKMapProvider = ({
1099
+ children
1100
+ }) => {
1101
+ const [isFollowing, setIsFollowing] = (0, import_react2.useState)(true);
1102
+ const [bearing, setBearingState] = (0, import_react2.useState)(0);
1103
+ const [pitch, setPitchState] = (0, import_react2.useState)(0);
1104
+ const [drawerState, setDrawerStateInternal] = (0, import_react2.useState)("closed");
1105
+ const drawerContentRef = (0, import_react2.useRef)(null);
1106
+ const setDrawerState = (0, import_react2.useCallback)((val) => {
1107
+ setDrawerStateInternal(val);
1108
+ }, []);
1109
+ const [hasControls, setHasControlsState] = (0, import_react2.useState)(false);
1110
+ const setHasControls = (0, import_react2.useCallback)((val) => {
1111
+ setHasControlsState((prev) => prev === val ? prev : val);
1112
+ }, []);
1113
+ const [etaData, setEtaDataState] = (0, import_react2.useState)(void 0);
1114
+ const setEtaData = (0, import_react2.useCallback)((newData) => {
1115
+ setEtaDataState((prev) => {
1116
+ if (!prev && !newData) return prev;
1117
+ if (!prev || !newData) return newData;
1118
+ if (prev.etaMins === newData.etaMins && prev.distanceRemainingKm === newData.distanceRemainingKm && prev.label === newData.label && prev.progress === newData.progress && prev.mode === newData.mode && prev.isCalculating === newData.isCalculating && prev.error === newData.error) {
1119
+ return prev;
1120
+ }
1121
+ return newData;
1122
+ });
1123
+ }, []);
1124
+ const [insets, setInsetsState] = (0, import_react2.useState)({
1125
+ bottom: 0,
1126
+ left: 0,
1127
+ right: 0,
1128
+ top: 0
1129
+ });
1130
+ const setInsets = (0, import_react2.useCallback)((newInsets) => {
1131
+ setInsetsState((prev) => ({ ...prev, ...newInsets }));
1132
+ }, []);
1133
+ const centerCallbackRef = (0, import_react2.useRef)(null);
1134
+ const resetBearingCallbackRef = (0, import_react2.useRef)(null);
1135
+ const centerOnUser = (0, import_react2.useCallback)((params) => {
1136
+ if (centerCallbackRef.current) {
1137
+ centerCallbackRef.current(params);
1138
+ }
1139
+ }, []);
1140
+ const resetBearing = (0, import_react2.useCallback)((params) => {
1141
+ if (resetBearingCallbackRef.current) {
1142
+ resetBearingCallbackRef.current(params);
1143
+ }
1144
+ }, []);
1145
+ const _registerCenterCallback = (0, import_react2.useCallback)(
1146
+ (cb) => {
1147
+ centerCallbackRef.current = cb;
1148
+ },
1149
+ []
1150
+ );
1151
+ const _registerResetBearingCallback = (0, import_react2.useCallback)(
1152
+ (cb) => {
1153
+ resetBearingCallbackRef.current = cb;
1154
+ },
1155
+ []
1156
+ );
1157
+ const setBearing = (0, import_react2.useCallback)((b) => {
1158
+ setBearingState(b);
1159
+ }, []);
1160
+ const setPitch = (0, import_react2.useCallback)((p) => {
1161
+ setPitchState(p);
1162
+ }, []);
1163
+ const contextValue = (0, import_react2.useMemo)(
1164
+ () => ({
1165
+ _registerCenterCallback,
1166
+ _registerResetBearingCallback,
1167
+ bearing,
1168
+ centerOnUser,
1169
+ drawerContentRef,
1170
+ drawerState,
1171
+ etaData,
1172
+ hasControls,
1173
+ insets,
1174
+ isFollowing,
1175
+ pitch,
1176
+ resetBearing,
1177
+ setBearing,
1178
+ setDrawerState,
1179
+ setEtaData,
1180
+ setHasControls,
1181
+ setInsets,
1182
+ setIsFollowing,
1183
+ setPitch
1184
+ }),
1185
+ [
1186
+ _registerCenterCallback,
1187
+ _registerResetBearingCallback,
1188
+ bearing,
1189
+ centerOnUser,
1190
+ drawerState,
1191
+ etaData,
1192
+ hasControls,
1193
+ isFollowing,
1194
+ pitch,
1195
+ resetBearing,
1196
+ setBearing,
1197
+ setDrawerState,
1198
+ setEtaData,
1199
+ setHasControls,
1200
+ setPitch,
1201
+ insets,
1202
+ setInsets
1203
+ ]
1204
+ );
1205
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MapControlsContext.Provider, { value: contextValue, children });
1206
+ };
1207
+ var useMapControls = () => {
1208
+ const context = (0, import_react2.useContext)(MapControlsContext);
1209
+ if (!context) {
1210
+ throw new Error("useMapControls must be used within a MapControlsProvider");
1211
+ }
1212
+ return context;
1213
+ };
1214
+
1215
+ // src/components/Map/components/MapControls.tsx
1216
+ var import_react3 = require("react");
1217
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1218
+ var resolveHeightToPixels2 = (height, containerHeight) => {
1219
+ if (typeof height === "number") {
1220
+ return height;
1221
+ }
1222
+ if (height.endsWith("%")) {
1223
+ return parseFloat(height) / 100 * containerHeight;
1224
+ }
1225
+ return parseFloat(height);
1226
+ };
1227
+ var MapControls = ({
1228
+ menuItems = [],
1229
+ drawerSide,
1230
+ customControls = [],
1231
+ etaBarHeight = 100,
1232
+ onRecenter: propRecenter,
1233
+ isMapCentered: propMapCentered,
1234
+ mapBearing: propMapBearing,
1235
+ onResetBearing: propResetBearing,
1236
+ etaData,
1237
+ customRecenterButton,
1238
+ defaultState,
1239
+ initialViewState,
1240
+ customCompassButton,
1241
+ drawerPeekHeight,
1242
+ drawerFullHeight,
1243
+ drawerContentRef,
1244
+ menuContent,
1245
+ children,
1246
+ enableTouchDrag = false,
1247
+ showScrollToTopButton = true,
1248
+ scrollToTopThreshold = 300,
1249
+ isInternal = false
1250
+ }) => {
1251
+ const mapContext = (0, import_react3.useContext)(MapControlsContext);
1252
+ const contextRef = mapContext?.drawerContentRef;
1253
+ const mergedRef = (0, import_react3.useCallback)(
1254
+ (node) => {
1255
+ if (drawerContentRef) {
1256
+ if (typeof drawerContentRef === "function") {
1257
+ drawerContentRef(node);
1258
+ } else {
1259
+ drawerContentRef.current = node;
1260
+ }
1261
+ }
1262
+ if (contextRef) {
1263
+ contextRef.current = node;
1264
+ }
1265
+ },
1266
+ [drawerContentRef, contextRef]
1267
+ );
1268
+ const [localDrawerState, setLocalDrawerState] = (0, import_react3.useState)(
1269
+ defaultState ?? "closed"
1270
+ );
1271
+ const drawerState = mapContext ? mapContext.drawerState : localDrawerState;
1272
+ const setDrawerState = (0, import_react3.useCallback)(
1273
+ (state) => {
1274
+ if (mapContext) {
1275
+ mapContext.setDrawerState(state);
1276
+ } else {
1277
+ setLocalDrawerState(state);
1278
+ }
1279
+ },
1280
+ [mapContext]
1281
+ );
1282
+ (0, import_react3.useEffect)(() => {
1283
+ if (defaultState && mapContext) {
1284
+ mapContext.setDrawerState(defaultState);
1285
+ }
1286
+ }, []);
1287
+ const [side, setSide] = (0, import_react3.useState)("left");
1288
+ const containerRef = (0, import_react3.useRef)(null);
1289
+ const [mapHeight, setMapHeight] = (0, import_react3.useState)(600);
1290
+ (0, import_react3.useEffect)(() => {
1291
+ const parent = containerRef.current?.parentElement;
1292
+ if (!parent) return;
1293
+ const updateHeight = () => {
1294
+ setMapHeight(parent.getBoundingClientRect().height);
1295
+ };
1296
+ updateHeight();
1297
+ const observer = new ResizeObserver(updateHeight);
1298
+ observer.observe(parent);
1299
+ return () => {
1300
+ observer.disconnect();
1301
+ };
1302
+ }, []);
1303
+ const setHasControls = mapContext?.setHasControls;
1304
+ (0, import_react3.useEffect)(() => {
1305
+ if (setHasControls && !isInternal) {
1306
+ setHasControls(true);
1307
+ return () => setHasControls(false);
1308
+ }
1309
+ }, [setHasControls, isInternal]);
1310
+ const isMapCentered = propMapCentered !== void 0 ? propMapCentered : mapContext ? mapContext.isFollowing : true;
1311
+ const mapBearing = propMapBearing !== void 0 ? propMapBearing : mapContext ? mapContext.bearing : 0;
1312
+ const onRecenter = (params) => {
1313
+ if (propRecenter) {
1314
+ propRecenter(params);
1315
+ } else if (mapContext) {
1316
+ mapContext.centerOnUser(params);
1317
+ }
1318
+ };
1319
+ const onResetBearing = (params) => {
1320
+ if (propResetBearing) {
1321
+ propResetBearing(params);
1322
+ } else if (mapContext) {
1323
+ mapContext.resetBearing(params);
1324
+ }
1325
+ };
1326
+ const mapPitch = mapContext ? mapContext.pitch : 0;
1327
+ const resolvedEtaData = etaData !== void 0 ? etaData : mapContext?.etaData;
1328
+ const [touchStartY, setTouchStartY] = (0, import_react3.useState)(null);
1329
+ const [currentTouchY, setCurrentTouchY] = (0, import_react3.useState)(null);
1330
+ (0, import_react3.useEffect)(() => {
1331
+ if (typeof window === "undefined") return;
1332
+ const checkLayout = () => {
1333
+ if (drawerSide) {
1334
+ setSide(drawerSide);
1335
+ } else {
1336
+ if (window.innerWidth < 768) {
1337
+ setSide("bottom");
1338
+ } else {
1339
+ setSide("left");
1340
+ }
1341
+ }
1342
+ };
1343
+ checkLayout();
1344
+ window.addEventListener("resize", checkLayout);
1345
+ return () => window.removeEventListener("resize", checkLayout);
1346
+ }, [drawerSide]);
1347
+ const isDragging = touchStartY !== null && currentTouchY !== null;
1348
+ const dragOffset = isDragging ? currentTouchY - touchStartY : 0;
1349
+ const fullPx = resolveHeightToPixels2(drawerFullHeight || "85%", mapHeight);
1350
+ const peekPx = resolveHeightToPixels2(drawerPeekHeight || "15%", mapHeight);
1351
+ const bottomInset = mapContext?.insets.bottom || 0;
1352
+ const closedPx = 32 + (drawerState === "closed" ? bottomInset : 0);
1353
+ let clampedDragOffset = dragOffset;
1354
+ if (drawerState === "full") {
1355
+ clampedDragOffset = Math.max(0, Math.min(fullPx - closedPx, dragOffset));
1356
+ } else if (drawerState === "peek") {
1357
+ clampedDragOffset = Math.max(
1358
+ -(fullPx - peekPx),
1359
+ // up to full
1360
+ Math.min(peekPx - closedPx, dragOffset)
1361
+ // down to closed
1362
+ );
1363
+ } else if (drawerState === "closed") {
1364
+ clampedDragOffset = Math.min(0, Math.max(-(fullPx - closedPx), dragOffset));
1365
+ }
1366
+ let baseHeight = 32;
1367
+ if (drawerState === "peek") {
1368
+ baseHeight = peekPx;
1369
+ } else if (drawerState === "full") {
1370
+ baseHeight = fullPx;
1371
+ }
1372
+ const currentDrawerHeight = baseHeight - clampedDragOffset;
1373
+ const startFadeHeight = peekPx + 20;
1374
+ const endFadeHeight = peekPx + (fullPx - peekPx) * 0.35;
1375
+ let etaOpacity = 1;
1376
+ let etaBottomStyle = "1rem";
1377
+ if (side === "bottom") {
1378
+ if (currentDrawerHeight <= startFadeHeight) {
1379
+ etaOpacity = 1;
1380
+ } else if (currentDrawerHeight >= endFadeHeight) {
1381
+ etaOpacity = 0;
1382
+ } else {
1383
+ const fraction = (currentDrawerHeight - startFadeHeight) / (endFadeHeight - startFadeHeight);
1384
+ etaOpacity = 1 - fraction;
1385
+ }
1386
+ const effectiveInset = drawerState === "closed" ? bottomInset : 0;
1387
+ etaBottomStyle = `calc(${baseHeight + effectiveInset + 16}px - var(--drag-offset, 0px))`;
1388
+ } else {
1389
+ etaOpacity = drawerState === "full" ? 0 : 1;
1390
+ etaBottomStyle = `calc(1rem + ${bottomInset}px)`;
1391
+ }
1392
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1393
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: containerRef, style: { display: "none" } }),
1394
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1395
+ MapDrawer,
1396
+ {
1397
+ clampedDragOffset,
1398
+ containerHeight: mapHeight,
1399
+ currentTouchY,
1400
+ drawerFullHeight,
1401
+ drawerPeekHeight,
1402
+ enableTouchDrag,
1403
+ etaBarHeight,
1404
+ etaData: resolvedEtaData,
1405
+ isDragging,
1406
+ menuContent: children || menuContent,
1407
+ menuItems,
1408
+ onChangeState: setDrawerState,
1409
+ ref: mergedRef,
1410
+ safeAreaInsets: mapContext?.insets,
1411
+ scrollToTopThreshold,
1412
+ setCurrentTouchY,
1413
+ setTouchStartY,
1414
+ showScrollToTopButton,
1415
+ side,
1416
+ state: drawerState,
1417
+ touchStartY
1418
+ }
1419
+ ),
1420
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1421
+ MapRightControls,
1422
+ {
1423
+ clampedDragOffset,
1424
+ customCompassButton,
1425
+ customControls,
1426
+ customRecenterButton,
1427
+ drawerPeekHeight,
1428
+ drawerState,
1429
+ etaBarHeight: etaBarHeight + (drawerState === "closed" ? bottomInset : 0),
1430
+ etaData: resolvedEtaData,
1431
+ initialViewState,
1432
+ insets: mapContext?.insets,
1433
+ isDragging,
1434
+ isMapCentered,
1435
+ mapBearing,
1436
+ mapPitch,
1437
+ onRecenter: () => onRecenter(),
1438
+ onResetBearing,
1439
+ side
1440
+ }
1441
+ ),
1442
+ resolvedEtaData && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1443
+ MapEtaPanel,
1444
+ {
1445
+ etaData: resolvedEtaData,
1446
+ isDragging,
1447
+ layoutMode: "floating-card",
1448
+ style: {
1449
+ bottom: etaBottomStyle,
1450
+ opacity: etaOpacity,
1451
+ pointerEvents: etaOpacity > 0.1 ? "auto" : "none",
1452
+ transition: isDragging ? "none" : "bottom 250ms ease-in-out, opacity 300ms ease-out"
1453
+ }
1454
+ }
1455
+ )
1456
+ ] });
1457
+ };
1458
+
1459
+ // src/components/Map/components/MapMenu.tsx
1460
+ var import_react4 = require("react");
1461
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1462
+ var MapMenu = ({ children }) => {
1463
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex flex-col gap-1.5", children });
1464
+ };
1465
+ var MapMenuItem = ({
1466
+ id,
1467
+ label,
1468
+ icon,
1469
+ onPress,
1470
+ badge,
1471
+ disabled = false,
1472
+ children
1473
+ }) => {
1474
+ const context = (0, import_react4.useContext)(MapControlsContext);
1475
+ const handleClick = (e) => {
1476
+ if (disabled) return;
1477
+ if (onPress) onPress();
1478
+ if (context && context.setDrawerState) {
1479
+ context.setDrawerState("closed");
1480
+ }
1481
+ };
1482
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1483
+ "button",
1484
+ {
1485
+ className: `flex items-center gap-3 w-full py-3 px-3.5 rounded-xl text-left cursor-pointer transition-all duration-150 ${disabled ? "opacity-45 cursor-not-allowed pointer-events-none" : "hover:bg-white/5 active:bg-white/10 text-white"}`,
1486
+ disabled,
1487
+ onClick: handleClick,
1488
+ type: "button",
1489
+ children: [
1490
+ icon && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "relative flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 text-zinc-300", children: [
1491
+ icon,
1492
+ badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "absolute -top-1 -right-1 bg-cyan-500 text-black text-[9px] font-extrabold min-w-[15px] h-3.5 rounded-full flex items-center justify-center px-1 border border-zinc-950 shadow", children: badge })
1493
+ ] }),
1494
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex-1 flex flex-col justify-center", children: [
1495
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-sm font-medium tracking-wide flex-1", children: label }),
1496
+ children
1497
+ ] })
1498
+ ]
1499
+ }
1500
+ );
1501
+ };
1502
+ MapMenu.displayName = "MapMenu";
1503
+ MapMenuItem.displayName = "MapMenuItem";
1504
+ var MapMenuTitle = ({
1505
+ children,
1506
+ showDivider = true,
1507
+ className = ""
1508
+ }) => {
1509
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1510
+ "div",
1511
+ {
1512
+ className: `flex items-center justify-between pb-3 first:mt-0 px-3.5 ${className} ${showDivider ? "border-b border-white/10 mb-2 mt-4" : ""}`,
1513
+ children
1514
+ }
1515
+ );
1516
+ };
1517
+ MapMenuTitle.displayName = "MapMenuTitle";
1518
+ var MapMenuDivider = ({
1519
+ className = ""
1520
+ }) => {
1521
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: `h-[1px] bg-white/10 my-3 mx-3.5 ${className}` });
1522
+ };
1523
+ MapMenuDivider.displayName = "MapMenuDivider";
1524
+ var MapMenuFooter = ({
1525
+ children,
1526
+ className = ""
1527
+ }) => {
1528
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1529
+ "div",
1530
+ {
1531
+ className: `mt-auto pt-4 border-t border-white/10 px-3.5 text-xs text-zinc-500 font-medium ${className}`,
1532
+ children
1533
+ }
1534
+ );
1535
+ };
1536
+ MapMenuFooter.displayName = "MapMenuFooter";
1537
+
1538
+ // src/types/types.ts
1539
+ var UIEvent = /* @__PURE__ */ ((UIEvent2) => {
1540
+ UIEvent2["TOP_SCROLL_MORE"] = "TOP_SCROLL_MORE";
1541
+ UIEvent2["BOTTOM_SCROLL_MORE"] = "BOTTOM_SCROLL_MORE";
1542
+ UIEvent2["BACK_GESTURE"] = "BACK_GESTURE";
1543
+ return UIEvent2;
1544
+ })(UIEvent || {});
1545
+ var GeolocationEvent = /* @__PURE__ */ ((GeolocationEvent2) => {
1546
+ GeolocationEvent2["LOCATION_UPDATE"] = "LOCATION_UPDATE";
1547
+ GeolocationEvent2["LOCATION_STREAM_UPDATE"] = "LOCATION_STREAM_UPDATE";
1548
+ return GeolocationEvent2;
1549
+ })(GeolocationEvent || {});
1550
+ var GlobalEvent = /* @__PURE__ */ ((GlobalEvent2) => {
1551
+ GlobalEvent2["UPDATE_CONTEXT"] = "UPDATE_CONTEXT";
1552
+ return GlobalEvent2;
1553
+ })(GlobalEvent || {});
1554
+
1555
+ // src/components/Map/hooks/useUserLocation.ts
1556
+ var import_react5 = require("react");
1557
+ var LERP_FACTOR = 0.12;
1558
+ var useUserLocation = () => {
1559
+ const [locationState, setLocationState] = (0, import_react5.useState)({
1560
+ accuracy: null,
1561
+ coords: null,
1562
+ heading: null
1563
+ });
1564
+ const targetRef = (0, import_react5.useRef)({
1565
+ accuracy: null,
1566
+ coords: null,
1567
+ heading: null
1568
+ });
1569
+ const currentRef = (0, import_react5.useRef)({
1570
+ accuracy: null,
1571
+ coords: null,
1572
+ heading: null
1573
+ });
1574
+ const mapPayload = (0, import_react5.useCallback)((payload) => {
1575
+ const coords = payload.location?.coords || payload.coords;
1576
+ const headingData = payload.heading || payload.coords;
1577
+ const newCoords = coords ? [coords.longitude, coords.latitude] : null;
1578
+ let heading = null;
1579
+ if (headingData?.trueHeading !== void 0 && headingData.trueHeading >= 0) {
1580
+ heading = headingData.trueHeading;
1581
+ } else if (headingData?.magneticHeading !== void 0 && headingData.magneticHeading >= 0) {
1582
+ heading = headingData.magneticHeading;
1583
+ } else if (headingData?.heading !== null && headingData.heading >= 0) {
1584
+ heading = headingData.heading;
1585
+ }
1586
+ return {
1587
+ accuracy: coords?.accuracy || null,
1588
+ coords: newCoords,
1589
+ heading
1590
+ };
1591
+ }, []);
1592
+ (0, import_react5.useEffect)(() => {
1593
+ let rafId;
1594
+ const animate = () => {
1595
+ let changed = false;
1596
+ if (targetRef.current.coords) {
1597
+ if (!currentRef.current.coords) {
1598
+ currentRef.current.coords = [...targetRef.current.coords];
1599
+ changed = true;
1600
+ } else {
1601
+ const [tLng, tLat] = targetRef.current.coords;
1602
+ const [cLng, cLat] = currentRef.current.coords;
1603
+ const nLng = cLng + (tLng - cLng) * LERP_FACTOR;
1604
+ const nLat = cLat + (tLat - cLat) * LERP_FACTOR;
1605
+ if (Math.abs(nLng - cLng) > 1e-7 || Math.abs(nLat - cLat) > 1e-7) {
1606
+ currentRef.current.coords = [nLng, nLat];
1607
+ changed = true;
1608
+ }
1609
+ }
1610
+ }
1611
+ if (targetRef.current.heading !== null) {
1612
+ if (currentRef.current.heading === null) {
1613
+ currentRef.current.heading = targetRef.current.heading;
1614
+ changed = true;
1615
+ } else {
1616
+ const target = targetRef.current.heading;
1617
+ const current = currentRef.current.heading;
1618
+ let delta = target - current;
1619
+ if (delta > 180) delta -= 360;
1620
+ if (delta < -180) delta += 360;
1621
+ const nHeading = current + delta * LERP_FACTOR;
1622
+ const normalized = (nHeading % 360 + 360) % 360;
1623
+ if (Math.abs(delta) > 0.1) {
1624
+ currentRef.current.heading = normalized;
1625
+ changed = true;
1626
+ }
1627
+ }
1628
+ }
1629
+ if (changed) {
1630
+ setLocationState({
1631
+ accuracy: targetRef.current.accuracy,
1632
+ coords: currentRef.current.coords ? [...currentRef.current.coords] : null,
1633
+ heading: currentRef.current.heading
1634
+ });
1635
+ }
1636
+ rafId = requestAnimationFrame(animate);
1637
+ };
1638
+ rafId = requestAnimationFrame(animate);
1639
+ sdk.location.getCurrentPosition().then((data) => {
1640
+ const payload = mapPayload(data);
1641
+ targetRef.current = payload;
1642
+ }).catch((e) => console.warn("Initial location fetch failed", e));
1643
+ const unsubscribeStream = sdk.on(
1644
+ "LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */,
1645
+ (payload) => {
1646
+ targetRef.current = mapPayload(payload);
1647
+ }
1648
+ );
1649
+ const unsubscribeLegacy = sdk.on(
1650
+ "LOCATION_UPDATE" /* LOCATION_UPDATE */,
1651
+ (payload) => {
1652
+ targetRef.current = mapPayload(payload);
1653
+ }
1654
+ );
1655
+ sdk.location.startStream({
1656
+ distanceFilter: 1,
1657
+ headingThreshold: 2
1658
+ }).catch((e) => console.warn("Failed to start location stream", e));
1659
+ return () => {
1660
+ cancelAnimationFrame(rafId);
1661
+ unsubscribeStream();
1662
+ unsubscribeLegacy();
1663
+ sdk.location.stopStream().catch(() => {
1664
+ });
1665
+ };
1666
+ }, [mapPayload]);
1667
+ return locationState;
1668
+ };
1669
+
1670
+ // src/components/Map/hooks/useLiveRouting.ts
1671
+ var import_react6 = require("react");
1672
+ var useLiveRouting = ({
1673
+ origin: manualOrigin,
1674
+ destination,
1675
+ profile = "driving",
1676
+ recalculateThreshold = 50,
1677
+ enabled = true,
1678
+ initialMode = "preview"
1679
+ }) => {
1680
+ const [route, setRoute] = (0, import_react6.useState)(null);
1681
+ const [totalDistance, setTotalDistance] = (0, import_react6.useState)(null);
1682
+ const [isCalculating, setIsCalculating] = (0, import_react6.useState)(false);
1683
+ const [error, setError] = (0, import_react6.useState)(null);
1684
+ const [mode, setMode] = (0, import_react6.useState)(initialMode);
1685
+ const userLocationState = useUserLocation();
1686
+ const userLocation = manualOrigin || userLocationState.coords;
1687
+ const lastCalcOrigin = (0, import_react6.useRef)(null);
1688
+ const lastCalcDest = (0, import_react6.useRef)(null);
1689
+ const isRequestInProgress = (0, import_react6.useRef)(false);
1690
+ const getDistance = (0, import_react6.useCallback)(
1691
+ (p1, p2) => {
1692
+ const R = 6371e3;
1693
+ const \u03C61 = p1[1] * Math.PI / 180;
1694
+ const \u03C62 = p2[1] * Math.PI / 180;
1695
+ const \u0394\u03C6 = (p2[1] - p1[1]) * Math.PI / 180;
1696
+ const \u0394\u03BB = (p2[0] - p1[0]) * Math.PI / 180;
1697
+ const a = Math.sin(\u0394\u03C6 / 2) * Math.sin(\u0394\u03C6 / 2) + Math.cos(\u03C61) * Math.cos(\u03C62) * Math.sin(\u0394\u03BB / 2) * Math.sin(\u0394\u03BB / 2);
1698
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1699
+ return R * c;
1700
+ },
1701
+ []
1702
+ );
1703
+ const currentDistanceMeters = (0, import_react6.useMemo)(() => {
1704
+ if (!userLocation || !destination) return null;
1705
+ return getDistance(userLocation, destination);
1706
+ }, [userLocation, destination, getDistance]);
1707
+ const calculate = (0, import_react6.useCallback)(
1708
+ async (origin, dest, isNewDest) => {
1709
+ if (isRequestInProgress.current) return;
1710
+ isRequestInProgress.current = true;
1711
+ setIsCalculating(true);
1712
+ try {
1713
+ const data = await sdk.routing.getRoute({
1714
+ destination: dest,
1715
+ origin,
1716
+ profile
1717
+ });
1718
+ setRoute(data);
1719
+ setError(null);
1720
+ if (isNewDest) {
1721
+ setTotalDistance(data.distanceMeters);
1722
+ }
1723
+ lastCalcOrigin.current = origin;
1724
+ lastCalcDest.current = dest;
1725
+ } catch (err) {
1726
+ setError(err.message || "Failed to fetch route");
1727
+ } finally {
1728
+ setIsCalculating(false);
1729
+ isRequestInProgress.current = false;
1730
+ }
1731
+ },
1732
+ [profile]
1733
+ );
1734
+ (0, import_react6.useEffect)(() => {
1735
+ if (!enabled || !destination || !userLocation) {
1736
+ if (!enabled || !destination) {
1737
+ setRoute(null);
1738
+ setTotalDistance(null);
1739
+ lastCalcOrigin.current = null;
1740
+ lastCalcDest.current = null;
1741
+ }
1742
+ return;
1743
+ }
1744
+ const destChanged = !lastCalcDest.current || lastCalcDest.current[0] !== destination[0] || lastCalcDest.current[1] !== destination[1];
1745
+ if (destChanged) {
1746
+ calculate(userLocation, destination, true);
1747
+ } else if (lastCalcOrigin.current) {
1748
+ const dist = getDistance(userLocation, lastCalcOrigin.current);
1749
+ if (dist > recalculateThreshold) {
1750
+ calculate(userLocation, destination, false);
1751
+ }
1752
+ } else {
1753
+ calculate(userLocation, destination, true);
1754
+ }
1755
+ }, [
1756
+ userLocation,
1757
+ destination,
1758
+ enabled,
1759
+ recalculateThreshold,
1760
+ calculate,
1761
+ getDistance
1762
+ ]);
1763
+ return {
1764
+ currentDistanceMeters,
1765
+ error,
1766
+ isCalculating,
1767
+ mode,
1768
+ recalculate: () => userLocation && destination && calculate(userLocation, destination, false),
1769
+ route,
1770
+ start: () => setMode("navigation"),
1771
+ totalDistance
1772
+ };
1773
+ };
1774
+ var useRoute = (origin, destination, profile = "driving") => {
1775
+ const [route, setRoute] = (0, import_react6.useState)(null);
1776
+ const [isCalculating, setIsCalculating] = (0, import_react6.useState)(false);
1777
+ const [error, setError] = (0, import_react6.useState)(null);
1778
+ (0, import_react6.useEffect)(() => {
1779
+ if (!origin || !destination) {
1780
+ setRoute(null);
1781
+ return;
1782
+ }
1783
+ const fetchRoute = async () => {
1784
+ setIsCalculating(true);
1785
+ try {
1786
+ const data = await sdk.routing.getRoute({
1787
+ destination,
1788
+ origin,
1789
+ profile
1790
+ });
1791
+ setRoute(data);
1792
+ setError(null);
1793
+ } catch (err) {
1794
+ setError(err.message || "Failed to fetch route");
1795
+ } finally {
1796
+ setIsCalculating(false);
1797
+ }
1798
+ };
1799
+ fetchRoute();
1800
+ }, [origin, destination, profile]);
1801
+ return { error, isCalculating, route };
1802
+ };
1803
+
1804
+ // src/components/Map/hooks/useTrimmedRoute.ts
1805
+ var import_react7 = require("react");
1806
+ var useTrimmedRoute = (route, userLocation) => {
1807
+ return (0, import_react7.useMemo)(() => {
1808
+ if (!route || !userLocation || route.coordinates.length < 2) return route;
1809
+ const coords = route.coordinates;
1810
+ let minDistance = Infinity;
1811
+ let closestIndex = 0;
1812
+ const getDistSq = (p1, p2) => {
1813
+ return (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2;
1814
+ };
1815
+ for (let i = 0; i < coords.length; i++) {
1816
+ const dist = getDistSq(userLocation, coords[i]);
1817
+ if (dist < minDistance) {
1818
+ minDistance = dist;
1819
+ closestIndex = i;
1820
+ }
1821
+ }
1822
+ const remainingCoords = coords.slice(closestIndex + 1);
1823
+ return {
1824
+ coordinates: [userLocation, ...remainingCoords],
1825
+ type: "LineString"
1826
+ };
1827
+ }, [route, userLocation]);
1828
+ };
1829
+
1830
+ // src/components/Map/layers/RouteLayer.tsx
1831
+ var import_react_maplibre = require("@vis.gl/react-maplibre");
1832
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1833
+ var RouteLayer = ({
1834
+ geometry,
1835
+ color = "#3b82f6",
1836
+ width = 5,
1837
+ opacity = 0.8
1838
+ }) => {
1839
+ if (!geometry) return null;
1840
+ const data = {
1841
+ geometry,
1842
+ properties: {},
1843
+ type: "Feature"
1844
+ };
1845
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_maplibre.Source, { data, id: "sdk-route-source", type: "geojson", children: [
1846
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1847
+ import_react_maplibre.Layer,
1848
+ {
1849
+ id: "sdk-route-layer-casing",
1850
+ paint: {
1851
+ "line-blur": 1,
1852
+ "line-color": "#ffffff",
1853
+ "line-opacity": opacity,
1854
+ "line-width": width + 2
1855
+ },
1856
+ type: "line"
1857
+ }
1858
+ ),
1859
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1860
+ import_react_maplibre.Layer,
1861
+ {
1862
+ id: "sdk-route-layer",
1863
+ layout: {
1864
+ "line-cap": "round",
1865
+ "line-join": "round"
1866
+ },
1867
+ paint: {
1868
+ "line-color": color,
1869
+ "line-opacity": opacity,
1870
+ "line-width": width
1871
+ },
1872
+ type: "line"
1873
+ }
1874
+ )
1875
+ ] });
1876
+ };
1877
+
1878
+ // src/components/Map/Map.tsx
1879
+ var import_react_maplibre5 = require("@vis.gl/react-maplibre");
1880
+ var import_react9 = __toESM(require("react"), 1);
1881
+ var import_maplibre_gl = require("maplibre-gl/dist/maplibre-gl.css");
1882
+
1883
+ // src/components/Map/layers/POILayers.tsx
1884
+ var import_react_maplibre2 = require("@vis.gl/react-maplibre");
1885
+ var import_react8 = require("react");
1886
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1887
+ function createEmojiIcon(emoji, size = 64) {
1888
+ const canvas = document.createElement("canvas");
1889
+ canvas.width = size;
1890
+ canvas.height = size;
1891
+ const ctx = canvas.getContext("2d");
1892
+ ctx.font = `${size * 0.75}px serif`;
1893
+ ctx.textAlign = "center";
1894
+ ctx.textBaseline = "middle";
1895
+ ctx.fillText(emoji, size / 2, size / 2);
1896
+ return ctx.getImageData(0, 0, size, size);
1897
+ }
1898
+ var POILayers = ({ pois }) => {
1899
+ const [loadedImages, setLoadedImages] = (0, import_react8.useState)(0);
1900
+ const { current: map } = (0, import_react_maplibre2.useMap)();
1901
+ const glPois = (0, import_react8.useMemo)(
1902
+ () => pois.filter((p) => !p.displayMode || p.displayMode === "gl"),
1903
+ [pois]
1904
+ );
1905
+ (0, import_react8.useEffect)(() => {
1906
+ if (!map) return;
1907
+ const glMap = map.getMap();
1908
+ const defaults = [
1909
+ { emoji: "\u{1F37A}", name: "sdk-default-bar" },
1910
+ { emoji: "\u{1F37D}\uFE0F", name: "sdk-default-restaurant" },
1911
+ { emoji: "\u{1F4CD}", name: "sdk-default-poi" }
1912
+ ];
1913
+ defaults.forEach((def) => {
1914
+ if (!glMap.hasImage(def.name)) {
1915
+ glMap.addImage(def.name, createEmojiIcon(def.emoji), { pixelRatio: 2 });
1916
+ }
1917
+ });
1918
+ let updates = 0;
1919
+ glPois.forEach((poi) => {
1920
+ if (poi.imageUrl) {
1921
+ const name = `custom-url-${poi.id}`;
1922
+ if (!glMap.hasImage(name)) {
1923
+ glMap.loadImage(poi.imageUrl, (error, image) => {
1924
+ if (!error && image) {
1925
+ if (!glMap.hasImage(name)) {
1926
+ glMap.addImage(name, image);
1927
+ setLoadedImages((prev) => prev + 1);
1928
+ }
1929
+ }
1930
+ });
1931
+ }
1932
+ }
1933
+ if (poi.emoji) {
1934
+ const name = `custom-emoji-${poi.emoji}`;
1935
+ if (!glMap.hasImage(name)) {
1936
+ glMap.addImage(name, createEmojiIcon(poi.emoji), { pixelRatio: 2 });
1937
+ updates++;
1938
+ }
1939
+ }
1940
+ });
1941
+ if (updates > 0) {
1942
+ setLoadedImages((prev) => prev + updates);
1943
+ }
1944
+ }, [map, glPois]);
1945
+ const geojson = (0, import_react8.useMemo)(
1946
+ () => ({
1947
+ features: glPois.map((poi) => ({
1948
+ geometry: {
1949
+ coordinates: [poi.longitude, poi.latitude],
1950
+ type: "Point"
1951
+ },
1952
+ properties: {
1953
+ ...poi,
1954
+ // Selection logic for the icon in GL
1955
+ "sdk-icon": poi.imageUrl ? `custom-url-${poi.id}` : poi.spriteName ? poi.spriteName : poi.emoji ? `custom-emoji-${poi.emoji}` : poi.type === "bar" ? "sdk-default-bar" : poi.type === "restaurant" ? "sdk-default-restaurant" : "sdk-default-poi"
1956
+ },
1957
+ type: "Feature"
1958
+ })),
1959
+ type: "FeatureCollection"
1960
+ }),
1961
+ [glPois]
1962
+ );
1963
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_maplibre2.Source, { data: geojson, id: "sdk-pois", type: "geojson", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1964
+ import_react_maplibre2.Layer,
1965
+ {
1966
+ id: "sdk-poi-layer",
1967
+ layout: {
1968
+ "icon-allow-overlap": true,
1969
+ "icon-image": ["get", "sdk-icon"],
1970
+ "icon-size": 1,
1971
+ "text-anchor": "top",
1972
+ "text-field": [
1973
+ "step",
1974
+ ["zoom"],
1975
+ "",
1976
+ // Show nothing when zoomed out
1977
+ 14,
1978
+ ["get", "name"]
1979
+ // Show name when zoomed in
1980
+ ],
1981
+ "text-font": ["Open Sans Bold"],
1982
+ "text-offset": [0, 1.5],
1983
+ "text-optional": true,
1984
+ "text-size": 11
1985
+ },
1986
+ paint: {
1987
+ "icon-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 12, 1],
1988
+ "text-color": "#ffffff",
1989
+ "text-halo-color": "#000000",
1990
+ "text-halo-width": 1,
1991
+ "text-opacity": ["interpolate", ["linear"], ["zoom"], 13.5, 0, 14, 1]
1992
+ },
1993
+ type: "symbol"
1994
+ }
1995
+ ) });
1996
+ };
1997
+
1998
+ // src/components/Map/layers/POIMarkers.tsx
1999
+ var import_react_maplibre3 = require("@vis.gl/react-maplibre");
2000
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2001
+ var POIMarkers = ({ pois, onClick }) => {
2002
+ const markerPois = pois.filter((p) => p.displayMode === "marker");
2003
+ if (markerPois.length === 0) return null;
2004
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: markerPois.map((poi) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2005
+ import_react_maplibre3.Marker,
2006
+ {
2007
+ anchor: "bottom",
2008
+ latitude: poi.latitude,
2009
+ longitude: poi.longitude,
2010
+ onClick: (e) => {
2011
+ e.originalEvent.stopPropagation();
2012
+ if (onClick) onClick(poi);
2013
+ },
2014
+ children: poi.markerComponent ? poi.markerComponent : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2015
+ "div",
2016
+ {
2017
+ style: {
2018
+ cursor: "pointer",
2019
+ filter: "drop-shadow(0 4px 6px rgba(0,0,0,0.1))",
2020
+ transform: "transition-transform duration-200 hover:scale-110"
2021
+ },
2022
+ children: [
2023
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2024
+ "div",
2025
+ {
2026
+ style: {
2027
+ alignItems: "center",
2028
+ backgroundColor: poi.type === "bar" ? "#f97316" : "#10b981",
2029
+ border: "2px solid white",
2030
+ borderRadius: "50%",
2031
+ display: "flex",
2032
+ height: "32px",
2033
+ justifyContent: "center",
2034
+ width: "32px"
2035
+ },
2036
+ children: poi.emoji ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: "16px" }, children: poi.emoji }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2037
+ "div",
2038
+ {
2039
+ style: {
2040
+ backgroundColor: "white",
2041
+ borderRadius: "50%",
2042
+ height: "8px",
2043
+ width: "8px"
2044
+ }
2045
+ }
2046
+ )
2047
+ }
2048
+ ),
2049
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2050
+ "div",
2051
+ {
2052
+ style: {
2053
+ borderLeft: "6px solid transparent",
2054
+ borderRight: "6px solid transparent",
2055
+ borderTop: `8px solid ${poi.type === "bar" ? "#f97316" : "#10b981"}`,
2056
+ height: 0,
2057
+ margin: "-2px auto 0",
2058
+ width: 0
2059
+ }
2060
+ }
2061
+ )
2062
+ ]
2063
+ }
2064
+ )
2065
+ },
2066
+ poi.id
2067
+ )) });
2068
+ };
2069
+
2070
+ // src/components/Map/layers/UserLocationLayer.tsx
2071
+ var import_react_maplibre4 = require("@vis.gl/react-maplibre");
2072
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2073
+ var UserLocationLayer = ({
2074
+ forcedLocation,
2075
+ heading
2076
+ }) => {
2077
+ if (!forcedLocation) return null;
2078
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2079
+ import_react_maplibre4.Marker,
2080
+ {
2081
+ anchor: "center",
2082
+ latitude: forcedLocation[1],
2083
+ longitude: forcedLocation[0],
2084
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2085
+ "div",
2086
+ {
2087
+ style: {
2088
+ alignItems: "center",
2089
+ display: "flex",
2090
+ justifyContent: "center",
2091
+ position: "relative"
2092
+ },
2093
+ children: [
2094
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2095
+ "div",
2096
+ {
2097
+ style: {
2098
+ animation: "pulse 2s infinite",
2099
+ backgroundColor: "rgba(59, 130, 246, 0.4)",
2100
+ borderRadius: "50%",
2101
+ height: "40px",
2102
+ position: "absolute",
2103
+ width: "40px"
2104
+ }
2105
+ }
2106
+ ),
2107
+ heading !== void 0 && heading !== null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2108
+ "div",
2109
+ {
2110
+ style: {
2111
+ height: "60px",
2112
+ position: "absolute",
2113
+ transform: `rotate(${heading}deg)`,
2114
+ width: "60px",
2115
+ zIndex: 0
2116
+ },
2117
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2118
+ "svg",
2119
+ {
2120
+ style: { height: "100%", width: "100%" },
2121
+ viewBox: "0 0 100 100",
2122
+ children: [
2123
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2124
+ "radialGradient",
2125
+ {
2126
+ cx: "50%",
2127
+ cy: "50%",
2128
+ fx: "50%",
2129
+ fy: "50%",
2130
+ id: "grad1",
2131
+ r: "50%",
2132
+ children: [
2133
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2134
+ "stop",
2135
+ {
2136
+ offset: "0%",
2137
+ style: {
2138
+ stopColor: "rgba(37, 99, 235, 0.6)",
2139
+ stopOpacity: 1
2140
+ }
2141
+ }
2142
+ ),
2143
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2144
+ "stop",
2145
+ {
2146
+ offset: "100%",
2147
+ style: {
2148
+ stopColor: "rgba(37, 99, 235, 0)",
2149
+ stopOpacity: 0
2150
+ }
2151
+ }
2152
+ )
2153
+ ]
2154
+ }
2155
+ ) }),
2156
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2157
+ "path",
2158
+ {
2159
+ d: "M 50 50 L 35 0 A 50 50 0 0 1 65 0 Z",
2160
+ fill: "url(#grad1)"
2161
+ }
2162
+ )
2163
+ ]
2164
+ }
2165
+ )
2166
+ }
2167
+ ),
2168
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2169
+ "div",
2170
+ {
2171
+ style: {
2172
+ alignItems: "center",
2173
+ backgroundColor: "white",
2174
+ borderRadius: "50%",
2175
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(37, 99, 235, 0.2)",
2176
+ display: "flex",
2177
+ height: "20px",
2178
+ justifyContent: "center",
2179
+ width: "20px",
2180
+ zIndex: 1
2181
+ },
2182
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2183
+ "div",
2184
+ {
2185
+ style: {
2186
+ backgroundColor: "#2563eb",
2187
+ border: "2px solid white",
2188
+ borderRadius: "50%",
2189
+ height: "12px",
2190
+ width: "12px"
2191
+ }
2192
+ }
2193
+ )
2194
+ }
2195
+ ),
2196
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: `@keyframes pulse { 0% { transform: scale(0.95); opacity: 0.7; } 70% { transform: scale(1.5); opacity: 0; } 100% { transform: scale(0.95); opacity: 0; } }` })
2197
+ ]
2198
+ }
2199
+ )
2200
+ }
2201
+ );
2202
+ };
2203
+
2204
+ // src/components/Map/Map.tsx
2205
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2206
+ var MapInternal = ({
2207
+ initialViewState = {
2208
+ latitude: 45.90583599233422,
2209
+ longitude: 6.12589018919138,
2210
+ zoom: 12
2211
+ },
2212
+ style = {},
2213
+ mapStyle = "https://tiles.cloudparse.fr/style/up_dark_matter",
2214
+ className = "",
2215
+ maxBounds,
2216
+ children,
2217
+ onClick,
2218
+ onMapClickMiss,
2219
+ onContextMenu,
2220
+ interactiveLayerIds = [],
2221
+ onLoad,
2222
+ showUserLocation = true,
2223
+ followUser: initialFollowUser = false,
2224
+ showUserLocationButton = true,
2225
+ renderUserLocation,
2226
+ pois = [],
2227
+ onPoiClick,
2228
+ route: externalRoute,
2229
+ showNavigationUI: externalShowNavigationUI,
2230
+ navigationData: externalNavigationData,
2231
+ onNavigationAbort,
2232
+ onNavigationModeChange,
2233
+ navigation,
2234
+ showControls = true,
2235
+ menuItems,
2236
+ menuContent,
2237
+ drawerState: propDrawerState,
2238
+ drawerContentRef,
2239
+ controlProps = {},
2240
+ enableTouchDrag = false
2241
+ }) => {
2242
+ const mapControls = (0, import_react9.useContext)(MapControlsContext);
2243
+ const mapRef = (0, import_react9.useRef)(null);
2244
+ const userLocationState = useUserLocation();
2245
+ const [progNav, setProgNav] = (0, import_react9.useState)(null);
2246
+ (0, import_react9.useEffect)(() => {
2247
+ const unsubCreate = sdk.on("SDK_NAVIGATION_CREATE", (payload) => {
2248
+ setProgNav(payload);
2249
+ });
2250
+ const unsubStart = sdk.on("SDK_NAVIGATION_START", () => {
2251
+ setProgNav((prev) => prev ? { ...prev, mode: "navigation" } : null);
2252
+ });
2253
+ const unsubAbort = sdk.on("SDK_NAVIGATION_ABORT", () => {
2254
+ setProgNav(null);
2255
+ });
2256
+ return () => {
2257
+ unsubCreate();
2258
+ unsubStart();
2259
+ unsubAbort();
2260
+ };
2261
+ }, []);
2262
+ const effectiveNavOptions = progNav || navigation;
2263
+ const internalNav = useLiveRouting({
2264
+ destination: effectiveNavOptions?.destination || null,
2265
+ enabled: !!effectiveNavOptions?.destination,
2266
+ initialMode: effectiveNavOptions?.mode || "preview",
2267
+ origin: effectiveNavOptions?.origin,
2268
+ profile: effectiveNavOptions?.profile || "driving",
2269
+ recalculateThreshold: effectiveNavOptions?.recalculateThreshold || 50
2270
+ });
2271
+ (0, import_react9.useEffect)(() => {
2272
+ if (onNavigationModeChange && navigation) {
2273
+ onNavigationModeChange(internalNav.mode);
2274
+ }
2275
+ }, [internalNav.mode, onNavigationModeChange, navigation]);
2276
+ const navigationData = effectiveNavOptions ? internalNav : externalNavigationData;
2277
+ const route = effectiveNavOptions ? internalNav.route?.geometry : externalRoute;
2278
+ const showNavigationUI = effectiveNavOptions?.showUI !== false && (effectiveNavOptions ? !!effectiveNavOptions.destination : externalShowNavigationUI);
2279
+ const mode = navigationData?.mode || "preview";
2280
+ const resolvedInitialFollow = mapControls ? mapControls.isFollowing : initialFollowUser;
2281
+ const isFollowingRef = (0, import_react9.useRef)(resolvedInitialFollow);
2282
+ const isFlyingRef = (0, import_react9.useRef)(false);
2283
+ const [localIsFollowingUI, setLocalIsFollowingUI] = (0, import_react9.useState)(
2284
+ resolvedInitialFollow
2285
+ );
2286
+ const isFollowingUI = mapControls ? mapControls.isFollowing : localIsFollowingUI;
2287
+ const setIsFollowingCtx = mapControls?.setIsFollowing;
2288
+ const setFollowMode = (0, import_react9.useCallback)(
2289
+ (follow) => {
2290
+ isFollowingRef.current = follow;
2291
+ setLocalIsFollowingUI(follow);
2292
+ if (setIsFollowingCtx) {
2293
+ setIsFollowingCtx(follow);
2294
+ }
2295
+ },
2296
+ [setIsFollowingCtx]
2297
+ );
2298
+ (0, import_react9.useEffect)(() => {
2299
+ if (mapControls && mapControls.isFollowing !== isFollowingRef.current) {
2300
+ isFollowingRef.current = mapControls.isFollowing;
2301
+ }
2302
+ }, [mapControls?.isFollowing, mapControls]);
2303
+ const mergedInteractiveLayerIds = [...interactiveLayerIds];
2304
+ if (pois.length > 0 && !mergedInteractiveLayerIds.includes("sdk-poi-layer")) {
2305
+ mergedInteractiveLayerIds.push("sdk-poi-layer");
2306
+ }
2307
+ const prevInitialFollow = (0, import_react9.useRef)(initialFollowUser);
2308
+ (0, import_react9.useEffect)(() => {
2309
+ if (initialFollowUser !== prevInitialFollow.current) {
2310
+ setFollowMode(initialFollowUser);
2311
+ prevInitialFollow.current = initialFollowUser;
2312
+ if (initialFollowUser && userLocationState.coords && mapRef.current) {
2313
+ isFlyingRef.current = true;
2314
+ mapRef.current.flyTo({
2315
+ center: userLocationState.coords,
2316
+ duration: 1e3,
2317
+ essential: true,
2318
+ zoom: Math.max(mapRef.current.getZoom(), 16)
2319
+ });
2320
+ setTimeout(() => {
2321
+ isFlyingRef.current = false;
2322
+ }, 1050);
2323
+ }
2324
+ }
2325
+ }, [initialFollowUser, setFollowMode, userLocationState.coords]);
2326
+ const lastAutoLockedRouteRef = (0, import_react9.useRef)(null);
2327
+ (0, import_react9.useEffect)(() => {
2328
+ if (!route || !mapRef.current) {
2329
+ if (!route) lastAutoLockedRouteRef.current = null;
2330
+ return;
2331
+ }
2332
+ const isNewRoute = route !== lastAutoLockedRouteRef.current;
2333
+ if (!isNewRoute && mode === "navigation") return;
2334
+ if (mode === "navigation") {
2335
+ setFollowMode(true);
2336
+ } else if (mode === "preview" && isNewRoute) {
2337
+ setFollowMode(false);
2338
+ const coords = route.coordinates;
2339
+ if (coords.length > 0) {
2340
+ const bounds = coords.reduce(
2341
+ (acc, coord) => {
2342
+ return [
2343
+ [Math.min(acc[0][0], coord[0]), Math.min(acc[0][1], coord[1])],
2344
+ [Math.max(acc[1][0], coord[0]), Math.max(acc[1][1], coord[1])]
2345
+ ];
2346
+ },
2347
+ [
2348
+ [coords[0][0], coords[0][1]],
2349
+ [coords[0][0], coords[0][1]]
2350
+ ]
2351
+ );
2352
+ mapRef.current.fitBounds(
2353
+ [bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]],
2354
+ { padding: 100, speed: 1.2 }
2355
+ );
2356
+ }
2357
+ }
2358
+ lastAutoLockedRouteRef.current = route;
2359
+ }, [route, mode, setFollowMode]);
2360
+ (0, import_react9.useEffect)(() => {
2361
+ if (isFollowingRef.current && !isFlyingRef.current && userLocationState.coords && mapRef.current) {
2362
+ mapRef.current.easeTo({
2363
+ center: userLocationState.coords,
2364
+ duration: 50,
2365
+ // Ultra-short for high-frequency interpolated stream
2366
+ easing: (t) => t,
2367
+ essential: true
2368
+ });
2369
+ }
2370
+ }, [userLocationState.coords]);
2371
+ const displayRoute = useTrimmedRoute(
2372
+ route,
2373
+ showUserLocation ? userLocationState.coords : null
2374
+ );
2375
+ const remainingMeters = navigationData?.currentDistanceMeters ?? navigationData?.route?.distanceMeters ?? 0;
2376
+ const etaMins = navigationData?.route?.durationSeconds ? Math.ceil(navigationData.route.durationSeconds / 60) : 0;
2377
+ const kmRemaining = remainingMeters / 1e3;
2378
+ const totalDist = navigationData?.totalDistance ?? 0;
2379
+ const progressValue = totalDist > 0 ? (totalDist - remainingMeters) / totalDist * 100 : 0;
2380
+ const showEtaData = (0, import_react9.useMemo)(() => {
2381
+ if (!navigationData || !route) return void 0;
2382
+ return {
2383
+ distanceRemainingKm: kmRemaining,
2384
+ etaMins,
2385
+ label: mode === "navigation" ? "Walking Guidance Active" : "Route Preview",
2386
+ mode,
2387
+ onAbort: onNavigationAbort || (() => {
2388
+ setProgNav(null);
2389
+ }),
2390
+ onStart: navigationData.start,
2391
+ progress: progressValue
2392
+ };
2393
+ }, [
2394
+ navigationData,
2395
+ route,
2396
+ etaMins,
2397
+ kmRemaining,
2398
+ mode,
2399
+ progressValue,
2400
+ onNavigationAbort
2401
+ ]);
2402
+ const setEtaDataCtx = mapControls?.setEtaData;
2403
+ (0, import_react9.useEffect)(() => {
2404
+ if (setEtaDataCtx) {
2405
+ setEtaDataCtx(showEtaData);
2406
+ }
2407
+ }, [showEtaData, setEtaDataCtx]);
2408
+ const setDrawerStateCtx = mapControls?.setDrawerState;
2409
+ (0, import_react9.useEffect)(() => {
2410
+ if (setDrawerStateCtx && propDrawerState !== void 0) {
2411
+ setDrawerStateCtx(propDrawerState);
2412
+ }
2413
+ }, [propDrawerState, setDrawerStateCtx]);
2414
+ const defaultMenuItems = (0, import_react9.useMemo)(
2415
+ () => [
2416
+ {
2417
+ icon: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2418
+ "svg",
2419
+ {
2420
+ className: "w-5 h-5",
2421
+ fill: "none",
2422
+ stroke: "currentColor",
2423
+ strokeWidth: "2",
2424
+ viewBox: "0 0 24 24",
2425
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2426
+ "path",
2427
+ {
2428
+ d: "M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.39-2.823 1.07-4",
2429
+ strokeLinecap: "round",
2430
+ strokeLinejoin: "round"
2431
+ }
2432
+ )
2433
+ }
2434
+ ),
2435
+ id: "walking",
2436
+ label: "Walking Mode",
2437
+ onPress: () => {
2438
+ }
2439
+ }
2440
+ ],
2441
+ []
2442
+ );
2443
+ const resolvedMenuItems = menuItems || defaultMenuItems;
2444
+ const { mapLibreChildren, menuContentFromChildren } = (0, import_react9.useMemo)(() => {
2445
+ const mapLibreChildren2 = [];
2446
+ const extractedMenuItems = [];
2447
+ import_react9.default.Children.forEach(children, (child) => {
2448
+ if (import_react9.default.isValidElement(child)) {
2449
+ const type = child.type;
2450
+ const displayName = type?.displayName || type?.name;
2451
+ const isMenu = type === MapMenu || displayName === "MapMenu";
2452
+ const isMenuComponent = isMenu || type === MapMenuItem || type === MapMenuTitle || type === MapMenuDivider || type === MapMenuFooter || [
2453
+ "MapMenuItem",
2454
+ "MapMenuTitle",
2455
+ "MapMenuDivider",
2456
+ "MapMenuFooter"
2457
+ ].includes(displayName);
2458
+ if (isMenu) {
2459
+ import_react9.default.Children.forEach(
2460
+ child.props.children,
2461
+ (nestedChild) => {
2462
+ if (nestedChild) {
2463
+ extractedMenuItems.push(nestedChild);
2464
+ }
2465
+ }
2466
+ );
2467
+ } else if (isMenuComponent) {
2468
+ extractedMenuItems.push(child);
2469
+ } else {
2470
+ mapLibreChildren2.push(child);
2471
+ }
2472
+ } else {
2473
+ mapLibreChildren2.push(child);
2474
+ }
2475
+ });
2476
+ const menuContentFromChildren2 = extractedMenuItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MapMenu, { children: extractedMenuItems }) : null;
2477
+ return { mapLibreChildren: mapLibreChildren2, menuContentFromChildren: menuContentFromChildren2 };
2478
+ }, [children]);
2479
+ const handleMapClick = (e) => {
2480
+ const clickedFeatures = e.features || [];
2481
+ if (onPoiClick) {
2482
+ const poiFeature = clickedFeatures.find(
2483
+ (f) => f.layer.id === "sdk-poi-layer"
2484
+ );
2485
+ if (poiFeature) {
2486
+ onPoiClick(poiFeature.properties);
2487
+ return;
2488
+ }
2489
+ }
2490
+ const hitInteractive = clickedFeatures.some(
2491
+ (f) => mergedInteractiveLayerIds.includes(f.layer.id)
2492
+ );
2493
+ if (!hitInteractive) {
2494
+ if (onMapClickMiss) {
2495
+ onMapClickMiss(e);
2496
+ }
2497
+ }
2498
+ if (onClick) onClick(e);
2499
+ };
2500
+ const handleCenterOnUser = (0, import_react9.useCallback)(
2501
+ (payload) => {
2502
+ if (userLocationState.coords && mapRef.current) {
2503
+ setFollowMode(true);
2504
+ isFlyingRef.current = true;
2505
+ mapRef.current.flyTo({
2506
+ center: userLocationState.coords,
2507
+ duration: 1e3,
2508
+ essential: true,
2509
+ pitch: payload?.pitch || 0,
2510
+ zoom: Math.max(mapRef.current.getZoom(), 16)
2511
+ });
2512
+ setTimeout(() => {
2513
+ isFlyingRef.current = false;
2514
+ }, 1050);
2515
+ }
2516
+ },
2517
+ [userLocationState.coords, setFollowMode]
2518
+ );
2519
+ const handleResetBearing = (0, import_react9.useCallback)((params) => {
2520
+ if (mapRef.current) {
2521
+ mapRef.current.easeTo({
2522
+ bearing: 0,
2523
+ duration: 500,
2524
+ essential: true,
2525
+ pitch: params?.pitch ?? 0
2526
+ });
2527
+ }
2528
+ }, []);
2529
+ (0, import_react9.useEffect)(() => {
2530
+ if (mapControls) {
2531
+ mapControls._registerCenterCallback(handleCenterOnUser);
2532
+ mapControls._registerResetBearingCallback(handleResetBearing);
2533
+ }
2534
+ }, [mapControls, handleCenterOnUser, handleResetBearing]);
2535
+ const handleMove = (0, import_react9.useCallback)(() => {
2536
+ if (mapRef.current && mapControls) {
2537
+ mapControls.setBearing(mapRef.current.getBearing());
2538
+ mapControls.setPitch(mapRef.current.getPitch());
2539
+ }
2540
+ }, [mapControls]);
2541
+ const handleUserInteraction = () => {
2542
+ if (isFollowingRef.current) {
2543
+ console.log("[SDK:Map] Manual interaction detected. Halting tracking.");
2544
+ setFollowMode(false);
2545
+ if (mapRef.current) {
2546
+ mapRef.current.stop();
2547
+ }
2548
+ }
2549
+ };
2550
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2551
+ "div",
2552
+ {
2553
+ className: `w-full h-full overflow-hidden relative ${className}`,
2554
+ style,
2555
+ children: [
2556
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2557
+ import_react_maplibre5.Map,
2558
+ {
2559
+ attributionControl: false,
2560
+ initialViewState,
2561
+ interactiveLayerIds: mergedInteractiveLayerIds,
2562
+ mapStyle,
2563
+ maxBounds,
2564
+ onClick: handleMapClick,
2565
+ onContextMenu,
2566
+ onDragStart: handleUserInteraction,
2567
+ onLoad: (e) => {
2568
+ if (onLoad) onLoad(e);
2569
+ if (initialFollowUser && userLocationState.coords && mapRef.current) {
2570
+ mapRef.current.jumpTo({
2571
+ center: userLocationState.coords,
2572
+ zoom: 16
2573
+ });
2574
+ }
2575
+ },
2576
+ onMouseDown: handleUserInteraction,
2577
+ onMove: handleMove,
2578
+ onTouchStart: handleUserInteraction,
2579
+ onWheel: handleUserInteraction,
2580
+ ref: mapRef,
2581
+ children: [
2582
+ showUserLocation && userLocationState.coords && (renderUserLocation ? renderUserLocation({
2583
+ coords: userLocationState.coords,
2584
+ heading: userLocationState.heading
2585
+ }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2586
+ UserLocationLayer,
2587
+ {
2588
+ forcedLocation: userLocationState.coords,
2589
+ heading: userLocationState.heading
2590
+ }
2591
+ )),
2592
+ displayRoute && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(RouteLayer, { geometry: displayRoute }),
2593
+ pois.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2594
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(POILayers, { pois }),
2595
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(POIMarkers, { onClick: onPoiClick, pois })
2596
+ ] }),
2597
+ mapLibreChildren
2598
+ ]
2599
+ }
2600
+ ),
2601
+ showControls && mapControls && !mapControls.hasControls && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2602
+ MapControls,
2603
+ {
2604
+ drawerContentRef,
2605
+ enableTouchDrag,
2606
+ etaData: showEtaData,
2607
+ initialViewState,
2608
+ isInternal: true,
2609
+ menuContent: menuContent || menuContentFromChildren,
2610
+ menuItems: resolvedMenuItems,
2611
+ ...controlProps
2612
+ }
2613
+ )
2614
+ ]
2615
+ }
2616
+ );
2617
+ };
2618
+ var SDKMapProviderWrapper = ({
2619
+ children
2620
+ }) => {
2621
+ const context = (0, import_react9.useContext)(MapControlsContext);
2622
+ if (context) {
2623
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_jsx_runtime11.Fragment, { children });
2624
+ }
2625
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SDKMapProvider, { children });
2626
+ };
2627
+ var Map2 = (props) => {
2628
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SDKMapProviderWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_maplibre5.MapProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MapInternal, { ...props }) }) });
2629
+ };
2630
+
2631
+ // src/components/MapWrapper.tsx
2632
+ var import_react10 = __toESM(require("react"), 1);
2633
+ var import_client = require("react-dom/client");
2634
+ function renderMap(containerId, props = {}) {
2635
+ const container = document.getElementById(containerId);
2636
+ if (!container) {
2637
+ throw new Error(`Container with id "${containerId}" not found.`);
2638
+ }
2639
+ const root = (0, import_client.createRoot)(container);
2640
+ root.render(import_react10.default.createElement(Map2, props));
2641
+ return root;
2642
+ }
2643
+
2644
+ // src/services/routingService.ts
2645
+ var RoutingService = {
2646
+ createErrorResponse(req, code, message) {
2647
+ return {
2648
+ error: { code, message },
2649
+ requestId: req.requestId,
2650
+ sdkVersion: req.sdkVersion,
2651
+ success: false,
2652
+ type: "SDK_ROUTING_GETROUTE_RESPONSE"
2653
+ };
2654
+ },
2655
+ decodePolyline(str, precision) {
2656
+ let index = 0, lat = 0, lng = 0;
2657
+ const coordinates = [];
2658
+ const shift = 10 ** precision;
2659
+ while (index < str.length) {
2660
+ let b, shiftNum = 0, result = 0;
2661
+ do {
2662
+ b = str.charCodeAt(index++) - 63;
2663
+ result |= (b & 31) << shiftNum;
2664
+ shiftNum += 5;
2665
+ } while (b >= 32);
2666
+ const dlat = result & 1 ? ~(result >> 1) : result >> 1;
2667
+ lat += dlat;
2668
+ shiftNum = 0;
2669
+ result = 0;
2670
+ do {
2671
+ b = str.charCodeAt(index++) - 63;
2672
+ result |= (b & 31) << shiftNum;
2673
+ shiftNum += 5;
2674
+ } while (b >= 32);
2675
+ const dlng = result & 1 ? ~(result >> 1) : result >> 1;
2676
+ lng += dlng;
2677
+ coordinates.push([lng / shift, lat / shift]);
2678
+ }
2679
+ return coordinates;
2680
+ },
2681
+ async fetchOSRMRoute(req, coords, profile, baseUrl) {
2682
+ const coordsString = coords.map((c) => `${c[0]},${c[1]}`).join(";");
2683
+ const osrmProfile = profile === "walking" ? "foot" : profile === "driving" ? "car" : "bike";
2684
+ const url = `${baseUrl}/route/v1/${osrmProfile}/${coordsString}?overview=full&geometries=geojson&steps=true`;
2685
+ const response = await fetch(url);
2686
+ if (!response.ok) {
2687
+ throw new Error(`OSRM Error: ${response.statusText}`);
2688
+ }
2689
+ const data = await response.json();
2690
+ if (data.code !== "Ok" || !data.routes || data.routes.length === 0) {
2691
+ throw new Error("NO_ROUTE");
2692
+ }
2693
+ const route = data.routes[0];
2694
+ const steps = route.legs?.flatMap(
2695
+ (leg) => leg.steps?.map((step) => ({
2696
+ distance: step.distance,
2697
+ duration: step.duration,
2698
+ instruction: step.maneuver?.instruction || "Continue"
2699
+ }))
2700
+ ) || [];
2701
+ return {
2702
+ data: {
2703
+ distanceMeters: route.distance,
2704
+ durationSeconds: route.duration,
2705
+ geometry: route.geometry,
2706
+ steps
2707
+ },
2708
+ requestId: req.requestId,
2709
+ sdkVersion: req.sdkVersion,
2710
+ success: true,
2711
+ type: "SDK_ROUTING_GETROUTE_RESPONSE"
2712
+ };
2713
+ },
2714
+ async fetchRoute(req, config) {
2715
+ const { origin, destination, waypoints, profile, language } = req.payload;
2716
+ if (!this.isValidCoordinate(origin) || !this.isValidCoordinate(destination)) {
2717
+ return this.createErrorResponse(
2718
+ req,
2719
+ "INVALID_INPUT",
2720
+ "Origin or destination coordinates are invalid."
2721
+ );
2722
+ }
2723
+ if (waypoints && !waypoints.every(this.isValidCoordinate)) {
2724
+ return this.createErrorResponse(
2725
+ req,
2726
+ "INVALID_INPUT",
2727
+ "One or more waypoints are invalid."
2728
+ );
2729
+ }
2730
+ const allCoords = [origin, ...waypoints || [], destination];
2731
+ try {
2732
+ if (config.backend === "osrm") {
2733
+ return await this.fetchOSRMRoute(
2734
+ req,
2735
+ allCoords,
2736
+ profile,
2737
+ config.baseUrl
2738
+ );
2739
+ } else {
2740
+ return await this.fetchValhallaRoute(
2741
+ req,
2742
+ allCoords,
2743
+ profile,
2744
+ language,
2745
+ config.baseUrl
2746
+ );
2747
+ }
2748
+ } catch (error) {
2749
+ if (error instanceof Error && error.message.includes("NO_ROUTE")) {
2750
+ return this.createErrorResponse(
2751
+ req,
2752
+ "NO_ROUTE",
2753
+ "No route could be found for the given points."
2754
+ );
2755
+ }
2756
+ if (error instanceof TypeError) {
2757
+ return this.createErrorResponse(
2758
+ req,
2759
+ "NETWORK_ERROR",
2760
+ "Failed to connect to the routing backend."
2761
+ );
2762
+ }
2763
+ return this.createErrorResponse(
2764
+ req,
2765
+ "BACKEND_ERROR",
2766
+ `Routing backend error: ${error instanceof Error ? error.message : "Unknown error"}`
2767
+ );
2768
+ }
2769
+ },
2770
+ async fetchValhallaRoute(req, coords, profile, language, baseUrl) {
2771
+ const valhallaProfile = profile === "driving" ? "auto" : profile === "walking" ? "pedestrian" : "bicycle";
2772
+ const body = {
2773
+ costing: valhallaProfile,
2774
+ directions_options: {
2775
+ language: language || "en-US"
2776
+ },
2777
+ locations: coords.map((c) => ({ lat: c[1], lon: c[0] }))
2778
+ };
2779
+ const response = await fetch(`${baseUrl}/route`, {
2780
+ body: JSON.stringify(body),
2781
+ headers: { "Content-Type": "application/json" },
2782
+ method: "POST"
2783
+ });
2784
+ if (!response.ok) {
2785
+ throw new Error(`Valhalla Error: ${response.statusText}`);
2786
+ }
2787
+ const data = await response.json();
2788
+ if (!data.trip || !data.trip.legs || data.trip.legs.length === 0) {
2789
+ throw new Error("NO_ROUTE");
2790
+ }
2791
+ const trip = data.trip;
2792
+ const decodedCoordinates = this.decodePolyline(trip.legs[0].shape, 6);
2793
+ const steps = trip.legs.flatMap(
2794
+ (leg) => leg.maneuvers?.map((maneuver) => ({
2795
+ distance: maneuver.length * 1e3,
2796
+ // Valhalla returns km
2797
+ duration: maneuver.time,
2798
+ instruction: maneuver.instruction || "Continue"
2799
+ }))
2800
+ ) || [];
2801
+ return {
2802
+ data: {
2803
+ distanceMeters: trip.summary.length * 1e3,
2804
+ // km to m
2805
+ durationSeconds: trip.summary.time,
2806
+ geometry: {
2807
+ coordinates: decodedCoordinates,
2808
+ type: "LineString"
2809
+ },
2810
+ steps
2811
+ },
2812
+ requestId: req.requestId,
2813
+ sdkVersion: req.sdkVersion,
2814
+ success: true,
2815
+ type: "SDK_ROUTING_GETROUTE_RESPONSE"
2816
+ };
2817
+ },
2818
+ isValidCoordinate(coord) {
2819
+ const [lng, lat] = coord;
2820
+ return lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90;
2821
+ }
2822
+ };
2823
+
2824
+ // package.json
2825
+ var version = "0.2.1";
2826
+
2827
+ // src/sdk.ts
2828
+ function deepFreeze(obj) {
2829
+ const propNames = Object.getOwnPropertyNames(obj);
2830
+ for (const name of propNames) {
2831
+ const value = obj[name];
2832
+ if (value && typeof value === "object" && !Object.isFrozen(value) && // Skip freezing of native objects or circular refs if any (though not expected here)
2833
+ !(value instanceof Bridge)) {
2834
+ deepFreeze(value);
2835
+ }
2836
+ }
2837
+ return Object.freeze(obj);
2838
+ }
2839
+ var globalRoutingConfig = null;
2840
+ var MiniAppSDK = class {
2841
+ /**
2842
+ * Map services.
2843
+ * Provides access to MapLibre-based mapping components.
2844
+ */
2845
+ maps = Object.freeze({
2846
+ /**
2847
+ * Renders a MapLibre map into a DOM element.
2848
+ * Useful for non-React environments.
2849
+ */
2850
+ render: renderMap
2851
+ });
2852
+ bridge;
2853
+ /**
2854
+ * Local SQLite Storage.
2855
+ * Data is persisted on the device and is isolated per Mini-App.
2856
+ */
2857
+ storage = Object.freeze({
2858
+ /** Retrieves a value by key. */
2859
+ get: (key) => this.bridge.send("STORAGE_GET", { key }),
2860
+ /** Retrieves all key-value pairs stored for this Mini-App. */
2861
+ getAll: () => this.bridge.send("STORAGE_GET_ALL"),
2862
+ /** Sets a value for a specific key. */
2863
+ set: (key, value) => this.bridge.send("STORAGE_SET", { key, value })
2864
+ });
2865
+ /**
2866
+ * Hardware-level Secure Storage.
2867
+ * Data is stored in the device's secure enclave (Keychain/Keystore) and is isolated per Mini-App.
2868
+ */
2869
+ secureStore = Object.freeze({
2870
+ /** Retrieves a value by key from secure storage. */
2871
+ get: (key) => this.bridge.send("SECURE_GET", { key }),
2872
+ /** Sets a value for a specific key in secure storage. */
2873
+ set: (key, value) => this.bridge.send("SECURE_SET", { key, value })
2874
+ });
2875
+ /**
2876
+ * Database Storage.
2877
+ * Access to user-app specific persistent database storage.
2878
+ */
2879
+ database = Object.freeze({
2880
+ /** Retrieves the user data stored in the backend database. */
2881
+ getUserData: () => this.bridge.send("DATABASE_GET_USER_DATA"),
2882
+ /** Updates the user data stored in the backend database. */
2883
+ setUserData: (data) => this.bridge.send("DATABASE_SET_USER_DATA", { data })
2884
+ });
2885
+ /**
2886
+ * Device Location services.
2887
+ */
2888
+ location = Object.freeze({
2889
+ /**
2890
+ * Retrieves the current GPS position of the device.
2891
+ */
2892
+ getCurrentPosition: () => this.bridge.send("GET_CURRENT_POSITION"),
2893
+ /**
2894
+ * Starts the continuous location and sensor stream.
2895
+ * @param config Optional configuration for thresholds and intervals.
2896
+ */
2897
+ startStream: (config) => this.bridge.send("START_LOCATION_STREAM", config),
2898
+ /**
2899
+ * Stops the continuous location and sensor stream.
2900
+ */
2901
+ stopStream: () => this.bridge.send("STOP_LOCATION_STREAM")
2902
+ });
2903
+ /**
2904
+ * Identity & Profile management.
2905
+ */
2906
+ user = Object.freeze({
2907
+ /**
2908
+ * Fetch user profile data.
2909
+ * @param fields Optional list of specific fields to retrieve (e.g., ['fullName', 'username']).
2910
+ */
2911
+ getProfile: (fields) => this.bridge.send("GET_USER_PROFILE", { fields })
2912
+ });
2913
+ /**
2914
+ * Explicit Permission Management.
2915
+ * Allows requesting access to sensitive device features or user data.
2916
+ */
2917
+ permission = Object.freeze({
2918
+ /**
2919
+ * Explicitly request access to sensitive data.
2920
+ * @param type The type of permission to request ('location' or 'profile').
2921
+ * @param fields If requesting 'profile', the specific fields required.
2922
+ */
2923
+ request: ((type, fields) => {
2924
+ return this.bridge.send("REQUEST_PERMISSION", { fields, type });
2925
+ })
2926
+ });
2927
+ /**
2928
+ * Payments integration.
2929
+ */
2930
+ payment = Object.freeze({
2931
+ /**
2932
+ * Triggers the native payment flow.
2933
+ * @param payload Amount, currency, and itemized list for the payment.
2934
+ */
2935
+ request: (payload) => this.bridge.send("PAYMENT_REQUEST", payload)
2936
+ });
2937
+ /**
2938
+ * Live Activities & Notifications.
2939
+ * Allows the Mini-App to display real-time updates in the host shell.
2940
+ */
2941
+ activity = Object.freeze({
2942
+ /**
2943
+ * Starts a new live activity.
2944
+ * @param id Unique identifier for the activity.
2945
+ * @param title Display title for the activity.
2946
+ * @param status Initial status message.
2947
+ */
2948
+ start: (id, title, status) => this.bridge.send("START_ACTIVITY", { id, status, title })
2949
+ });
2950
+ /**
2951
+ * Haptic feedback.
2952
+ */
2953
+ haptic = Object.freeze({
2954
+ /**
2955
+ * Triggers a haptic vibration on the device.
2956
+ * @param style The style of haptic feedback (e.g., 'light', 'medium', 'heavy', 'success', 'warning', 'error').
2957
+ */
2958
+ trigger: (style) => this.bridge.send("TRIGGER_HAPTIC", { style })
2959
+ });
2960
+ /**
2961
+ * Network Proxy.
2962
+ * Allows the Mini-App to make network requests through the host shell to bypass CORS or use host-level authentication.
2963
+ */
2964
+ network = Object.freeze({
2965
+ /**
2966
+ * Performs a network request via the host shell.
2967
+ * @param options Request method, URL, headers, and body.
2968
+ */
2969
+ request: async (options) => {
2970
+ return this.bridge.send("NETWORK_REQUEST", options);
2971
+ }
2972
+ });
2973
+ /**
2974
+ * Navigation and Journey controls.
2975
+ * Allows programmatically starting itineraries and managing navigation state.
2976
+ */
2977
+ navigation = Object.freeze({
2978
+ /**
2979
+ * Aborts the current navigation journey.
2980
+ */
2981
+ abort: () => {
2982
+ this.bridge.handleNativeMessage({
2983
+ eventName: "SDK_NAVIGATION_ABORT",
2984
+ type: "EVENT"
2985
+ });
2986
+ },
2987
+ /**
2988
+ * Creates a new itinerary preview.
2989
+ * @param options Destination and routing options.
2990
+ */
2991
+ createJourney: (options) => {
2992
+ this.bridge.handleNativeMessage({
2993
+ eventName: "SDK_NAVIGATION_CREATE",
2994
+ payload: { ...options, mode: "preview" },
2995
+ type: "EVENT"
2996
+ });
2997
+ },
2998
+ /**
2999
+ * Starts the active navigation (active guidance).
3000
+ */
3001
+ start: () => {
3002
+ this.bridge.handleNativeMessage({
3003
+ eventName: "SDK_NAVIGATION_START",
3004
+ type: "EVENT"
3005
+ });
3006
+ }
3007
+ });
3008
+ /**
3009
+ * Fidelity & Rewards program integration.
3010
+ */
3011
+ fidelity = Object.freeze({
3012
+ /** Returns current fidelity balance. */
3013
+ getFidelityBalance: () => this.bridge.send("GET_FIDELITY_BALANCE"),
3014
+ /**
3015
+ * Retrieves fidelity transaction history.
3016
+ * @param params Filtering and pagination parameters.
3017
+ */
3018
+ getFidelityHistory: (params) => this.bridge.send("GET_FIDELITY_HISTORY", params),
3019
+ /**
3020
+ * Triggers the native fidelity history view.
3021
+ */
3022
+ showHistory: () => this.bridge.send("SHOW_FIDELITY_HISTORY"),
3023
+ /**
3024
+ * Authorize spending fidelity points (FPoints) via Two-Phase Authorization.
3025
+ * @param intentUuid UUID generated by your backend for the transaction.
3026
+ */
3027
+ spendFPoints: (intentUuid) => this.bridge.send("SPEND_FPOINTS", { intentUuid })
3028
+ });
3029
+ /**
3030
+ * UI and Navigation controls.
3031
+ */
3032
+ ui = Object.freeze({
3033
+ /** Closes the Mini-App and returns to the host shell. */
3034
+ exit: () => this.bridge.send("EXIT"),
3035
+ /**
3036
+ * Configures native scroll behaviors like Pull-to-Refresh and Infinite Scroll.
3037
+ * @param config Scroll configuration for top/bottom detection.
3038
+ */
3039
+ setScrollConfig: (config) => this.bridge.send("SET_SCROLL_CONFIG", config)
3040
+ });
3041
+ /**
3042
+ * Debug controls
3043
+ */
3044
+ debug = Object.freeze({
3045
+ /**
3046
+ * Log to the shell app
3047
+ * @param message
3048
+ */
3049
+ log: (...message) => this.bridge.send("DEBUG_LOG", { message })
3050
+ });
3051
+ /**
3052
+ * Routing integration (OSRM / Valhalla).
3053
+ * Provides access to route calculation and rendering on the host map.
3054
+ */
3055
+ routing = Object.freeze({
3056
+ /**
3057
+ * Clears the currently rendered route.
3058
+ */
3059
+ clearRoute: () => {
3060
+ const requestId = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2);
3061
+ return this.bridge.send("SDK_ROUTING_CLEARROUTE", {
3062
+ requestId,
3063
+ sdkVersion: version
3064
+ });
3065
+ },
3066
+ /**
3067
+ * Draws the calculated route on the host map.
3068
+ * @param routeData The route data returned by getRoute.
3069
+ */
3070
+ drawRoute: (routeData) => {
3071
+ const requestId = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2);
3072
+ return this.bridge.send("SDK_ROUTING_DRAWROUTE", {
3073
+ payload: { routeData },
3074
+ requestId,
3075
+ sdkVersion: version
3076
+ });
3077
+ },
3078
+ /**
3079
+ * Calculates a route between points.
3080
+ * @param payload Request payload containing origin, destination, and options.
3081
+ */
3082
+ getRoute: async (payload) => {
3083
+ const requestId = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2);
3084
+ const request = {
3085
+ payload,
3086
+ requestId,
3087
+ sdkVersion: version,
3088
+ type: "SDK_ROUTING_GETROUTE"
3089
+ };
3090
+ if (globalRoutingConfig) {
3091
+ const response = await RoutingService.fetchRoute(
3092
+ request,
3093
+ globalRoutingConfig
3094
+ );
3095
+ if (response.success && response.data) {
3096
+ return response.data;
3097
+ }
3098
+ throw new Error(response.error?.message || "Routing failed");
3099
+ }
3100
+ return this.bridge.send("SDK_ROUTING_GETROUTE", {
3101
+ payload,
3102
+ requestId,
3103
+ sdkVersion: version
3104
+ });
3105
+ }
3106
+ });
3107
+ constructor() {
3108
+ this.bridge = new Bridge();
3109
+ this.initialize = this.initialize.bind(this);
3110
+ this.on = this.on.bind(this);
3111
+ this.off = this.off.bind(this);
3112
+ }
3113
+ /**
3114
+ * Configures global SDK settings.
3115
+ */
3116
+ config(options) {
3117
+ if (options.routingBackend) {
3118
+ globalRoutingConfig = options.routingBackend;
3119
+ }
3120
+ }
3121
+ /**
3122
+ * Updates the global context shared between the Mini-App and the native shell.
3123
+ * Useful for synchronizing state like theme preferences, UI flags, or custom app-specific context.
3124
+ * @param context Partial context object to merge into the existing shell context.
3125
+ */
3126
+ updateContext(context) {
3127
+ return this.bridge.send("UPDATE_CONTEXT", context);
3128
+ }
3129
+ /**
3130
+ * The starting point for the Mini-App. Must be called before any other SDK methods.
3131
+ * @param options The options to provide to the shell app
3132
+ */
3133
+ initialize(options) {
3134
+ return this.bridge.send("INITIALIZE", options);
3135
+ }
3136
+ /**
3137
+ * Registers a callback for a specific event from the native shell.
3138
+ * @param event The name of the event to listen for.
3139
+ * @param callback The function to execute when the event is triggered.
3140
+ * @returns A function to unregister the listener.
3141
+ */
3142
+ on(event, callback) {
3143
+ return this.bridge.on(event, callback);
3144
+ }
3145
+ /**
3146
+ * Unregisters a previously registered event listener.
3147
+ * @param event The name of the event.
3148
+ * @param callback The callback function to remove.
3149
+ */
3150
+ off(event, callback) {
3151
+ this.bridge.off(event, callback);
3152
+ }
3153
+ };
3154
+ var sdk = deepFreeze(new MiniAppSDK());
3155
+
3156
+ // src/components/BridgeMocker.tsx
3157
+ var import_react11 = require("react");
3158
+ var import_jsx_runtime12 = require("react/jsx-runtime");
3159
+ var getBearing = (p1, p2) => {
3160
+ const lat1 = p1[1] * Math.PI / 180;
3161
+ const lat2 = p2[1] * Math.PI / 180;
3162
+ const dLon = (p2[0] - p1[0]) * Math.PI / 180;
3163
+ const y = Math.sin(dLon) * Math.cos(lat2);
3164
+ const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
3165
+ return (Math.atan2(y, x) * 180 / Math.PI + 360) % 360;
3166
+ };
3167
+ var BridgeMocker = ({
3168
+ appId,
3169
+ initialLocation = [6.1258, 45.9058],
3170
+ // Annecy default
3171
+ isMoving = false,
3172
+ simulationPath,
3173
+ simulationSpeed = 1,
3174
+ mockJWTToken,
3175
+ children
3176
+ }) => {
3177
+ const [autoMove, setAutoMove] = (0, import_react11.useState)(isMoving);
3178
+ const [location, setLocation] = (0, import_react11.useState)(initialLocation);
3179
+ const [heading, setHeading] = (0, import_react11.useState)(0);
3180
+ const [speedMultiplier, setSpeedMultiplier] = (0, import_react11.useState)(simulationSpeed);
3181
+ const [isReady, setIsReady] = (0, import_react11.useState)(false);
3182
+ const [isMinimized, setIsMinimized] = (0, import_react11.useState)(true);
3183
+ const streamIntervalRef = (0, import_react11.useRef)(null);
3184
+ const pathIndexRef = (0, import_react11.useRef)(0);
3185
+ const activeConfigRef = (0, import_react11.useRef)(null);
3186
+ const stateRef = (0, import_react11.useRef)({
3187
+ autoMove,
3188
+ heading,
3189
+ location,
3190
+ mockJWTToken,
3191
+ pushLocationUpdate: () => {
3192
+ },
3193
+ setHeading,
3194
+ setLocation,
3195
+ simulationPath,
3196
+ speedMultiplier
3197
+ });
3198
+ const pushLocationUpdate = (0, import_react11.useCallback)(() => {
3199
+ const currentState = stateRef.current;
3200
+ const payload = {
3201
+ accelerometer: null,
3202
+ heading: {
3203
+ accuracy: 3,
3204
+ magneticHeading: currentState.heading,
3205
+ trueHeading: currentState.heading
3206
+ },
3207
+ location: {
3208
+ coords: {
3209
+ accuracy: 10,
3210
+ altitude: 0,
3211
+ altitudeAccuracy: null,
3212
+ heading: currentState.heading,
3213
+ latitude: currentState.location[1],
3214
+ longitude: currentState.location[0],
3215
+ speed: currentState.autoMove ? 1.5 * currentState.speedMultiplier : 0
3216
+ },
3217
+ timestamp: Date.now()
3218
+ },
3219
+ timestamp: Date.now()
3220
+ };
3221
+ window.__MiniAppBridge?.onNativeMessage({
3222
+ eventName: "LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */,
3223
+ payload,
3224
+ type: "EVENT"
3225
+ });
3226
+ }, []);
3227
+ (0, import_react11.useEffect)(() => {
3228
+ stateRef.current = {
3229
+ autoMove,
3230
+ heading,
3231
+ location,
3232
+ mockJWTToken,
3233
+ pushLocationUpdate,
3234
+ setHeading,
3235
+ setLocation,
3236
+ simulationPath,
3237
+ speedMultiplier
3238
+ };
3239
+ }, [
3240
+ location,
3241
+ heading,
3242
+ autoMove,
3243
+ pushLocationUpdate,
3244
+ simulationPath,
3245
+ speedMultiplier,
3246
+ mockJWTToken
3247
+ ]);
3248
+ (0, import_react11.useEffect)(() => {
3249
+ setAutoMove(isMoving);
3250
+ }, [isMoving]);
3251
+ (0, import_react11.useEffect)(() => {
3252
+ setSpeedMultiplier(simulationSpeed);
3253
+ }, [simulationSpeed]);
3254
+ (0, import_react11.useEffect)(() => {
3255
+ if (simulationPath && simulationPath.length > 0) {
3256
+ pathIndexRef.current = 0;
3257
+ setLocation(simulationPath[0]);
3258
+ }
3259
+ }, [simulationPath]);
3260
+ (0, import_react11.useEffect)(() => {
3261
+ if (streamIntervalRef.current && activeConfigRef.current) {
3262
+ sdk.bridge.send(
3263
+ "START_LOCATION_STREAM",
3264
+ activeConfigRef.current
3265
+ );
3266
+ }
3267
+ }, [speedMultiplier]);
3268
+ (0, import_react11.useEffect)(() => {
3269
+ const bridge = sdk.bridge;
3270
+ const originalSend = bridge.send.bind(bridge);
3271
+ const stopStream = () => {
3272
+ if (streamIntervalRef.current) {
3273
+ clearInterval(streamIntervalRef.current);
3274
+ streamIntervalRef.current = null;
3275
+ }
3276
+ activeConfigRef.current = null;
3277
+ };
3278
+ const startStream = (config) => {
3279
+ stopStream();
3280
+ activeConfigRef.current = config || {};
3281
+ const baseInterval = config?.interval || 500;
3282
+ const interval = Math.max(
3283
+ 10,
3284
+ baseInterval / stateRef.current.speedMultiplier
3285
+ );
3286
+ streamIntervalRef.current = setInterval(() => {
3287
+ const state = stateRef.current;
3288
+ if (state.autoMove) {
3289
+ if (state.simulationPath && state.simulationPath.length > 0) {
3290
+ if (pathIndexRef.current < state.simulationPath.length - 1) {
3291
+ const currentPoint = state.simulationPath[pathIndexRef.current];
3292
+ const nextPoint = state.simulationPath[pathIndexRef.current + 1];
3293
+ pathIndexRef.current += 1;
3294
+ const newHeading = getBearing(currentPoint, nextPoint);
3295
+ state.setHeading(newHeading);
3296
+ state.setLocation(nextPoint);
3297
+ } else {
3298
+ state.setLocation(
3299
+ state.simulationPath[state.simulationPath.length - 1]
3300
+ );
3301
+ }
3302
+ } else {
3303
+ state.setLocation((prev) => [
3304
+ prev[0] + (Math.random() - 0.5) * 1e-4,
3305
+ prev[1] + (Math.random() - 0.5) * 1e-4
3306
+ ]);
3307
+ }
3308
+ }
3309
+ state.pushLocationUpdate();
3310
+ }, interval);
3311
+ };
3312
+ const simulateNetworkRequest = async (payload) => {
3313
+ const {
3314
+ url,
3315
+ method = "GET",
3316
+ headers: incomingHeaders = {},
3317
+ body
3318
+ } = payload;
3319
+ const state = stateRef.current;
3320
+ const headers = new Headers(incomingHeaders);
3321
+ if (state.mockJWTToken) {
3322
+ headers.set(
3323
+ "Authorization",
3324
+ `Bearer ${state.mockJWTToken.replace("Bearer ", "")}`
3325
+ );
3326
+ }
3327
+ if (body && typeof body === "object" && !headers.has("Content-Type")) {
3328
+ headers.set("Content-Type", "application/json");
3329
+ }
3330
+ try {
3331
+ const response = await fetch(url, {
3332
+ body: body ? typeof body === "string" ? body : JSON.stringify(body) : void 0,
3333
+ headers,
3334
+ method
3335
+ });
3336
+ const contentType = response.headers.get("content-type");
3337
+ let responseData;
3338
+ if (contentType?.includes("application/json")) {
3339
+ responseData = await response.json();
3340
+ } else {
3341
+ responseData = await response.text();
3342
+ }
3343
+ const headersObj = {};
3344
+ response.headers.forEach((value, key) => {
3345
+ headersObj[key] = value;
3346
+ });
3347
+ return {
3348
+ data: responseData,
3349
+ headers: headersObj,
3350
+ status: response.status,
3351
+ statusText: response.statusText
3352
+ };
3353
+ } catch (error) {
3354
+ throw {
3355
+ error: error.message || "Network request failed",
3356
+ status: 0
3357
+ };
3358
+ }
3359
+ };
3360
+ bridge.send = async (type, payload) => {
3361
+ if (type === "INITIALIZE") {
3362
+ return Promise.resolve({
3363
+ appId,
3364
+ // Legacy support
3365
+ context: { theme: "dark" },
3366
+ // Legacy support
3367
+ description: "Mocked for Dev",
3368
+ env: {},
3369
+ insets: { bottom: 0, left: 0, right: 0, top: 0 },
3370
+ name: "Vite Dev (Mock)",
3371
+ params: {},
3372
+ profile: {
3373
+ countryCode: "FR",
3374
+ email: "caveman@up.app",
3375
+ fullName: "Smart Caveman",
3376
+ localNumber: "0600000000",
3377
+ username: "smart_caveman",
3378
+ uuid: "mock-user-42"
3379
+ },
3380
+ success: true,
3381
+ userData: {},
3382
+ uuid: appId || "mock-app-id",
3383
+ version: "1.0.0"
3384
+ });
3385
+ }
3386
+ if (type === "GET_CURRENT_POSITION") {
3387
+ const state = stateRef.current;
3388
+ return Promise.resolve({
3389
+ coords: {
3390
+ accuracy: 10,
3391
+ altitude: 0,
3392
+ altitudeAccuracy: 0,
3393
+ heading: state.heading,
3394
+ latitude: state.location[1],
3395
+ longitude: state.location[0],
3396
+ speed: state.autoMove ? 1.5 * state.speedMultiplier : 0
3397
+ },
3398
+ timestamp: Date.now()
3399
+ });
3400
+ }
3401
+ if (type === "START_LOCATION_STREAM") {
3402
+ startStream(payload);
3403
+ return Promise.resolve();
3404
+ }
3405
+ if (type === "STOP_LOCATION_STREAM") {
3406
+ stopStream();
3407
+ return Promise.resolve();
3408
+ }
3409
+ if (type === "NETWORK_REQUEST") {
3410
+ return simulateNetworkRequest(payload);
3411
+ }
3412
+ if (type === "STORAGE_GET" || type === "SECURE_GET") {
3413
+ return Promise.resolve(localStorage.getItem(`mock_sdk_${payload.key}`));
3414
+ }
3415
+ if (type === "STORAGE_SET" || type === "SECURE_SET") {
3416
+ localStorage.setItem(`mock_sdk_${payload.key}`, payload.value);
3417
+ return Promise.resolve();
3418
+ }
3419
+ if (type === "GET_USER_PROFILE") {
3420
+ return Promise.resolve({
3421
+ countryCode: "FR",
3422
+ email: "caveman@up.app",
3423
+ fullName: "Smart Caveman",
3424
+ localNumber: "0600000000",
3425
+ username: "smart_caveman",
3426
+ uuid: "mock-user-42"
3427
+ });
3428
+ }
3429
+ if (type === "GET_FIDELITY_BALANCE") {
3430
+ return Promise.resolve({ balance: 1337, currency: "FPoints" });
3431
+ }
3432
+ if (type === "PAYMENT_REQUEST") {
3433
+ return Promise.resolve({
3434
+ success: true,
3435
+ transactionId: `tx_${Math.random().toString(36).slice(2)}`
3436
+ });
3437
+ }
3438
+ if (type === "TRIGGER_HAPTIC" || type === "EXIT" || type === "UPDATE_CONTEXT") {
3439
+ return Promise.resolve({ success: true });
3440
+ }
3441
+ return originalSend(type, payload);
3442
+ };
3443
+ setIsReady(true);
3444
+ return () => {
3445
+ stopStream();
3446
+ bridge.send = originalSend;
3447
+ };
3448
+ }, [appId]);
3449
+ if (!isReady) return null;
3450
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "relative w-full h-full", children: [
3451
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "w-full h-full", children }),
3452
+ isMinimized ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3453
+ "button",
3454
+ {
3455
+ className: "fixed bottom-4 right-4 w-8 h-8 rounded-full bg-slate-900/60 backdrop-blur-sm border border-slate-700/50 flex items-center justify-center text-slate-400 hover:text-blue-400 z-[9999] transition-all shadow-lg group",
3456
+ onClick: () => setIsMinimized(false),
3457
+ title: "Open Simulation Controls",
3458
+ type: "button",
3459
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3460
+ "svg",
3461
+ {
3462
+ className: "group-hover:scale-110 transition-transform",
3463
+ fill: "none",
3464
+ height: "14",
3465
+ stroke: "currentColor",
3466
+ strokeLinecap: "round",
3467
+ strokeLinejoin: "round",
3468
+ strokeWidth: "2",
3469
+ viewBox: "0 0 24 24",
3470
+ width: "14",
3471
+ xmlns: "http://www.w3.org/2000/svg",
3472
+ children: [
3473
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M21 4H3" }),
3474
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M10 8H3" }),
3475
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M21 12H3" }),
3476
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M10 16H3" }),
3477
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M21 20H3" }),
3478
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("circle", { cx: "14", cy: "8", r: "2" }),
3479
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("circle", { cx: "14", cy: "16", r: "2" })
3480
+ ]
3481
+ }
3482
+ )
3483
+ }
3484
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3485
+ "div",
3486
+ {
3487
+ className: "fixed bottom-4 right-4 bg-slate-900 text-slate-300 font-mono text-xs rounded-xl shadow-2xl border border-slate-700 pointer-events-auto flex flex-col w-80 overflow-hidden animate-in slide-in-from-right duration-300",
3488
+ style: { zIndex: 9998 },
3489
+ children: [
3490
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3491
+ "div",
3492
+ {
3493
+ className: "flex justify-between items-center p-3 border-b border-slate-800 cursor-pointer bg-slate-800/50 hover:bg-slate-800 transition-colors",
3494
+ onClick: () => setIsMinimized(true),
3495
+ children: [
3496
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "font-bold text-slate-100 flex items-center gap-2", children: [
3497
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-lg", children: "\u{1F6E0}\uFE0F" }),
3498
+ " Simulation Controls"
3499
+ ] }),
3500
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { className: "text-slate-500 hover:text-white", type: "button", children: "\u25BC" })
3501
+ ]
3502
+ }
3503
+ ),
3504
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "p-4 flex flex-col gap-3", children: [
3505
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex justify-between items-center", children: [
3506
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer", children: [
3507
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3508
+ "input",
3509
+ {
3510
+ checked: autoMove,
3511
+ className: "accent-blue-500 w-4 h-4",
3512
+ onChange: (e) => setAutoMove(e.target.checked),
3513
+ type: "checkbox"
3514
+ }
3515
+ ),
3516
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "text-slate-200 font-bold", children: [
3517
+ "Auto-Move ",
3518
+ simulationPath ? "(Path)" : "(Random)"
3519
+ ] })
3520
+ ] }),
3521
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
3522
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] text-slate-500", children: "Speed:" }),
3523
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3524
+ "select",
3525
+ {
3526
+ className: "bg-slate-800 border border-slate-700 rounded px-1 py-0.5 text-white focus:outline-none focus:border-blue-500 text-[10px]",
3527
+ onChange: (e) => setSpeedMultiplier(Number(e.target.value)),
3528
+ value: speedMultiplier,
3529
+ children: [1, 2, 5, 10, 50, 100, 250].map((s) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("option", { value: s, children: [
3530
+ s,
3531
+ "x"
3532
+ ] }, s))
3533
+ }
3534
+ )
3535
+ ] })
3536
+ ] }),
3537
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
3538
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-1", children: [
3539
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] text-slate-500 text-left", children: "Lng" }),
3540
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3541
+ "input",
3542
+ {
3543
+ className: "bg-slate-800 border border-slate-700 rounded px-2 py-1 text-white focus:outline-none focus:border-blue-500",
3544
+ onChange: (e) => {
3545
+ const lng = parseFloat(e.target.value) || 0;
3546
+ setLocation([lng, location[1]]);
3547
+ pushLocationUpdate();
3548
+ },
3549
+ step: "0.001",
3550
+ type: "number",
3551
+ value: location[0]
3552
+ }
3553
+ )
3554
+ ] }),
3555
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-1", children: [
3556
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] text-slate-500 text-left", children: "Lat" }),
3557
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3558
+ "input",
3559
+ {
3560
+ className: "bg-slate-800 border border-slate-700 rounded px-2 py-1 text-white focus:outline-none focus:border-blue-500",
3561
+ onChange: (e) => {
3562
+ const lat = parseFloat(e.target.value) || 0;
3563
+ setLocation([location[0], lat]);
3564
+ pushLocationUpdate();
3565
+ },
3566
+ step: "0.001",
3567
+ type: "number",
3568
+ value: location[1]
3569
+ }
3570
+ )
3571
+ ] }),
3572
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-1 col-span-2", children: [
3573
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[10px] text-slate-500", children: "Heading (Degrees)" }),
3574
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3575
+ "input",
3576
+ {
3577
+ className: "w-full accent-blue-500",
3578
+ max: "360",
3579
+ min: "0",
3580
+ onChange: (e) => {
3581
+ setHeading(parseInt(e.target.value, 10));
3582
+ pushLocationUpdate();
3583
+ },
3584
+ type: "range",
3585
+ value: heading
3586
+ }
3587
+ ),
3588
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "text-right text-slate-400", children: [
3589
+ heading,
3590
+ "\xB0"
3591
+ ] })
3592
+ ] })
3593
+ ] })
3594
+ ] })
3595
+ ]
3596
+ }
3597
+ )
3598
+ ] });
3599
+ };
3600
+
3601
+ // src/components/DebugConsole.tsx
3602
+ var import_react12 = require("react");
3603
+ var import_jsx_runtime13 = require("react/jsx-runtime");
3604
+ var DebugConsole = ({
3605
+ isOpen: initialOpen = false,
3606
+ onClose
3607
+ }) => {
3608
+ const [showConsole, setShowConsole] = (0, import_react12.useState)(initialOpen);
3609
+ const [logs, setLogs] = (0, import_react12.useState)([]);
3610
+ const scrollRef = (0, import_react12.useRef)(null);
3611
+ (0, import_react12.useEffect)(() => {
3612
+ setShowConsole(initialOpen);
3613
+ }, [initialOpen]);
3614
+ const clearLogs = () => setLogs([]);
3615
+ const addLog = (0, import_react12.useCallback)((label, data) => {
3616
+ if (label.includes("LOCATION_STREAM_UPDATE" /* LOCATION_STREAM_UPDATE */)) return;
3617
+ const entry = {
3618
+ content: typeof data === "string" ? data : JSON.stringify(data, null, 2),
3619
+ id: Math.random().toString(36).slice(2),
3620
+ label,
3621
+ time: (/* @__PURE__ */ new Date()).toLocaleTimeString()
3622
+ };
3623
+ setLogs((prev) => [entry, ...prev].slice(0, 100));
3624
+ }, []);
3625
+ (0, import_react12.useEffect)(() => {
3626
+ if (typeof window === "undefined") return;
3627
+ const bridge = sdk.bridge;
3628
+ const originalSend = bridge.send.bind(bridge);
3629
+ bridge.send = async (type, payload) => {
3630
+ addLog(`OUT: ${type}`, payload);
3631
+ return originalSend(type, payload);
3632
+ };
3633
+ const originalHandle = bridge.handleNativeMessage.bind(bridge);
3634
+ bridge.handleNativeMessage = (envelope) => {
3635
+ const type = envelope.eventName || envelope.event || envelope.type || "MSG";
3636
+ if (typeof type === "string") {
3637
+ addLog(`IN: ${type}`, envelope.payload || envelope.value || envelope);
3638
+ originalHandle(envelope);
3639
+ }
3640
+ };
3641
+ return () => {
3642
+ bridge.send = originalSend;
3643
+ bridge.handleNativeMessage = originalHandle;
3644
+ };
3645
+ }, [addLog]);
3646
+ if (!showConsole) {
3647
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3648
+ "button",
3649
+ {
3650
+ className: "fixed bottom-4 left-4 w-8 h-8 rounded-full bg-slate-900/60 backdrop-blur-sm border border-slate-700/50 flex items-center justify-center text-slate-400 hover:text-emerald-400 transition-all z-[9998] shadow-lg group",
3651
+ onClick: () => setShowConsole(true),
3652
+ title: "Open Debug Console",
3653
+ type: "button",
3654
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3655
+ "svg",
3656
+ {
3657
+ className: "group-hover:scale-110 transition-transform",
3658
+ fill: "none",
3659
+ height: "14",
3660
+ stroke: "currentColor",
3661
+ strokeLinecap: "round",
3662
+ strokeLinejoin: "round",
3663
+ strokeWidth: "2",
3664
+ viewBox: "0 0 24 24",
3665
+ width: "14",
3666
+ xmlns: "http://www.w3.org/2000/svg",
3667
+ children: [
3668
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "m8 2 1.88 1.88" }),
3669
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M14.12 3.88 16 2" }),
3670
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" }),
3671
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" }),
3672
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M12 20v-9" }),
3673
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M6.53 9C4.6 8.8 3 7.1 3 5" }),
3674
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M6 13H2" }),
3675
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M3 21c0-2.1 1.7-3.9 3.8-4" }),
3676
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M20.97 5c0 2.1-1.6 3.8-3.5 4" }),
3677
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M22 13h-4" }),
3678
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M17.2 17c2.1.1 3.8 1.9 3.8 4" })
3679
+ ]
3680
+ }
3681
+ )
3682
+ }
3683
+ );
3684
+ }
3685
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "fixed inset-x-0 bottom-0 w-full bg-[#0a080f] border-t border-slate-800 z-[9998] p-4 shadow-2xl flex flex-col animate-in slide-in-from-bottom duration-300", children: [
3686
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
3687
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "text-[10px] font-bold uppercase tracking-widest text-slate-400 flex items-center gap-2", children: [
3688
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-2 h-2 rounded-full bg-emerald-500 animate-pulse" }),
3689
+ "UP SDK Debug Console"
3690
+ ] }),
3691
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-4", children: [
3692
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3693
+ "button",
3694
+ {
3695
+ className: "text-[10px] text-slate-500 font-bold uppercase hover:text-slate-300 transition-colors",
3696
+ onClick: clearLogs,
3697
+ type: "button",
3698
+ children: "Clear"
3699
+ }
3700
+ ),
3701
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3702
+ "button",
3703
+ {
3704
+ className: "text-[10px] text-pink-500 font-bold uppercase hover:underline transition-colors",
3705
+ onClick: () => {
3706
+ setShowConsole(false);
3707
+ if (onClose) onClose();
3708
+ },
3709
+ type: "button",
3710
+ children: "Close"
3711
+ }
3712
+ )
3713
+ ] })
3714
+ ] }),
3715
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3716
+ "div",
3717
+ {
3718
+ className: "bg-black/80 rounded-xl p-3 font-mono text-[10px] text-emerald-400 min-h-[120px] max-h-[240px] overflow-y-auto border border-slate-900 custom-scrollbar",
3719
+ ref: scrollRef,
3720
+ children: logs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-slate-600 italic text-center py-8", children: "No logs yet. Interacting with the SDK will show events here." }) : logs.map((log) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3721
+ "div",
3722
+ {
3723
+ className: "mb-3 pb-3 border-b border-slate-900/50 last:border-0 last:mb-0 last:pb-0",
3724
+ children: [
3725
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-2 text-slate-500 mb-1 items-center", children: [
3726
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[8px]", children: log.time }),
3727
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-bold text-white uppercase text-[8px] bg-slate-800 px-1.5 py-0.5 rounded leading-none", children: log.label })
3728
+ ] }),
3729
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { className: "whitespace-pre-wrap break-all opacity-90", children: log.content })
3730
+ ]
3731
+ },
3732
+ log.id
3733
+ ))
3734
+ }
3735
+ )
3736
+ ] });
3737
+ };
3738
+
3739
+ // src/components/SDKProvider.tsx
3740
+ var import_react13 = require("react");
3741
+ var import_jsx_runtime14 = require("react/jsx-runtime");
3742
+ var SDKContext = (0, import_react13.createContext)({ isMocked: false });
3743
+ var useIsMocked = () => (0, import_react13.useContext)(SDKContext).isMocked;
3744
+ var SDKProvider = ({
3745
+ children,
3746
+ debug = false,
3747
+ ...mockerProps
3748
+ }) => {
3749
+ const isMocked = typeof window !== "undefined" && !(window.__UP_REACT_NATIVE_APP__ === true || !!window.ReactNativeWebView);
3750
+ const value = (0, import_react13.useMemo)(() => ({ isMocked }), [isMocked]);
3751
+ const content = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
3752
+ children,
3753
+ debug && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DebugConsole, {})
3754
+ ] });
3755
+ if (isMocked) {
3756
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SDKContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(BridgeMocker, { ...mockerProps, children: content }) });
3757
+ }
3758
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SDKContext.Provider, { value, children: content });
3759
+ };
3760
+
3761
+ // src/host/routingHandler.ts
3762
+ async function handleRouteRequest(req, config) {
3763
+ return RoutingService.fetchRoute(req, config);
3764
+ }
3765
+ function handleDrawRoute(map, miniAppId, req) {
3766
+ const sourceId = `sdk-route-${miniAppId}`;
3767
+ const casingLayerId = `sdk-route-casing-${miniAppId}`;
3768
+ const lineLayerId = `sdk-route-line-${miniAppId}`;
3769
+ const geojson = {
3770
+ features: [
3771
+ {
3772
+ geometry: req.payload.routeData.geometry,
3773
+ properties: {},
3774
+ type: "Feature"
3775
+ }
3776
+ ],
3777
+ type: "FeatureCollection"
3778
+ };
3779
+ const source = map.getSource(sourceId);
3780
+ if (source) {
3781
+ source.setData(geojson);
3782
+ } else {
3783
+ map.addSource(sourceId, {
3784
+ data: geojson,
3785
+ type: "geojson"
3786
+ });
3787
+ map.addLayer({
3788
+ id: casingLayerId,
3789
+ layout: {
3790
+ "line-cap": "round",
3791
+ "line-join": "round"
3792
+ },
3793
+ paint: {
3794
+ "line-color": "#111111",
3795
+ "line-opacity": 0.8,
3796
+ "line-width": 8
3797
+ },
3798
+ source: sourceId,
3799
+ type: "line"
3800
+ });
3801
+ map.addLayer({
3802
+ id: lineLayerId,
3803
+ layout: {
3804
+ "line-cap": "round",
3805
+ "line-join": "round"
3806
+ },
3807
+ paint: {
3808
+ "line-color": "#3b82f6",
3809
+ // Bright blue
3810
+ "line-width": 4
3811
+ },
3812
+ source: sourceId,
3813
+ type: "line"
3814
+ });
3815
+ }
3816
+ }
3817
+ function handleClearRoute(map, miniAppId) {
3818
+ const sourceId = `sdk-route-${miniAppId}`;
3819
+ const casingLayerId = `sdk-route-casing-${miniAppId}`;
3820
+ const lineLayerId = `sdk-route-line-${miniAppId}`;
3821
+ if (map.getLayer(lineLayerId)) map.removeLayer(lineLayerId);
3822
+ if (map.getLayer(casingLayerId)) map.removeLayer(casingLayerId);
3823
+ if (map.getSource(sourceId)) map.removeSource(sourceId);
3824
+ }
3825
+
3826
+ // src/index.ts
3827
+ var src_default = sdk;
3828
+ // Annotate the CommonJS export names for ESM import in node:
3829
+ 0 && (module.exports = {
3830
+ BridgeMocker,
3831
+ DebugConsole,
3832
+ GeolocationEvent,
3833
+ GlobalEvent,
3834
+ Map,
3835
+ MapControls,
3836
+ MapControlsContext,
3837
+ MapMenu,
3838
+ MapMenuDivider,
3839
+ MapMenuFooter,
3840
+ MapMenuItem,
3841
+ MapMenuTitle,
3842
+ MiniAppSDK,
3843
+ RouteLayer,
3844
+ SDKMapProvider,
3845
+ SDKProvider,
3846
+ UIEvent,
3847
+ handleClearRoute,
3848
+ handleDrawRoute,
3849
+ handleRouteRequest,
3850
+ renderMap,
3851
+ sdk,
3852
+ useIsMocked,
3853
+ useLiveRouting,
3854
+ useMapControls,
3855
+ useRoute,
3856
+ useTrimmedRoute,
3857
+ useUserLocation
3858
+ });
3859
+ //# sourceMappingURL=index.cjs.map