@retor/react-native 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,14 +6,16 @@ Embed Retor 3D experiences in a React Native / Expo app — with composable bott
6
6
 
7
7
  ```bash
8
8
  # Expo
9
- npx expo install react-native-webview react-native-gesture-handler react-native-reanimated react-native-svg
9
+ npx expo install react-native-webview react-native-gesture-handler react-native-reanimated react-native-svg expo-blur
10
10
  npm install @gorhom/bottom-sheet lucide-react-native @retor/react-native
11
11
 
12
12
  # bare React Native
13
- npm install react-native-webview react-native-gesture-handler react-native-reanimated react-native-svg @gorhom/bottom-sheet lucide-react-native @retor/react-native
13
+ npm install react-native-webview react-native-gesture-handler react-native-reanimated react-native-svg @gorhom/bottom-sheet lucide-react-native expo-blur @retor/react-native
14
14
  cd ios && pod install
15
15
  ```
16
16
 
17
+ > `expo-blur` is optional — it's used for the default blurred sheet background. Skip it if you don't want blur and pass a custom `backgroundComponent` to any sheet.
18
+
17
19
  You also need to wrap your app root in a `GestureHandlerRootView` (per `@gorhom/bottom-sheet` requirements):
18
20
 
19
21
  ```tsx
package/dist/index.d.mts CHANGED
@@ -214,15 +214,19 @@ interface LineDetailSheetProps {
214
214
  /** Custom content (typically a `<LineTagList>`). */
215
215
  children?: React.ReactNode;
216
216
  }
217
- /**
218
- * Sheet shown when a line is open. Includes a default header with the
219
- * autoplay button + minimize arrow, the tag list, and a Done footer.
220
- */
221
217
  declare function LineDetailSheet({ snapPoints, renderHeader, children }: LineDetailSheetProps): React.JSX.Element;
222
218
  interface LineTagListProps {
223
219
  children: (tag: RetorTag, isActive: boolean) => React.ReactNode;
220
+ /** Optional header rendered above the list (used internally for the default header). */
221
+ listHeader?: React.ReactNode;
224
222
  }
225
- declare function LineTagList({ children }: LineTagListProps): React.JSX.Element | null;
223
+ /**
224
+ * Renders the tags of the active line as a vertical list inside a
225
+ * `BottomSheetScrollView`. Tracks each item's Y position via `onLayout`
226
+ * and scrolls the closest tag to the top of the visible area as the
227
+ * camera scrolls.
228
+ */
229
+ declare function LineTagList({ children, listHeader }: LineTagListProps): React.JSX.Element | null;
226
230
 
227
231
  interface AddNoteSheetProps {
228
232
  /** Snap points. Defaults to ["50%"]. */
@@ -261,4 +265,13 @@ interface CoverPhotoProps {
261
265
  */
262
266
  declare function CoverPhoto({ projectId, baseUrl, style }: CoverPhotoProps): React.JSX.Element;
263
267
 
264
- export { type AddNoteFormApi, AddNoteSheet, type AddNoteSheetProps, CoverPhoto, type CoverPhotoProps, Hud, type HudProps, type InitPayload, LineDetailSheet, type LineDetailSheetProps, type LineProgressPayload, LineTagList, type LineTagListProps, LinesCarousel, type LinesCarouselProps, type NoteSubmitPayload, Notes, type NotesProps, ProjectSheet, type ProjectSheetProps, type RetorBridgeContextValue, type RetorLine, type RetorProject, type RetorTag, Viewer, type ViewerHandle, type ViewerProps, useActiveLine, useAddNote, useAutoplay, useLineProgress, useLines, useProject, useRetorBridge, useViewer };
268
+ /**
269
+ * Default background component for the SDK's bottom sheets.
270
+ * Renders an iOS-style blur with a dark tint overlay so the 3D scene
271
+ * remains visible behind the sheet.
272
+ */
273
+ declare function BlurBackground({ style }: {
274
+ style?: StyleProp<ViewStyle>;
275
+ }): React.JSX.Element;
276
+
277
+ export { type AddNoteFormApi, AddNoteSheet, type AddNoteSheetProps, BlurBackground, CoverPhoto, type CoverPhotoProps, Hud, type HudProps, type InitPayload, LineDetailSheet, type LineDetailSheetProps, type LineProgressPayload, LineTagList, type LineTagListProps, LinesCarousel, type LinesCarouselProps, type NoteSubmitPayload, Notes, type NotesProps, ProjectSheet, type ProjectSheetProps, type RetorBridgeContextValue, type RetorLine, type RetorProject, type RetorTag, Viewer, type ViewerHandle, type ViewerProps, useActiveLine, useAddNote, useAutoplay, useLineProgress, useLines, useProject, useRetorBridge, useViewer };
package/dist/index.d.ts CHANGED
@@ -214,15 +214,19 @@ interface LineDetailSheetProps {
214
214
  /** Custom content (typically a `<LineTagList>`). */
215
215
  children?: React.ReactNode;
216
216
  }
217
- /**
218
- * Sheet shown when a line is open. Includes a default header with the
219
- * autoplay button + minimize arrow, the tag list, and a Done footer.
220
- */
221
217
  declare function LineDetailSheet({ snapPoints, renderHeader, children }: LineDetailSheetProps): React.JSX.Element;
222
218
  interface LineTagListProps {
223
219
  children: (tag: RetorTag, isActive: boolean) => React.ReactNode;
220
+ /** Optional header rendered above the list (used internally for the default header). */
221
+ listHeader?: React.ReactNode;
224
222
  }
225
- declare function LineTagList({ children }: LineTagListProps): React.JSX.Element | null;
223
+ /**
224
+ * Renders the tags of the active line as a vertical list inside a
225
+ * `BottomSheetScrollView`. Tracks each item's Y position via `onLayout`
226
+ * and scrolls the closest tag to the top of the visible area as the
227
+ * camera scrolls.
228
+ */
229
+ declare function LineTagList({ children, listHeader }: LineTagListProps): React.JSX.Element | null;
226
230
 
227
231
  interface AddNoteSheetProps {
228
232
  /** Snap points. Defaults to ["50%"]. */
@@ -261,4 +265,13 @@ interface CoverPhotoProps {
261
265
  */
262
266
  declare function CoverPhoto({ projectId, baseUrl, style }: CoverPhotoProps): React.JSX.Element;
263
267
 
264
- export { type AddNoteFormApi, AddNoteSheet, type AddNoteSheetProps, CoverPhoto, type CoverPhotoProps, Hud, type HudProps, type InitPayload, LineDetailSheet, type LineDetailSheetProps, type LineProgressPayload, LineTagList, type LineTagListProps, LinesCarousel, type LinesCarouselProps, type NoteSubmitPayload, Notes, type NotesProps, ProjectSheet, type ProjectSheetProps, type RetorBridgeContextValue, type RetorLine, type RetorProject, type RetorTag, Viewer, type ViewerHandle, type ViewerProps, useActiveLine, useAddNote, useAutoplay, useLineProgress, useLines, useProject, useRetorBridge, useViewer };
268
+ /**
269
+ * Default background component for the SDK's bottom sheets.
270
+ * Renders an iOS-style blur with a dark tint overlay so the 3D scene
271
+ * remains visible behind the sheet.
272
+ */
273
+ declare function BlurBackground({ style }: {
274
+ style?: StyleProp<ViewStyle>;
275
+ }): React.JSX.Element;
276
+
277
+ export { type AddNoteFormApi, AddNoteSheet, type AddNoteSheetProps, BlurBackground, CoverPhoto, type CoverPhotoProps, Hud, type HudProps, type InitPayload, LineDetailSheet, type LineDetailSheetProps, type LineProgressPayload, LineTagList, type LineTagListProps, LinesCarousel, type LinesCarouselProps, type NoteSubmitPayload, Notes, type NotesProps, ProjectSheet, type ProjectSheetProps, type RetorBridgeContextValue, type RetorLine, type RetorProject, type RetorTag, Viewer, type ViewerHandle, type ViewerProps, useActiveLine, useAddNote, useAutoplay, useLineProgress, useLines, useProject, useRetorBridge, useViewer };
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AddNoteSheet: () => AddNoteSheet,
34
+ BlurBackground: () => BlurBackground,
34
35
  CoverPhoto: () => CoverPhoto,
35
36
  Hud: () => Hud,
36
37
  LineDetailSheet: () => LineDetailSheet,
@@ -216,14 +217,10 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
216
217
  openLine: (lineId) => send("open-line", { lineId }),
217
218
  exitLine: () => send("exit-line"),
218
219
  scrollToTag: (tagId) => send("scroll-to-tag", { tagId }),
219
- toggleAutoplay: () => {
220
- setIsPlaying((v) => !v);
221
- send("toggle-autoplay");
222
- },
223
- setAutoplay: (playing) => {
224
- setIsPlaying(playing);
225
- send("set-autoplay", { playing });
226
- }
220
+ // Bridge owns autoplay state — it'll emit `autoplay-state` back, which
221
+ // we use to update isPlaying. No optimistic local update.
222
+ toggleAutoplay: () => send("toggle-autoplay"),
223
+ setAutoplay: (playing) => send("set-autoplay", { playing })
227
224
  }),
228
225
  [send]
229
226
  );
@@ -298,6 +295,11 @@ var Viewer = (0, import_react2.forwardRef)(function Viewer2({ projectId, id = "d
298
295
  onLineProgress?.(payload);
299
296
  break;
300
297
  }
298
+ case "autoplay-state": {
299
+ const payload = data.payload;
300
+ setIsPlaying(!!payload.playing);
301
+ break;
302
+ }
301
303
  default:
302
304
  onMessage?.(data.type, data.payload);
303
305
  }
@@ -356,23 +358,37 @@ function Hud({ children }) {
356
358
  }
357
359
 
358
360
  // src/ProjectSheet.tsx
359
- var import_react4 = __toESM(require("react"));
360
- var import_react_native3 = require("react-native");
361
+ var import_react5 = __toESM(require("react"));
362
+ var import_react_native4 = require("react-native");
361
363
  var import_bottom_sheet2 = require("@gorhom/bottom-sheet");
362
364
  var import_lucide_react_native = require("lucide-react-native");
365
+
366
+ // src/BlurBackground.tsx
367
+ var import_react4 = __toESM(require("react"));
368
+ var import_react_native3 = require("react-native");
369
+ var import_expo_blur = require("expo-blur");
370
+ function BlurBackground({ style }) {
371
+ return /* @__PURE__ */ import_react4.default.createElement(import_react_native3.View, { style: [style, import_react_native3.StyleSheet.absoluteFill, { overflow: "hidden", borderTopLeftRadius: 24, borderTopRightRadius: 24 }] }, /* @__PURE__ */ import_react4.default.createElement(import_expo_blur.BlurView, { intensity: 60, tint: "dark", style: import_react_native3.StyleSheet.absoluteFill }), /* @__PURE__ */ import_react4.default.createElement(import_react_native3.View, { style: [import_react_native3.StyleSheet.absoluteFill, { backgroundColor: "rgba(20,20,20,0.55)" }] }));
372
+ }
373
+
374
+ // src/ProjectSheet.tsx
363
375
  function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
364
376
  const { project, activeLineId, isAddNoteOpen } = useRetorBridge();
365
- const sheetRef = (0, import_react4.useRef)(null);
366
- const snapPointsArr = (0, import_react4.useMemo)(() => snapPoints, [snapPoints]);
367
- const [minimized, setMinimized] = (0, import_react4.useState)(false);
368
- (0, import_react4.useEffect)(() => {
377
+ const sheetRef = (0, import_react5.useRef)(null);
378
+ const snapPointsArr = (0, import_react5.useMemo)(() => snapPoints, [snapPoints]);
379
+ const [minimized, setMinimized] = (0, import_react5.useState)(false);
380
+ (0, import_react5.useEffect)(() => {
369
381
  if (activeLineId || isAddNoteOpen) {
370
382
  sheetRef.current?.dismiss();
371
- } else {
383
+ return;
384
+ }
385
+ const t = setTimeout(() => {
372
386
  sheetRef.current?.present();
387
+ sheetRef.current?.snapToIndex(snapPointsArr.length - 1);
373
388
  setMinimized(false);
374
- }
375
- }, [activeLineId, isAddNoteOpen]);
389
+ }, 80);
390
+ return () => clearTimeout(t);
391
+ }, [activeLineId, isAddNoteOpen, snapPointsArr.length]);
376
392
  const handleSheetChange = (index) => {
377
393
  setMinimized(index === 0);
378
394
  };
@@ -383,7 +399,7 @@ function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
383
399
  sheetRef.current?.snapToIndex(0);
384
400
  }
385
401
  };
386
- return /* @__PURE__ */ import_react4.default.createElement(
402
+ return /* @__PURE__ */ import_react5.default.createElement(
387
403
  import_bottom_sheet2.BottomSheetModal,
388
404
  {
389
405
  ref: sheetRef,
@@ -391,7 +407,7 @@ function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
391
407
  enablePanDownToClose: false,
392
408
  enableDismissOnClose: false,
393
409
  onChange: handleSheetChange,
394
- backdropComponent: (props) => /* @__PURE__ */ import_react4.default.createElement(
410
+ backdropComponent: (props) => /* @__PURE__ */ import_react5.default.createElement(
395
411
  import_bottom_sheet2.BottomSheetBackdrop,
396
412
  {
397
413
  ...props,
@@ -402,41 +418,40 @@ function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
402
418
  }
403
419
  ),
404
420
  handleIndicatorStyle: styles2.handle,
405
- backgroundStyle: styles2.background
421
+ backgroundComponent: BlurBackground
406
422
  },
407
- /* @__PURE__ */ import_react4.default.createElement(import_bottom_sheet2.BottomSheetView, { style: styles2.content }, renderHeader ? renderHeader({ name: project?.name, description: project?.description }) : /* @__PURE__ */ import_react4.default.createElement(import_react_native3.View, { style: styles2.header }, /* @__PURE__ */ import_react4.default.createElement(import_react_native3.View, { style: { flex: 1, minWidth: 0 } }, project?.name && /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Text, { style: styles2.title, numberOfLines: 1 }, project.name), project?.description && /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Text, { style: styles2.subtitle, numberOfLines: 4 }, project.description)), /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Pressable, { style: styles2.iconBtn, onPress: toggleMinimize }, minimized ? /* @__PURE__ */ import_react4.default.createElement(import_lucide_react_native.ArrowUp, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ import_react4.default.createElement(import_lucide_react_native.ArrowDown, { size: 14, color: "rgba(255,255,255,0.6)" }))), children ?? /* @__PURE__ */ import_react4.default.createElement(DefaultLinesCarousel, null))
423
+ /* @__PURE__ */ import_react5.default.createElement(import_bottom_sheet2.BottomSheetView, { style: styles2.content }, renderHeader ? renderHeader({ name: project?.name, description: project?.description }) : /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles2.header }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: { flex: 1, minWidth: 0 } }, project?.name && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles2.title, numberOfLines: 1 }, project.name), project?.description && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles2.subtitle, numberOfLines: 4 }, project.description)), /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Pressable, { style: styles2.iconBtn, onPress: toggleMinimize }, minimized ? /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native.ArrowUp, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native.ArrowDown, { size: 14, color: "rgba(255,255,255,0.6)" }))), children ?? /* @__PURE__ */ import_react5.default.createElement(DefaultLinesCarousel, null))
408
424
  );
409
425
  }
410
426
  function DefaultLinesCarousel() {
411
- return /* @__PURE__ */ import_react4.default.createElement(LinesCarousel, null, (line) => /* @__PURE__ */ import_react4.default.createElement(DefaultLineCard, { line }));
427
+ return /* @__PURE__ */ import_react5.default.createElement(LinesCarousel, null, (line) => /* @__PURE__ */ import_react5.default.createElement(DefaultLineCard, { line }));
412
428
  }
413
429
  function LinesCarousel({ children, gap = 12, paddingHorizontal = 16 }) {
414
430
  const { lines } = useRetorBridge();
415
431
  if (lines.length === 0) return null;
416
- return /* @__PURE__ */ import_react4.default.createElement(import_react_native3.View, { style: styles2.carouselWrap }, /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Text, { style: styles2.carouselLabel }, "Routes"), /* @__PURE__ */ import_react4.default.createElement(
417
- import_react_native3.ScrollView,
432
+ return /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles2.carouselWrap }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles2.carouselLabel }, "Routes"), /* @__PURE__ */ import_react5.default.createElement(
433
+ import_react_native4.ScrollView,
418
434
  {
419
435
  horizontal: true,
420
436
  showsHorizontalScrollIndicator: false,
421
437
  contentContainerStyle: { paddingHorizontal, gap }
422
438
  },
423
- lines.map((line, idx) => /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, { key: line._id }, children(line, idx)))
439
+ lines.map((line, idx) => /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, { key: line._id }, children(line, idx)))
424
440
  ));
425
441
  }
426
442
  function DefaultLineCard({ line }) {
427
443
  const { controls } = useRetorBridge();
428
- return /* @__PURE__ */ import_react4.default.createElement(
429
- import_react_native3.Pressable,
444
+ return /* @__PURE__ */ import_react5.default.createElement(
445
+ import_react_native4.Pressable,
430
446
  {
431
447
  onPress: () => controls.openLine(line._id),
432
448
  style: styles2.lineCard
433
449
  },
434
- /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Text, { style: styles2.lineCardTitle, numberOfLines: 1 }, line.name || "Line"),
435
- line.subtitle && /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Text, { style: styles2.lineCardSubtitle, numberOfLines: 2 }, line.subtitle)
450
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles2.lineCardTitle, numberOfLines: 1 }, line.name || "Line"),
451
+ line.subtitle && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles2.lineCardSubtitle, numberOfLines: 2 }, line.subtitle)
436
452
  );
437
453
  }
438
- var styles2 = import_react_native3.StyleSheet.create({
439
- background: { backgroundColor: "rgba(20,20,20,0.95)" },
454
+ var styles2 = import_react_native4.StyleSheet.create({
440
455
  handle: { backgroundColor: "rgba(255,255,255,0.3)" },
441
456
  content: { flex: 1, paddingTop: 12, paddingBottom: 24 },
442
457
  header: {
@@ -477,24 +492,28 @@ var styles2 = import_react_native3.StyleSheet.create({
477
492
  });
478
493
 
479
494
  // src/LineDetailSheet.tsx
480
- var import_react5 = __toESM(require("react"));
481
- var import_react_native4 = require("react-native");
495
+ var import_react6 = __toESM(require("react"));
496
+ var import_react_native5 = require("react-native");
482
497
  var import_bottom_sheet3 = require("@gorhom/bottom-sheet");
483
498
  var import_react_native_svg = __toESM(require("react-native-svg"));
484
499
  var import_lucide_react_native2 = require("lucide-react-native");
485
500
  function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
486
501
  const { activeLine, isAddNoteOpen, controls } = useRetorBridge();
487
- const sheetRef = (0, import_react5.useRef)(null);
488
- const snapPointsArr = (0, import_react5.useMemo)(() => snapPoints, [snapPoints]);
489
- const [minimized, setMinimized] = (0, import_react5.useState)(false);
490
- (0, import_react5.useEffect)(() => {
491
- if (activeLine && !isAddNoteOpen) {
492
- sheetRef.current?.present();
493
- setMinimized(false);
494
- } else {
502
+ const sheetRef = (0, import_react6.useRef)(null);
503
+ const snapPointsArr = (0, import_react6.useMemo)(() => snapPoints, [snapPoints]);
504
+ const [minimized, setMinimized] = (0, import_react6.useState)(false);
505
+ (0, import_react6.useEffect)(() => {
506
+ if (!activeLine || isAddNoteOpen) {
495
507
  sheetRef.current?.dismiss();
508
+ return;
496
509
  }
497
- }, [activeLine, isAddNoteOpen]);
510
+ const t = setTimeout(() => {
511
+ sheetRef.current?.present();
512
+ sheetRef.current?.snapToIndex(snapPointsArr.length - 1);
513
+ setMinimized(false);
514
+ }, 80);
515
+ return () => clearTimeout(t);
516
+ }, [activeLine, isAddNoteOpen, snapPointsArr.length]);
498
517
  const handleSheetChange = (index) => {
499
518
  setMinimized(index === 0);
500
519
  };
@@ -505,7 +524,12 @@ function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }
505
524
  sheetRef.current?.snapToIndex(0);
506
525
  }
507
526
  };
508
- return /* @__PURE__ */ import_react5.default.createElement(
527
+ const renderFooter = (0, import_react6.useCallback)(
528
+ (props) => /* @__PURE__ */ import_react6.default.createElement(import_bottom_sheet3.BottomSheetFooter, { ...props, bottomInset: 0 }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles3.footer }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Pressable, { style: styles3.doneButton, onPress: () => controls.exitLine() }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.doneText }, "Done")))),
529
+ [controls]
530
+ );
531
+ const header = activeLine ? renderHeader ? renderHeader(activeLine) : /* @__PURE__ */ import_react6.default.createElement(DefaultHeader, { line: activeLine, minimized, onToggleMinimize: toggleMinimize }) : null;
532
+ return /* @__PURE__ */ import_react6.default.createElement(
509
533
  import_bottom_sheet3.BottomSheetModal,
510
534
  {
511
535
  ref: sheetRef,
@@ -513,7 +537,8 @@ function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }
513
537
  enablePanDownToClose: false,
514
538
  enableDismissOnClose: false,
515
539
  onChange: handleSheetChange,
516
- backdropComponent: (props) => /* @__PURE__ */ import_react5.default.createElement(
540
+ footerComponent: renderFooter,
541
+ backdropComponent: (props) => /* @__PURE__ */ import_react6.default.createElement(
517
542
  import_bottom_sheet3.BottomSheetBackdrop,
518
543
  {
519
544
  ...props,
@@ -524,16 +549,9 @@ function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }
524
549
  }
525
550
  ),
526
551
  handleIndicatorStyle: styles3.handle,
527
- backgroundStyle: styles3.background
552
+ backgroundComponent: BlurBackground
528
553
  },
529
- activeLine && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.root }, renderHeader ? renderHeader(activeLine) : /* @__PURE__ */ import_react5.default.createElement(
530
- DefaultHeader,
531
- {
532
- line: activeLine,
533
- minimized,
534
- onToggleMinimize: toggleMinimize
535
- }
536
- ), /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.listContainer }, children ?? /* @__PURE__ */ import_react5.default.createElement(DefaultLineTagList, null)), /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.footer }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Pressable, { style: styles3.doneButton, onPress: () => controls.exitLine() }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.doneText }, "Done"))))
554
+ activeLine && (children ?? /* @__PURE__ */ import_react6.default.createElement(DefaultLineTagList, { listHeader: header }))
537
555
  );
538
556
  }
539
557
  function DefaultHeader({
@@ -541,13 +559,13 @@ function DefaultHeader({
541
559
  minimized,
542
560
  onToggleMinimize
543
561
  }) {
544
- return /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.header }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.title, numberOfLines: 1 }, line.name), line.subtitle && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.subtitle, numberOfLines: 1 }, line.subtitle), line.description && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.description, numberOfLines: minimized ? 2 : 4 }, line.description)), /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.headerActions }, /* @__PURE__ */ import_react5.default.createElement(AutoplayButton, null), /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Pressable, { style: styles3.iconBtn, onPress: onToggleMinimize }, minimized ? /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native2.ArrowUp, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native2.ArrowDown, { size: 14, color: "rgba(255,255,255,0.6)" }))));
562
+ return /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles3.header }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.title, numberOfLines: 1 }, line.name), line.subtitle && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.subtitle, numberOfLines: 1 }, line.subtitle), line.description && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.description, numberOfLines: minimized ? 2 : 4 }, line.description)), /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles3.headerActions }, /* @__PURE__ */ import_react6.default.createElement(AutoplayButton, null), /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Pressable, { style: styles3.iconBtn, onPress: onToggleMinimize }, minimized ? /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native2.ArrowUp, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native2.ArrowDown, { size: 14, color: "rgba(255,255,255,0.6)" }))));
545
563
  }
546
564
  function AutoplayButton() {
547
565
  const { isPlaying, progress, controls } = useRetorBridge();
548
566
  const r = 12.5;
549
567
  const c = 2 * Math.PI * r;
550
- return /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Pressable, { style: styles3.iconBtn, onPress: () => controls.toggleAutoplay() }, /* @__PURE__ */ import_react5.default.createElement(import_react_native_svg.default, { width: 28, height: 28, style: import_react_native4.StyleSheet.absoluteFill }, /* @__PURE__ */ import_react5.default.createElement(
568
+ return /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Pressable, { style: styles3.iconBtn, onPress: () => controls.toggleAutoplay() }, /* @__PURE__ */ import_react6.default.createElement(import_react_native_svg.default, { width: 28, height: 28, style: import_react_native5.StyleSheet.absoluteFill }, /* @__PURE__ */ import_react6.default.createElement(
551
569
  import_react_native_svg.Circle,
552
570
  {
553
571
  cx: 14,
@@ -561,59 +579,58 @@ function AutoplayButton() {
561
579
  strokeLinecap: "round",
562
580
  transform: "rotate(-90, 14, 14)"
563
581
  }
564
- )), isPlaying ? /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native2.Pause, { size: 11, color: "white", fill: "white" }) : /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native2.Play, { size: 11, color: "white", fill: "white", style: { marginLeft: 1 } }));
582
+ )), isPlaying ? /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native2.Pause, { size: 11, color: "white", fill: "white" }) : /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native2.Play, { size: 11, color: "white", fill: "white", style: { marginLeft: 1 } }));
565
583
  }
566
- function LineTagList({ children }) {
584
+ function LineTagList({ children, listHeader }) {
567
585
  const { activeLine, closestTagId } = useRetorBridge();
568
- const listRef = (0, import_react5.useRef)(null);
569
- const tags = (0, import_react5.useMemo)(
586
+ const scrollRef = (0, import_react6.useRef)(null);
587
+ const offsetsRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
588
+ const tags = (0, import_react6.useMemo)(
570
589
  () => (activeLine?.tags ?? []).filter((t) => t.name && t.name.trim().length > 0),
571
590
  [activeLine]
572
591
  );
573
- (0, import_react5.useEffect)(() => {
592
+ (0, import_react6.useEffect)(() => {
593
+ offsetsRef.current = /* @__PURE__ */ new Map();
594
+ }, [activeLine?._id]);
595
+ (0, import_react6.useEffect)(() => {
574
596
  if (!closestTagId) return;
575
- const index = tags.findIndex((t) => t._id === closestTagId);
576
- if (index < 0) return;
577
- requestAnimationFrame(() => {
578
- try {
579
- listRef.current?.scrollToIndex({ index, animated: true, viewPosition: 0 });
580
- } catch {
597
+ const t = setTimeout(() => {
598
+ const y = offsetsRef.current.get(closestTagId);
599
+ if (y != null) {
600
+ scrollRef.current?.scrollTo({ y, animated: true });
581
601
  }
582
- });
583
- }, [closestTagId, tags]);
602
+ }, 60);
603
+ return () => clearTimeout(t);
604
+ }, [closestTagId]);
584
605
  if (!activeLine) return null;
585
- return /* @__PURE__ */ import_react5.default.createElement(
586
- import_bottom_sheet3.BottomSheetFlatList,
606
+ const handleItemLayout = (id, e) => {
607
+ offsetsRef.current.set(id, e.nativeEvent.layout.y);
608
+ };
609
+ return /* @__PURE__ */ import_react6.default.createElement(
610
+ import_bottom_sheet3.BottomSheetScrollView,
587
611
  {
588
- ref: listRef,
589
- data: tags,
590
- keyExtractor: (t) => t._id,
591
- renderItem: ({ item }) => /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, null, children(item, item._id === closestTagId)),
592
- contentContainerStyle: styles3.list,
593
- onScrollToIndexFailed: (info) => {
594
- setTimeout(() => {
595
- listRef.current?.scrollToOffset({ offset: info.averageItemLength * info.index, animated: true });
596
- }, 100);
597
- }
598
- }
612
+ ref: scrollRef,
613
+ contentContainerStyle: styles3.list
614
+ },
615
+ listHeader,
616
+ tags.map((tag) => /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { key: tag._id, onLayout: (e) => handleItemLayout(tag._id, e) }, children(tag, tag._id === closestTagId)))
599
617
  );
600
618
  }
601
- function DefaultLineTagList() {
602
- return /* @__PURE__ */ import_react5.default.createElement(LineTagList, null, (tag, isActive) => /* @__PURE__ */ import_react5.default.createElement(DefaultTagItem, { tag, isActive }));
619
+ function DefaultLineTagList({ listHeader }) {
620
+ return /* @__PURE__ */ import_react6.default.createElement(LineTagList, { listHeader }, (tag, isActive) => /* @__PURE__ */ import_react6.default.createElement(DefaultTagItem, { tag, isActive }));
603
621
  }
604
622
  function DefaultTagItem({ tag, isActive }) {
605
- const { controls, openAddNote, activeLine } = useRetorBridge();
606
- const showPlus = isActive && (activeLine?.notesSupported ?? false);
607
- return /* @__PURE__ */ import_react5.default.createElement(
608
- import_react_native4.Pressable,
623
+ const { controls, openAddNote } = useRetorBridge();
624
+ return /* @__PURE__ */ import_react6.default.createElement(
625
+ import_react_native5.Pressable,
609
626
  {
610
627
  onPress: () => controls.scrollToTag(tag._id),
611
628
  style: [styles3.tagItem, isActive && styles3.tagItemActive]
612
629
  },
613
- /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: [styles3.tagText, isActive && styles3.tagTextActive], numberOfLines: 1 }, tag.name), isActive && tag.description && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.tagDescription, numberOfLines: 2 }, tag.description)),
614
- tag.subtitle && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.tagSubtitle, numberOfLines: 1 }, tag.subtitle),
615
- showPlus && /* @__PURE__ */ import_react5.default.createElement(
616
- import_react_native4.Pressable,
630
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: [styles3.tagText, isActive && styles3.tagTextActive], numberOfLines: 1 }, tag.name), isActive && tag.description && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.tagDescription, numberOfLines: 2 }, tag.description)),
631
+ tag.subtitle && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles3.tagSubtitle, numberOfLines: 1 }, tag.subtitle),
632
+ isActive && /* @__PURE__ */ import_react6.default.createElement(
633
+ import_react_native5.Pressable,
617
634
  {
618
635
  onPress: (e) => {
619
636
  e.stopPropagation();
@@ -621,20 +638,18 @@ function DefaultTagItem({ tag, isActive }) {
621
638
  },
622
639
  style: styles3.plusBtn
623
640
  },
624
- /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native2.Plus, { size: 14, color: "white" })
641
+ /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native2.Plus, { size: 14, color: "white" })
625
642
  )
626
643
  );
627
644
  }
628
- var styles3 = import_react_native4.StyleSheet.create({
629
- background: { backgroundColor: "rgba(20,20,20,0.95)" },
645
+ var styles3 = import_react_native5.StyleSheet.create({
630
646
  handle: { backgroundColor: "rgba(255,255,255,0.3)" },
631
- root: { flex: 1 },
632
647
  header: {
633
648
  flexDirection: "row",
634
649
  alignItems: "flex-start",
635
650
  paddingHorizontal: 24,
636
651
  paddingTop: 8,
637
- paddingBottom: 12,
652
+ paddingBottom: 16,
638
653
  gap: 8
639
654
  },
640
655
  headerActions: { flexDirection: "row", alignItems: "center", gap: 6 },
@@ -650,8 +665,7 @@ var styles3 = import_react_native4.StyleSheet.create({
650
665
  justifyContent: "center",
651
666
  position: "relative"
652
667
  },
653
- listContainer: { flex: 1 },
654
- list: { paddingHorizontal: 16, paddingBottom: 8, gap: 4 },
668
+ list: { paddingHorizontal: 16, paddingBottom: 96, gap: 4 },
655
669
  tagItem: {
656
670
  flexDirection: "row",
657
671
  alignItems: "center",
@@ -666,14 +680,19 @@ var styles3 = import_react_native4.StyleSheet.create({
666
680
  tagDescription: { color: "rgba(255,255,255,0.4)", fontSize: 11, marginTop: 2, lineHeight: 14 },
667
681
  tagSubtitle: { color: "rgba(255,255,255,0.4)", fontSize: 10 },
668
682
  plusBtn: {
669
- width: 22,
670
- height: 22,
671
- borderRadius: 11,
672
- backgroundColor: "rgba(255,255,255,0.15)",
683
+ width: 24,
684
+ height: 24,
685
+ borderRadius: 12,
686
+ backgroundColor: "rgba(255,255,255,0.2)",
673
687
  alignItems: "center",
674
688
  justifyContent: "center"
675
689
  },
676
- footer: { paddingHorizontal: 16, paddingBottom: 16, paddingTop: 8 },
690
+ footer: {
691
+ paddingHorizontal: 16,
692
+ paddingBottom: 24,
693
+ paddingTop: 8,
694
+ backgroundColor: "transparent"
695
+ },
677
696
  doneButton: {
678
697
  backgroundColor: "white",
679
698
  borderRadius: 14,
@@ -684,8 +703,8 @@ var styles3 = import_react_native4.StyleSheet.create({
684
703
  });
685
704
 
686
705
  // src/AddNoteSheet.tsx
687
- var import_react6 = __toESM(require("react"));
688
- var import_react_native5 = require("react-native");
706
+ var import_react7 = __toESM(require("react"));
707
+ var import_react_native6 = require("react-native");
689
708
  var import_bottom_sheet4 = require("@gorhom/bottom-sheet");
690
709
  var import_lucide_react_native3 = require("lucide-react-native");
691
710
  function AddNoteSheet({
@@ -695,11 +714,11 @@ function AddNoteSheet({
695
714
  renderForm
696
715
  }) {
697
716
  const { isAddNoteOpen, addNoteTagId, activeLine, closeAddNote, submitNote } = useRetorBridge();
698
- const sheetRef = (0, import_react6.useRef)(null);
699
- const snapPointsArr = (0, import_react6.useMemo)(() => snapPoints, [snapPoints]);
700
- const [text, setText] = (0, import_react6.useState)("");
701
- const [isPrivate, setPrivate] = (0, import_react6.useState)(true);
702
- (0, import_react6.useEffect)(() => {
717
+ const sheetRef = (0, import_react7.useRef)(null);
718
+ const snapPointsArr = (0, import_react7.useMemo)(() => snapPoints, [snapPoints]);
719
+ const [text, setText] = (0, import_react7.useState)("");
720
+ const [isPrivate, setPrivate] = (0, import_react7.useState)(true);
721
+ (0, import_react7.useEffect)(() => {
703
722
  if (isAddNoteOpen) {
704
723
  sheetRef.current?.present();
705
724
  setText("");
@@ -708,7 +727,7 @@ function AddNoteSheet({
708
727
  sheetRef.current?.dismiss();
709
728
  }
710
729
  }, [isAddNoteOpen]);
711
- const tag = (0, import_react6.useMemo)(
730
+ const tag = (0, import_react7.useMemo)(
712
731
  () => activeLine?.tags.find((t) => t._id === addNoteTagId) ?? null,
713
732
  [activeLine, addNoteTagId]
714
733
  );
@@ -728,14 +747,14 @@ function AddNoteSheet({
728
747
  close: closeAddNote,
729
748
  maxLength
730
749
  };
731
- return /* @__PURE__ */ import_react6.default.createElement(
750
+ return /* @__PURE__ */ import_react7.default.createElement(
732
751
  import_bottom_sheet4.BottomSheetModal,
733
752
  {
734
753
  ref: sheetRef,
735
754
  snapPoints: snapPointsArr,
736
755
  enablePanDownToClose: true,
737
756
  onDismiss: closeAddNote,
738
- backdropComponent: (props) => /* @__PURE__ */ import_react6.default.createElement(
757
+ backdropComponent: (props) => /* @__PURE__ */ import_react7.default.createElement(
739
758
  import_bottom_sheet4.BottomSheetBackdrop,
740
759
  {
741
760
  ...props,
@@ -745,9 +764,9 @@ function AddNoteSheet({
745
764
  }
746
765
  ),
747
766
  handleIndicatorStyle: styles4.handle,
748
- backgroundStyle: styles4.background
767
+ backgroundComponent: BlurBackground
749
768
  },
750
- /* @__PURE__ */ import_react6.default.createElement(import_bottom_sheet4.BottomSheetView, { style: styles4.content }, renderForm ? renderForm(formApi) : /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles4.form }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles4.headerRow }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: { flex: 1 } }, activeLine && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles4.title, numberOfLines: 1 }, activeLine.name), tag && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles4.subtitle, numberOfLines: 1 }, tag.name)), /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Pressable, { onPress: closeAddNote, style: styles4.closeBtn }, /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native3.X, { size: 14, color: "rgba(255,255,255,0.6)" }))), /* @__PURE__ */ import_react6.default.createElement(
769
+ /* @__PURE__ */ import_react7.default.createElement(import_bottom_sheet4.BottomSheetView, { style: styles4.content }, renderForm ? renderForm(formApi) : /* @__PURE__ */ import_react7.default.createElement(import_react_native6.View, { style: styles4.form }, /* @__PURE__ */ import_react7.default.createElement(import_react_native6.View, { style: styles4.headerRow }, /* @__PURE__ */ import_react7.default.createElement(import_react_native6.View, { style: { flex: 1 } }, activeLine && /* @__PURE__ */ import_react7.default.createElement(import_react_native6.Text, { style: styles4.title, numberOfLines: 1 }, activeLine.name), tag && /* @__PURE__ */ import_react7.default.createElement(import_react_native6.Text, { style: styles4.subtitle, numberOfLines: 1 }, tag.name)), /* @__PURE__ */ import_react7.default.createElement(import_react_native6.Pressable, { onPress: closeAddNote, style: styles4.closeBtn }, /* @__PURE__ */ import_react7.default.createElement(import_lucide_react_native3.X, { size: 14, color: "rgba(255,255,255,0.6)" }))), /* @__PURE__ */ import_react7.default.createElement(
751
770
  import_bottom_sheet4.BottomSheetTextInput,
752
771
  {
753
772
  value: text,
@@ -760,26 +779,25 @@ function AddNoteSheet({
760
779
  style: styles4.input,
761
780
  autoFocus: true
762
781
  }
763
- ), /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles4.footer }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: [styles4.counter, text.length > maxLength * 0.9 && { color: "#fbbf24" }] }, text.length, "/", maxLength), /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: { flex: 1 } }), /* @__PURE__ */ import_react6.default.createElement(
764
- import_react_native5.Pressable,
782
+ ), /* @__PURE__ */ import_react7.default.createElement(import_react_native6.View, { style: styles4.footer }, /* @__PURE__ */ import_react7.default.createElement(import_react_native6.Text, { style: [styles4.counter, text.length > maxLength * 0.9 && { color: "#fbbf24" }] }, text.length, "/", maxLength), /* @__PURE__ */ import_react7.default.createElement(import_react_native6.View, { style: { flex: 1 } }), /* @__PURE__ */ import_react7.default.createElement(
783
+ import_react_native6.Pressable,
765
784
  {
766
785
  onPress: () => setPrivate(!isPrivate),
767
786
  style: [styles4.pill, !isPrivate && styles4.pillActive]
768
787
  },
769
- /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: [styles4.pillText, !isPrivate && styles4.pillTextActive] }, isPrivate ? "Private" : "Public")
770
- ), /* @__PURE__ */ import_react6.default.createElement(
771
- import_react_native5.Pressable,
788
+ /* @__PURE__ */ import_react7.default.createElement(import_react_native6.Text, { style: [styles4.pillText, !isPrivate && styles4.pillTextActive] }, isPrivate ? "Private" : "Public")
789
+ ), /* @__PURE__ */ import_react7.default.createElement(
790
+ import_react_native6.Pressable,
772
791
  {
773
792
  onPress: handleSubmit,
774
793
  disabled: !text.trim(),
775
794
  style: [styles4.submit, !text.trim() && styles4.submitDisabled]
776
795
  },
777
- /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native3.ArrowUp, { size: 16, color: "black", strokeWidth: 2.5 })
796
+ /* @__PURE__ */ import_react7.default.createElement(import_lucide_react_native3.ArrowUp, { size: 16, color: "black", strokeWidth: 2.5 })
778
797
  ))))
779
798
  );
780
799
  }
781
- var styles4 = import_react_native5.StyleSheet.create({
782
- background: { backgroundColor: "rgba(20,20,20,0.98)" },
800
+ var styles4 = import_react_native6.StyleSheet.create({
783
801
  handle: { backgroundColor: "rgba(255,255,255,0.3)" },
784
802
  content: { flex: 1, padding: 24 },
785
803
  form: { flex: 1 },
@@ -831,11 +849,11 @@ var styles4 = import_react_native5.StyleSheet.create({
831
849
  });
832
850
 
833
851
  // src/CoverPhoto.tsx
834
- var import_react7 = __toESM(require("react"));
852
+ var import_react8 = __toESM(require("react"));
835
853
  var import_react_native_webview2 = require("react-native-webview");
836
854
  function CoverPhoto({ projectId, baseUrl = "https://retor.app", style }) {
837
855
  const uri = `${baseUrl}/p/${projectId}?cover=true`;
838
- return /* @__PURE__ */ import_react7.default.createElement(
856
+ return /* @__PURE__ */ import_react8.default.createElement(
839
857
  import_react_native_webview2.WebView,
840
858
  {
841
859
  source: { uri },
@@ -850,6 +868,7 @@ function CoverPhoto({ projectId, baseUrl = "https://retor.app", style }) {
850
868
  // Annotate the CommonJS export names for ESM import in node:
851
869
  0 && (module.exports = {
852
870
  AddNoteSheet,
871
+ BlurBackground,
853
872
  CoverPhoto,
854
873
  Hud,
855
874
  LineDetailSheet,
package/dist/index.mjs CHANGED
@@ -173,14 +173,10 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
173
173
  openLine: (lineId) => send("open-line", { lineId }),
174
174
  exitLine: () => send("exit-line"),
175
175
  scrollToTag: (tagId) => send("scroll-to-tag", { tagId }),
176
- toggleAutoplay: () => {
177
- setIsPlaying((v) => !v);
178
- send("toggle-autoplay");
179
- },
180
- setAutoplay: (playing) => {
181
- setIsPlaying(playing);
182
- send("set-autoplay", { playing });
183
- }
176
+ // Bridge owns autoplay state — it'll emit `autoplay-state` back, which
177
+ // we use to update isPlaying. No optimistic local update.
178
+ toggleAutoplay: () => send("toggle-autoplay"),
179
+ setAutoplay: (playing) => send("set-autoplay", { playing })
184
180
  }),
185
181
  [send]
186
182
  );
@@ -255,6 +251,11 @@ var Viewer = forwardRef(function Viewer2({ projectId, id = "default", baseUrl =
255
251
  onLineProgress?.(payload);
256
252
  break;
257
253
  }
254
+ case "autoplay-state": {
255
+ const payload = data.payload;
256
+ setIsPlaying(!!payload.playing);
257
+ break;
258
+ }
258
259
  default:
259
260
  onMessage?.(data.type, data.payload);
260
261
  }
@@ -313,10 +314,20 @@ function Hud({ children }) {
313
314
  }
314
315
 
315
316
  // src/ProjectSheet.tsx
316
- import React4, { useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
317
- import { Pressable, ScrollView, StyleSheet as StyleSheet3, Text, View as View3 } from "react-native";
317
+ import React5, { useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
318
+ import { Pressable, ScrollView, StyleSheet as StyleSheet4, Text, View as View4 } from "react-native";
318
319
  import { BottomSheetBackdrop, BottomSheetModal, BottomSheetView } from "@gorhom/bottom-sheet";
319
320
  import { ArrowDown, ArrowUp } from "lucide-react-native";
321
+
322
+ // src/BlurBackground.tsx
323
+ import React4 from "react";
324
+ import { StyleSheet as StyleSheet3, View as View3 } from "react-native";
325
+ import { BlurView } from "expo-blur";
326
+ function BlurBackground({ style }) {
327
+ return /* @__PURE__ */ React4.createElement(View3, { style: [style, StyleSheet3.absoluteFill, { overflow: "hidden", borderTopLeftRadius: 24, borderTopRightRadius: 24 }] }, /* @__PURE__ */ React4.createElement(BlurView, { intensity: 60, tint: "dark", style: StyleSheet3.absoluteFill }), /* @__PURE__ */ React4.createElement(View3, { style: [StyleSheet3.absoluteFill, { backgroundColor: "rgba(20,20,20,0.55)" }] }));
328
+ }
329
+
330
+ // src/ProjectSheet.tsx
320
331
  function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
321
332
  const { project, activeLineId, isAddNoteOpen } = useRetorBridge();
322
333
  const sheetRef = useRef2(null);
@@ -325,11 +336,15 @@ function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
325
336
  useEffect2(() => {
326
337
  if (activeLineId || isAddNoteOpen) {
327
338
  sheetRef.current?.dismiss();
328
- } else {
339
+ return;
340
+ }
341
+ const t = setTimeout(() => {
329
342
  sheetRef.current?.present();
343
+ sheetRef.current?.snapToIndex(snapPointsArr.length - 1);
330
344
  setMinimized(false);
331
- }
332
- }, [activeLineId, isAddNoteOpen]);
345
+ }, 80);
346
+ return () => clearTimeout(t);
347
+ }, [activeLineId, isAddNoteOpen, snapPointsArr.length]);
333
348
  const handleSheetChange = (index) => {
334
349
  setMinimized(index === 0);
335
350
  };
@@ -340,7 +355,7 @@ function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
340
355
  sheetRef.current?.snapToIndex(0);
341
356
  }
342
357
  };
343
- return /* @__PURE__ */ React4.createElement(
358
+ return /* @__PURE__ */ React5.createElement(
344
359
  BottomSheetModal,
345
360
  {
346
361
  ref: sheetRef,
@@ -348,7 +363,7 @@ function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
348
363
  enablePanDownToClose: false,
349
364
  enableDismissOnClose: false,
350
365
  onChange: handleSheetChange,
351
- backdropComponent: (props) => /* @__PURE__ */ React4.createElement(
366
+ backdropComponent: (props) => /* @__PURE__ */ React5.createElement(
352
367
  BottomSheetBackdrop,
353
368
  {
354
369
  ...props,
@@ -359,41 +374,40 @@ function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
359
374
  }
360
375
  ),
361
376
  handleIndicatorStyle: styles2.handle,
362
- backgroundStyle: styles2.background
377
+ backgroundComponent: BlurBackground
363
378
  },
364
- /* @__PURE__ */ React4.createElement(BottomSheetView, { style: styles2.content }, renderHeader ? renderHeader({ name: project?.name, description: project?.description }) : /* @__PURE__ */ React4.createElement(View3, { style: styles2.header }, /* @__PURE__ */ React4.createElement(View3, { style: { flex: 1, minWidth: 0 } }, project?.name && /* @__PURE__ */ React4.createElement(Text, { style: styles2.title, numberOfLines: 1 }, project.name), project?.description && /* @__PURE__ */ React4.createElement(Text, { style: styles2.subtitle, numberOfLines: 4 }, project.description)), /* @__PURE__ */ React4.createElement(Pressable, { style: styles2.iconBtn, onPress: toggleMinimize }, minimized ? /* @__PURE__ */ React4.createElement(ArrowUp, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ React4.createElement(ArrowDown, { size: 14, color: "rgba(255,255,255,0.6)" }))), children ?? /* @__PURE__ */ React4.createElement(DefaultLinesCarousel, null))
379
+ /* @__PURE__ */ React5.createElement(BottomSheetView, { style: styles2.content }, renderHeader ? renderHeader({ name: project?.name, description: project?.description }) : /* @__PURE__ */ React5.createElement(View4, { style: styles2.header }, /* @__PURE__ */ React5.createElement(View4, { style: { flex: 1, minWidth: 0 } }, project?.name && /* @__PURE__ */ React5.createElement(Text, { style: styles2.title, numberOfLines: 1 }, project.name), project?.description && /* @__PURE__ */ React5.createElement(Text, { style: styles2.subtitle, numberOfLines: 4 }, project.description)), /* @__PURE__ */ React5.createElement(Pressable, { style: styles2.iconBtn, onPress: toggleMinimize }, minimized ? /* @__PURE__ */ React5.createElement(ArrowUp, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ React5.createElement(ArrowDown, { size: 14, color: "rgba(255,255,255,0.6)" }))), children ?? /* @__PURE__ */ React5.createElement(DefaultLinesCarousel, null))
365
380
  );
366
381
  }
367
382
  function DefaultLinesCarousel() {
368
- return /* @__PURE__ */ React4.createElement(LinesCarousel, null, (line) => /* @__PURE__ */ React4.createElement(DefaultLineCard, { line }));
383
+ return /* @__PURE__ */ React5.createElement(LinesCarousel, null, (line) => /* @__PURE__ */ React5.createElement(DefaultLineCard, { line }));
369
384
  }
370
385
  function LinesCarousel({ children, gap = 12, paddingHorizontal = 16 }) {
371
386
  const { lines } = useRetorBridge();
372
387
  if (lines.length === 0) return null;
373
- return /* @__PURE__ */ React4.createElement(View3, { style: styles2.carouselWrap }, /* @__PURE__ */ React4.createElement(Text, { style: styles2.carouselLabel }, "Routes"), /* @__PURE__ */ React4.createElement(
388
+ return /* @__PURE__ */ React5.createElement(View4, { style: styles2.carouselWrap }, /* @__PURE__ */ React5.createElement(Text, { style: styles2.carouselLabel }, "Routes"), /* @__PURE__ */ React5.createElement(
374
389
  ScrollView,
375
390
  {
376
391
  horizontal: true,
377
392
  showsHorizontalScrollIndicator: false,
378
393
  contentContainerStyle: { paddingHorizontal, gap }
379
394
  },
380
- lines.map((line, idx) => /* @__PURE__ */ React4.createElement(React4.Fragment, { key: line._id }, children(line, idx)))
395
+ lines.map((line, idx) => /* @__PURE__ */ React5.createElement(React5.Fragment, { key: line._id }, children(line, idx)))
381
396
  ));
382
397
  }
383
398
  function DefaultLineCard({ line }) {
384
399
  const { controls } = useRetorBridge();
385
- return /* @__PURE__ */ React4.createElement(
400
+ return /* @__PURE__ */ React5.createElement(
386
401
  Pressable,
387
402
  {
388
403
  onPress: () => controls.openLine(line._id),
389
404
  style: styles2.lineCard
390
405
  },
391
- /* @__PURE__ */ React4.createElement(Text, { style: styles2.lineCardTitle, numberOfLines: 1 }, line.name || "Line"),
392
- line.subtitle && /* @__PURE__ */ React4.createElement(Text, { style: styles2.lineCardSubtitle, numberOfLines: 2 }, line.subtitle)
406
+ /* @__PURE__ */ React5.createElement(Text, { style: styles2.lineCardTitle, numberOfLines: 1 }, line.name || "Line"),
407
+ line.subtitle && /* @__PURE__ */ React5.createElement(Text, { style: styles2.lineCardSubtitle, numberOfLines: 2 }, line.subtitle)
393
408
  );
394
409
  }
395
- var styles2 = StyleSheet3.create({
396
- background: { backgroundColor: "rgba(20,20,20,0.95)" },
410
+ var styles2 = StyleSheet4.create({
397
411
  handle: { backgroundColor: "rgba(255,255,255,0.3)" },
398
412
  content: { flex: 1, paddingTop: 12, paddingBottom: 24 },
399
413
  header: {
@@ -434,9 +448,14 @@ var styles2 = StyleSheet3.create({
434
448
  });
435
449
 
436
450
  // src/LineDetailSheet.tsx
437
- import React5, { useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
438
- import { Pressable as Pressable2, StyleSheet as StyleSheet4, Text as Text2, View as View4 } from "react-native";
439
- import { BottomSheetBackdrop as BottomSheetBackdrop2, BottomSheetFlatList, BottomSheetModal as BottomSheetModal2 } from "@gorhom/bottom-sheet";
451
+ import React6, { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
452
+ import { Pressable as Pressable2, StyleSheet as StyleSheet5, Text as Text2, View as View5 } from "react-native";
453
+ import {
454
+ BottomSheetBackdrop as BottomSheetBackdrop2,
455
+ BottomSheetFooter,
456
+ BottomSheetModal as BottomSheetModal2,
457
+ BottomSheetScrollView
458
+ } from "@gorhom/bottom-sheet";
440
459
  import Svg, { Circle } from "react-native-svg";
441
460
  import { ArrowDown as ArrowDown2, ArrowUp as ArrowUp2, Pause, Play, Plus } from "lucide-react-native";
442
461
  function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
@@ -445,13 +464,17 @@ function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }
445
464
  const snapPointsArr = useMemo4(() => snapPoints, [snapPoints]);
446
465
  const [minimized, setMinimized] = useState3(false);
447
466
  useEffect3(() => {
448
- if (activeLine && !isAddNoteOpen) {
449
- sheetRef.current?.present();
450
- setMinimized(false);
451
- } else {
467
+ if (!activeLine || isAddNoteOpen) {
452
468
  sheetRef.current?.dismiss();
469
+ return;
453
470
  }
454
- }, [activeLine, isAddNoteOpen]);
471
+ const t = setTimeout(() => {
472
+ sheetRef.current?.present();
473
+ sheetRef.current?.snapToIndex(snapPointsArr.length - 1);
474
+ setMinimized(false);
475
+ }, 80);
476
+ return () => clearTimeout(t);
477
+ }, [activeLine, isAddNoteOpen, snapPointsArr.length]);
455
478
  const handleSheetChange = (index) => {
456
479
  setMinimized(index === 0);
457
480
  };
@@ -462,7 +485,12 @@ function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }
462
485
  sheetRef.current?.snapToIndex(0);
463
486
  }
464
487
  };
465
- return /* @__PURE__ */ React5.createElement(
488
+ const renderFooter = useCallback2(
489
+ (props) => /* @__PURE__ */ React6.createElement(BottomSheetFooter, { ...props, bottomInset: 0 }, /* @__PURE__ */ React6.createElement(View5, { style: styles3.footer }, /* @__PURE__ */ React6.createElement(Pressable2, { style: styles3.doneButton, onPress: () => controls.exitLine() }, /* @__PURE__ */ React6.createElement(Text2, { style: styles3.doneText }, "Done")))),
490
+ [controls]
491
+ );
492
+ const header = activeLine ? renderHeader ? renderHeader(activeLine) : /* @__PURE__ */ React6.createElement(DefaultHeader, { line: activeLine, minimized, onToggleMinimize: toggleMinimize }) : null;
493
+ return /* @__PURE__ */ React6.createElement(
466
494
  BottomSheetModal2,
467
495
  {
468
496
  ref: sheetRef,
@@ -470,7 +498,8 @@ function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }
470
498
  enablePanDownToClose: false,
471
499
  enableDismissOnClose: false,
472
500
  onChange: handleSheetChange,
473
- backdropComponent: (props) => /* @__PURE__ */ React5.createElement(
501
+ footerComponent: renderFooter,
502
+ backdropComponent: (props) => /* @__PURE__ */ React6.createElement(
474
503
  BottomSheetBackdrop2,
475
504
  {
476
505
  ...props,
@@ -481,16 +510,9 @@ function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }
481
510
  }
482
511
  ),
483
512
  handleIndicatorStyle: styles3.handle,
484
- backgroundStyle: styles3.background
513
+ backgroundComponent: BlurBackground
485
514
  },
486
- activeLine && /* @__PURE__ */ React5.createElement(View4, { style: styles3.root }, renderHeader ? renderHeader(activeLine) : /* @__PURE__ */ React5.createElement(
487
- DefaultHeader,
488
- {
489
- line: activeLine,
490
- minimized,
491
- onToggleMinimize: toggleMinimize
492
- }
493
- ), /* @__PURE__ */ React5.createElement(View4, { style: styles3.listContainer }, children ?? /* @__PURE__ */ React5.createElement(DefaultLineTagList, null)), /* @__PURE__ */ React5.createElement(View4, { style: styles3.footer }, /* @__PURE__ */ React5.createElement(Pressable2, { style: styles3.doneButton, onPress: () => controls.exitLine() }, /* @__PURE__ */ React5.createElement(Text2, { style: styles3.doneText }, "Done"))))
515
+ activeLine && (children ?? /* @__PURE__ */ React6.createElement(DefaultLineTagList, { listHeader: header }))
494
516
  );
495
517
  }
496
518
  function DefaultHeader({
@@ -498,13 +520,13 @@ function DefaultHeader({
498
520
  minimized,
499
521
  onToggleMinimize
500
522
  }) {
501
- return /* @__PURE__ */ React5.createElement(View4, { style: styles3.header }, /* @__PURE__ */ React5.createElement(View4, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React5.createElement(Text2, { style: styles3.title, numberOfLines: 1 }, line.name), line.subtitle && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.subtitle, numberOfLines: 1 }, line.subtitle), line.description && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.description, numberOfLines: minimized ? 2 : 4 }, line.description)), /* @__PURE__ */ React5.createElement(View4, { style: styles3.headerActions }, /* @__PURE__ */ React5.createElement(AutoplayButton, null), /* @__PURE__ */ React5.createElement(Pressable2, { style: styles3.iconBtn, onPress: onToggleMinimize }, minimized ? /* @__PURE__ */ React5.createElement(ArrowUp2, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ React5.createElement(ArrowDown2, { size: 14, color: "rgba(255,255,255,0.6)" }))));
523
+ return /* @__PURE__ */ React6.createElement(View5, { style: styles3.header }, /* @__PURE__ */ React6.createElement(View5, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React6.createElement(Text2, { style: styles3.title, numberOfLines: 1 }, line.name), line.subtitle && /* @__PURE__ */ React6.createElement(Text2, { style: styles3.subtitle, numberOfLines: 1 }, line.subtitle), line.description && /* @__PURE__ */ React6.createElement(Text2, { style: styles3.description, numberOfLines: minimized ? 2 : 4 }, line.description)), /* @__PURE__ */ React6.createElement(View5, { style: styles3.headerActions }, /* @__PURE__ */ React6.createElement(AutoplayButton, null), /* @__PURE__ */ React6.createElement(Pressable2, { style: styles3.iconBtn, onPress: onToggleMinimize }, minimized ? /* @__PURE__ */ React6.createElement(ArrowUp2, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ React6.createElement(ArrowDown2, { size: 14, color: "rgba(255,255,255,0.6)" }))));
502
524
  }
503
525
  function AutoplayButton() {
504
526
  const { isPlaying, progress, controls } = useRetorBridge();
505
527
  const r = 12.5;
506
528
  const c = 2 * Math.PI * r;
507
- return /* @__PURE__ */ React5.createElement(Pressable2, { style: styles3.iconBtn, onPress: () => controls.toggleAutoplay() }, /* @__PURE__ */ React5.createElement(Svg, { width: 28, height: 28, style: StyleSheet4.absoluteFill }, /* @__PURE__ */ React5.createElement(
529
+ return /* @__PURE__ */ React6.createElement(Pressable2, { style: styles3.iconBtn, onPress: () => controls.toggleAutoplay() }, /* @__PURE__ */ React6.createElement(Svg, { width: 28, height: 28, style: StyleSheet5.absoluteFill }, /* @__PURE__ */ React6.createElement(
508
530
  Circle,
509
531
  {
510
532
  cx: 14,
@@ -518,58 +540,57 @@ function AutoplayButton() {
518
540
  strokeLinecap: "round",
519
541
  transform: "rotate(-90, 14, 14)"
520
542
  }
521
- )), isPlaying ? /* @__PURE__ */ React5.createElement(Pause, { size: 11, color: "white", fill: "white" }) : /* @__PURE__ */ React5.createElement(Play, { size: 11, color: "white", fill: "white", style: { marginLeft: 1 } }));
543
+ )), isPlaying ? /* @__PURE__ */ React6.createElement(Pause, { size: 11, color: "white", fill: "white" }) : /* @__PURE__ */ React6.createElement(Play, { size: 11, color: "white", fill: "white", style: { marginLeft: 1 } }));
522
544
  }
523
- function LineTagList({ children }) {
545
+ function LineTagList({ children, listHeader }) {
524
546
  const { activeLine, closestTagId } = useRetorBridge();
525
- const listRef = useRef3(null);
547
+ const scrollRef = useRef3(null);
548
+ const offsetsRef = useRef3(/* @__PURE__ */ new Map());
526
549
  const tags = useMemo4(
527
550
  () => (activeLine?.tags ?? []).filter((t) => t.name && t.name.trim().length > 0),
528
551
  [activeLine]
529
552
  );
553
+ useEffect3(() => {
554
+ offsetsRef.current = /* @__PURE__ */ new Map();
555
+ }, [activeLine?._id]);
530
556
  useEffect3(() => {
531
557
  if (!closestTagId) return;
532
- const index = tags.findIndex((t) => t._id === closestTagId);
533
- if (index < 0) return;
534
- requestAnimationFrame(() => {
535
- try {
536
- listRef.current?.scrollToIndex({ index, animated: true, viewPosition: 0 });
537
- } catch {
558
+ const t = setTimeout(() => {
559
+ const y = offsetsRef.current.get(closestTagId);
560
+ if (y != null) {
561
+ scrollRef.current?.scrollTo({ y, animated: true });
538
562
  }
539
- });
540
- }, [closestTagId, tags]);
563
+ }, 60);
564
+ return () => clearTimeout(t);
565
+ }, [closestTagId]);
541
566
  if (!activeLine) return null;
542
- return /* @__PURE__ */ React5.createElement(
543
- BottomSheetFlatList,
567
+ const handleItemLayout = (id, e) => {
568
+ offsetsRef.current.set(id, e.nativeEvent.layout.y);
569
+ };
570
+ return /* @__PURE__ */ React6.createElement(
571
+ BottomSheetScrollView,
544
572
  {
545
- ref: listRef,
546
- data: tags,
547
- keyExtractor: (t) => t._id,
548
- renderItem: ({ item }) => /* @__PURE__ */ React5.createElement(View4, null, children(item, item._id === closestTagId)),
549
- contentContainerStyle: styles3.list,
550
- onScrollToIndexFailed: (info) => {
551
- setTimeout(() => {
552
- listRef.current?.scrollToOffset({ offset: info.averageItemLength * info.index, animated: true });
553
- }, 100);
554
- }
555
- }
573
+ ref: scrollRef,
574
+ contentContainerStyle: styles3.list
575
+ },
576
+ listHeader,
577
+ tags.map((tag) => /* @__PURE__ */ React6.createElement(View5, { key: tag._id, onLayout: (e) => handleItemLayout(tag._id, e) }, children(tag, tag._id === closestTagId)))
556
578
  );
557
579
  }
558
- function DefaultLineTagList() {
559
- return /* @__PURE__ */ React5.createElement(LineTagList, null, (tag, isActive) => /* @__PURE__ */ React5.createElement(DefaultTagItem, { tag, isActive }));
580
+ function DefaultLineTagList({ listHeader }) {
581
+ return /* @__PURE__ */ React6.createElement(LineTagList, { listHeader }, (tag, isActive) => /* @__PURE__ */ React6.createElement(DefaultTagItem, { tag, isActive }));
560
582
  }
561
583
  function DefaultTagItem({ tag, isActive }) {
562
- const { controls, openAddNote, activeLine } = useRetorBridge();
563
- const showPlus = isActive && (activeLine?.notesSupported ?? false);
564
- return /* @__PURE__ */ React5.createElement(
584
+ const { controls, openAddNote } = useRetorBridge();
585
+ return /* @__PURE__ */ React6.createElement(
565
586
  Pressable2,
566
587
  {
567
588
  onPress: () => controls.scrollToTag(tag._id),
568
589
  style: [styles3.tagItem, isActive && styles3.tagItemActive]
569
590
  },
570
- /* @__PURE__ */ React5.createElement(View4, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React5.createElement(Text2, { style: [styles3.tagText, isActive && styles3.tagTextActive], numberOfLines: 1 }, tag.name), isActive && tag.description && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.tagDescription, numberOfLines: 2 }, tag.description)),
571
- tag.subtitle && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.tagSubtitle, numberOfLines: 1 }, tag.subtitle),
572
- showPlus && /* @__PURE__ */ React5.createElement(
591
+ /* @__PURE__ */ React6.createElement(View5, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React6.createElement(Text2, { style: [styles3.tagText, isActive && styles3.tagTextActive], numberOfLines: 1 }, tag.name), isActive && tag.description && /* @__PURE__ */ React6.createElement(Text2, { style: styles3.tagDescription, numberOfLines: 2 }, tag.description)),
592
+ tag.subtitle && /* @__PURE__ */ React6.createElement(Text2, { style: styles3.tagSubtitle, numberOfLines: 1 }, tag.subtitle),
593
+ isActive && /* @__PURE__ */ React6.createElement(
573
594
  Pressable2,
574
595
  {
575
596
  onPress: (e) => {
@@ -578,20 +599,18 @@ function DefaultTagItem({ tag, isActive }) {
578
599
  },
579
600
  style: styles3.plusBtn
580
601
  },
581
- /* @__PURE__ */ React5.createElement(Plus, { size: 14, color: "white" })
602
+ /* @__PURE__ */ React6.createElement(Plus, { size: 14, color: "white" })
582
603
  )
583
604
  );
584
605
  }
585
- var styles3 = StyleSheet4.create({
586
- background: { backgroundColor: "rgba(20,20,20,0.95)" },
606
+ var styles3 = StyleSheet5.create({
587
607
  handle: { backgroundColor: "rgba(255,255,255,0.3)" },
588
- root: { flex: 1 },
589
608
  header: {
590
609
  flexDirection: "row",
591
610
  alignItems: "flex-start",
592
611
  paddingHorizontal: 24,
593
612
  paddingTop: 8,
594
- paddingBottom: 12,
613
+ paddingBottom: 16,
595
614
  gap: 8
596
615
  },
597
616
  headerActions: { flexDirection: "row", alignItems: "center", gap: 6 },
@@ -607,8 +626,7 @@ var styles3 = StyleSheet4.create({
607
626
  justifyContent: "center",
608
627
  position: "relative"
609
628
  },
610
- listContainer: { flex: 1 },
611
- list: { paddingHorizontal: 16, paddingBottom: 8, gap: 4 },
629
+ list: { paddingHorizontal: 16, paddingBottom: 96, gap: 4 },
612
630
  tagItem: {
613
631
  flexDirection: "row",
614
632
  alignItems: "center",
@@ -623,14 +641,19 @@ var styles3 = StyleSheet4.create({
623
641
  tagDescription: { color: "rgba(255,255,255,0.4)", fontSize: 11, marginTop: 2, lineHeight: 14 },
624
642
  tagSubtitle: { color: "rgba(255,255,255,0.4)", fontSize: 10 },
625
643
  plusBtn: {
626
- width: 22,
627
- height: 22,
628
- borderRadius: 11,
629
- backgroundColor: "rgba(255,255,255,0.15)",
644
+ width: 24,
645
+ height: 24,
646
+ borderRadius: 12,
647
+ backgroundColor: "rgba(255,255,255,0.2)",
630
648
  alignItems: "center",
631
649
  justifyContent: "center"
632
650
  },
633
- footer: { paddingHorizontal: 16, paddingBottom: 16, paddingTop: 8 },
651
+ footer: {
652
+ paddingHorizontal: 16,
653
+ paddingBottom: 24,
654
+ paddingTop: 8,
655
+ backgroundColor: "transparent"
656
+ },
634
657
  doneButton: {
635
658
  backgroundColor: "white",
636
659
  borderRadius: 14,
@@ -641,8 +664,8 @@ var styles3 = StyleSheet4.create({
641
664
  });
642
665
 
643
666
  // src/AddNoteSheet.tsx
644
- import React6, { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef4, useState as useState4 } from "react";
645
- import { Pressable as Pressable3, StyleSheet as StyleSheet5, Text as Text3, View as View5 } from "react-native";
667
+ import React7, { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef4, useState as useState4 } from "react";
668
+ import { Pressable as Pressable3, StyleSheet as StyleSheet6, Text as Text3, View as View6 } from "react-native";
646
669
  import {
647
670
  BottomSheetBackdrop as BottomSheetBackdrop3,
648
671
  BottomSheetModal as BottomSheetModal3,
@@ -690,14 +713,14 @@ function AddNoteSheet({
690
713
  close: closeAddNote,
691
714
  maxLength
692
715
  };
693
- return /* @__PURE__ */ React6.createElement(
716
+ return /* @__PURE__ */ React7.createElement(
694
717
  BottomSheetModal3,
695
718
  {
696
719
  ref: sheetRef,
697
720
  snapPoints: snapPointsArr,
698
721
  enablePanDownToClose: true,
699
722
  onDismiss: closeAddNote,
700
- backdropComponent: (props) => /* @__PURE__ */ React6.createElement(
723
+ backdropComponent: (props) => /* @__PURE__ */ React7.createElement(
701
724
  BottomSheetBackdrop3,
702
725
  {
703
726
  ...props,
@@ -707,9 +730,9 @@ function AddNoteSheet({
707
730
  }
708
731
  ),
709
732
  handleIndicatorStyle: styles4.handle,
710
- backgroundStyle: styles4.background
733
+ backgroundComponent: BlurBackground
711
734
  },
712
- /* @__PURE__ */ React6.createElement(BottomSheetView2, { style: styles4.content }, renderForm ? renderForm(formApi) : /* @__PURE__ */ React6.createElement(View5, { style: styles4.form }, /* @__PURE__ */ React6.createElement(View5, { style: styles4.headerRow }, /* @__PURE__ */ React6.createElement(View5, { style: { flex: 1 } }, activeLine && /* @__PURE__ */ React6.createElement(Text3, { style: styles4.title, numberOfLines: 1 }, activeLine.name), tag && /* @__PURE__ */ React6.createElement(Text3, { style: styles4.subtitle, numberOfLines: 1 }, tag.name)), /* @__PURE__ */ React6.createElement(Pressable3, { onPress: closeAddNote, style: styles4.closeBtn }, /* @__PURE__ */ React6.createElement(X, { size: 14, color: "rgba(255,255,255,0.6)" }))), /* @__PURE__ */ React6.createElement(
735
+ /* @__PURE__ */ React7.createElement(BottomSheetView2, { style: styles4.content }, renderForm ? renderForm(formApi) : /* @__PURE__ */ React7.createElement(View6, { style: styles4.form }, /* @__PURE__ */ React7.createElement(View6, { style: styles4.headerRow }, /* @__PURE__ */ React7.createElement(View6, { style: { flex: 1 } }, activeLine && /* @__PURE__ */ React7.createElement(Text3, { style: styles4.title, numberOfLines: 1 }, activeLine.name), tag && /* @__PURE__ */ React7.createElement(Text3, { style: styles4.subtitle, numberOfLines: 1 }, tag.name)), /* @__PURE__ */ React7.createElement(Pressable3, { onPress: closeAddNote, style: styles4.closeBtn }, /* @__PURE__ */ React7.createElement(X, { size: 14, color: "rgba(255,255,255,0.6)" }))), /* @__PURE__ */ React7.createElement(
713
736
  BottomSheetTextInput,
714
737
  {
715
738
  value: text,
@@ -722,26 +745,25 @@ function AddNoteSheet({
722
745
  style: styles4.input,
723
746
  autoFocus: true
724
747
  }
725
- ), /* @__PURE__ */ React6.createElement(View5, { style: styles4.footer }, /* @__PURE__ */ React6.createElement(Text3, { style: [styles4.counter, text.length > maxLength * 0.9 && { color: "#fbbf24" }] }, text.length, "/", maxLength), /* @__PURE__ */ React6.createElement(View5, { style: { flex: 1 } }), /* @__PURE__ */ React6.createElement(
748
+ ), /* @__PURE__ */ React7.createElement(View6, { style: styles4.footer }, /* @__PURE__ */ React7.createElement(Text3, { style: [styles4.counter, text.length > maxLength * 0.9 && { color: "#fbbf24" }] }, text.length, "/", maxLength), /* @__PURE__ */ React7.createElement(View6, { style: { flex: 1 } }), /* @__PURE__ */ React7.createElement(
726
749
  Pressable3,
727
750
  {
728
751
  onPress: () => setPrivate(!isPrivate),
729
752
  style: [styles4.pill, !isPrivate && styles4.pillActive]
730
753
  },
731
- /* @__PURE__ */ React6.createElement(Text3, { style: [styles4.pillText, !isPrivate && styles4.pillTextActive] }, isPrivate ? "Private" : "Public")
732
- ), /* @__PURE__ */ React6.createElement(
754
+ /* @__PURE__ */ React7.createElement(Text3, { style: [styles4.pillText, !isPrivate && styles4.pillTextActive] }, isPrivate ? "Private" : "Public")
755
+ ), /* @__PURE__ */ React7.createElement(
733
756
  Pressable3,
734
757
  {
735
758
  onPress: handleSubmit,
736
759
  disabled: !text.trim(),
737
760
  style: [styles4.submit, !text.trim() && styles4.submitDisabled]
738
761
  },
739
- /* @__PURE__ */ React6.createElement(ArrowUp3, { size: 16, color: "black", strokeWidth: 2.5 })
762
+ /* @__PURE__ */ React7.createElement(ArrowUp3, { size: 16, color: "black", strokeWidth: 2.5 })
740
763
  ))))
741
764
  );
742
765
  }
743
- var styles4 = StyleSheet5.create({
744
- background: { backgroundColor: "rgba(20,20,20,0.98)" },
766
+ var styles4 = StyleSheet6.create({
745
767
  handle: { backgroundColor: "rgba(255,255,255,0.3)" },
746
768
  content: { flex: 1, padding: 24 },
747
769
  form: { flex: 1 },
@@ -793,11 +815,11 @@ var styles4 = StyleSheet5.create({
793
815
  });
794
816
 
795
817
  // src/CoverPhoto.tsx
796
- import React7 from "react";
818
+ import React8 from "react";
797
819
  import { WebView as WebView2 } from "react-native-webview";
798
820
  function CoverPhoto({ projectId, baseUrl = "https://retor.app", style }) {
799
821
  const uri = `${baseUrl}/p/${projectId}?cover=true`;
800
- return /* @__PURE__ */ React7.createElement(
822
+ return /* @__PURE__ */ React8.createElement(
801
823
  WebView2,
802
824
  {
803
825
  source: { uri },
@@ -811,6 +833,7 @@ function CoverPhoto({ projectId, baseUrl = "https://retor.app", style }) {
811
833
  }
812
834
  export {
813
835
  AddNoteSheet,
836
+ BlurBackground,
814
837
  CoverPhoto,
815
838
  Hud,
816
839
  LineDetailSheet,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retor/react-native",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "React Native SDK for embedding Retor 3D experiences",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -27,6 +27,7 @@
27
27
  "license": "MIT",
28
28
  "peerDependencies": {
29
29
  "@gorhom/bottom-sheet": ">=5",
30
+ "expo-blur": ">=14",
30
31
  "lucide-react-native": ">=1",
31
32
  "react": ">=18",
32
33
  "react-native": ">=0.72",
@@ -35,9 +36,15 @@
35
36
  "react-native-svg": ">=14",
36
37
  "react-native-webview": ">=13"
37
38
  },
39
+ "peerDependenciesMeta": {
40
+ "expo-blur": {
41
+ "optional": true
42
+ }
43
+ },
38
44
  "devDependencies": {
39
45
  "@gorhom/bottom-sheet": "^5.1.0",
40
46
  "@types/react": "^19.0.0",
47
+ "expo-blur": "^55.0.14",
41
48
  "lucide-react-native": "^1.8.0",
42
49
  "react": "19.1.0",
43
50
  "react-native": "0.81.4",
@@ -49,8 +56,8 @@
49
56
  "typescript": "^5.4.0"
50
57
  },
51
58
  "scripts": {
52
- "build": "tsup src/index.tsx --format cjs,esm --dts --clean --external react --external react-native --external react-native-webview --external @gorhom/bottom-sheet --external react-native-gesture-handler --external react-native-reanimated --external react-native-svg --external lucide-react-native",
53
- "dev": "tsup src/index.tsx --format cjs,esm --dts --watch --external react --external react-native --external react-native-webview --external @gorhom/bottom-sheet --external react-native-gesture-handler --external react-native-reanimated --external react-native-svg --external lucide-react-native",
59
+ "build": "tsup src/index.tsx --format cjs,esm --dts --clean --external react --external react-native --external react-native-webview --external @gorhom/bottom-sheet --external react-native-gesture-handler --external react-native-reanimated --external react-native-svg --external lucide-react-native --external expo-blur",
60
+ "dev": "tsup src/index.tsx --format cjs,esm --dts --watch --external react --external react-native --external react-native-webview --external @gorhom/bottom-sheet --external react-native-gesture-handler --external react-native-reanimated --external react-native-svg --external lucide-react-native --external expo-blur",
54
61
  "typecheck": "tsc --noEmit"
55
62
  }
56
63
  }