@cloudparse/up-miniapps-sdk 0.1.5 → 0.2.0

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