@bsky.app/peek-menu 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.
Files changed (75) hide show
  1. package/.eslintrc.js +5 -0
  2. package/CHANGELOG.md +7 -0
  3. package/README.md +121 -0
  4. package/build/ExpoContextMenuNativeView.android.d.ts +6 -0
  5. package/build/ExpoContextMenuNativeView.android.d.ts.map +1 -0
  6. package/build/ExpoContextMenuNativeView.android.js +9 -0
  7. package/build/ExpoContextMenuNativeView.android.js.map +1 -0
  8. package/build/ExpoContextMenuNativeView.d.ts +5 -0
  9. package/build/ExpoContextMenuNativeView.d.ts.map +1 -0
  10. package/build/ExpoContextMenuNativeView.js +4 -0
  11. package/build/ExpoContextMenuNativeView.js.map +1 -0
  12. package/build/ExpoContextMenuNativeView.web.d.ts +7 -0
  13. package/build/ExpoContextMenuNativeView.web.d.ts.map +1 -0
  14. package/build/ExpoContextMenuNativeView.web.js +10 -0
  15. package/build/ExpoContextMenuNativeView.web.js.map +1 -0
  16. package/build/Menu.d.ts +6 -0
  17. package/build/Menu.d.ts.map +1 -0
  18. package/build/Menu.js +10 -0
  19. package/build/Menu.js.map +1 -0
  20. package/build/MenuItem.d.ts +11 -0
  21. package/build/MenuItem.d.ts.map +1 -0
  22. package/build/MenuItem.js +10 -0
  23. package/build/MenuItem.js.map +1 -0
  24. package/build/MenuItemIcon.d.ts +6 -0
  25. package/build/MenuItemIcon.d.ts.map +1 -0
  26. package/build/MenuItemIcon.js +11 -0
  27. package/build/MenuItemIcon.js.map +1 -0
  28. package/build/MenuItemText.d.ts +5 -0
  29. package/build/MenuItemText.d.ts.map +1 -0
  30. package/build/MenuItemText.js +11 -0
  31. package/build/MenuItemText.js.map +1 -0
  32. package/build/Root.d.ts +8 -0
  33. package/build/Root.d.ts.map +1 -0
  34. package/build/Root.js +81 -0
  35. package/build/Root.js.map +1 -0
  36. package/build/Trigger.d.ts +15 -0
  37. package/build/Trigger.d.ts.map +1 -0
  38. package/build/Trigger.js +10 -0
  39. package/build/Trigger.js.map +1 -0
  40. package/build/index.d.ts +8 -0
  41. package/build/index.d.ts.map +1 -0
  42. package/build/index.js +7 -0
  43. package/build/index.js.map +1 -0
  44. package/build/registry.d.ts +13 -0
  45. package/build/registry.d.ts.map +1 -0
  46. package/build/registry.js +18 -0
  47. package/build/registry.js.map +1 -0
  48. package/build/types.d.ts +70 -0
  49. package/build/types.d.ts.map +1 -0
  50. package/build/types.js +2 -0
  51. package/build/types.js.map +1 -0
  52. package/expo-module.config.json +6 -0
  53. package/ios/ExpoBlueskyPeekMenu.podspec +22 -0
  54. package/ios/ExpoBlueskyPeekMenuModule.swift +24 -0
  55. package/ios/ExpoBlueskyPeekMenuView.swift +111 -0
  56. package/ios/IconRenderer.swift +69 -0
  57. package/ios/ImagePreviewController.swift +133 -0
  58. package/ios/MenuBuilder.swift +51 -0
  59. package/ios/PreviewFactory.swift +27 -0
  60. package/ios/SVGPathParser.swift +320 -0
  61. package/package.json +38 -0
  62. package/src/ExpoContextMenuNativeView.android.tsx +10 -0
  63. package/src/ExpoContextMenuNativeView.tsx +10 -0
  64. package/src/ExpoContextMenuNativeView.web.tsx +11 -0
  65. package/src/Menu.tsx +17 -0
  66. package/src/MenuItem.tsx +22 -0
  67. package/src/MenuItemIcon.tsx +17 -0
  68. package/src/MenuItemText.tsx +16 -0
  69. package/src/Root.tsx +119 -0
  70. package/src/Trigger.tsx +26 -0
  71. package/src/index.ts +12 -0
  72. package/src/registry.ts +32 -0
  73. package/src/types.ts +71 -0
  74. package/tsconfig.json +9 -0
  75. package/vitest.config.ts +7 -0
package/.eslintrc.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ['universe/native', 'universe/web'],
4
+ ignorePatterns: ['build'],
5
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @bsky.app/peek-menu
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#27](https://github.com/bluesky-social/toolbox/pull/27) [`890bae9`](https://github.com/bluesky-social/toolbox/commit/890bae91f04f08c0ac8b20bdc8ce4cead3a68e63) Thanks [@mozzius](https://github.com/mozzius)! - Initial release of @bsky.app/peek-menu, extracted from social-app's expo-bluesky-context-menu module.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @bsky.app/peek-menu
2
+
3
+ Native iOS context menu with peek preview for images. Long-pressing a wrapped view shows a `UIContextMenuInteraction` with a full-size image preview and action menu. Android and web fall through to a passthrough `View`.
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ npm install @bsky.app/peek-menu
9
+ ```
10
+
11
+ **Note:** The iOS native side shares `SDImageCache.shared` with `expo-image` via `SDWebImage ~> 5.21.0`. Your app must have a compatible SDWebImage version installed (expo-image provides this).
12
+
13
+ ## Usage
14
+
15
+ Declarative, compound-component API. `Root` collects children tagged as `Trigger` and `Menu`, serializes the menu items, and renders a single native view.
16
+
17
+ ```tsx
18
+ import * as PeekMenu from '@bsky.app/peek-menu'
19
+
20
+ ;<PeekMenu.Root>
21
+ <PeekMenu.Trigger
22
+ preview={{
23
+ type: 'image',
24
+ uri: fullsizeUrl,
25
+ thumbUri: thumbUrl,
26
+ aspectRatio: 1.5,
27
+ }}
28
+ borderRadius={12}>
29
+ {children}
30
+ </PeekMenu.Trigger>
31
+ <PeekMenu.Menu>
32
+ <PeekMenu.MenuItem id="save" onSelect={handleSave}>
33
+ <PeekMenu.MenuItemIcon icon={SaveIcon} />
34
+ <PeekMenu.MenuItemText>Save image</PeekMenu.MenuItemText>
35
+ </PeekMenu.MenuItem>
36
+ </PeekMenu.Menu>
37
+ </PeekMenu.Root>
38
+ ```
39
+
40
+ `Trigger`, `Menu`, `MenuItem`, `MenuItemIcon`, and `MenuItemText` are sentinel components — they render nothing. `Root` walks the children tree at render time, extracts their props, and passes serialized data to the native view.
41
+
42
+ ### Props
43
+
44
+ **`Trigger`**
45
+
46
+ - `preview?: PreviewContent` — what to show during peek. Only `image` is implemented; `video` and `externalCard` are typed but will fall back to no preview.
47
+ - `borderRadius?: number` — corner radius of the thumbnail. Used in the native targeted-preview so the lift animation matches the clipping.
48
+ - `onPreviewPress?: () => void` — fires when the user taps the expanded preview to commit into it (i.e. open the lightbox).
49
+
50
+ **`MenuItem`**
51
+
52
+ - `id: string` — stable identifier, sent back in the `onItemPress` event.
53
+ - `onSelect: () => void` — called when this item is tapped.
54
+ - `destructive?: boolean` — renders the item in red.
55
+ - `disabled?: boolean` — greys the item out.
56
+
57
+ **`MenuItemIcon`**
58
+
59
+ - `icon: SvgIconMeta` — any object with `svgPaths: string[]`, `svgViewBox: string`, and `svgStrokeWidth: number`. Rendered natively via `IconRenderer`.
60
+
61
+ ### Icon compatibility
62
+
63
+ The `SvgIconMeta` type is intentionally minimal:
64
+
65
+ ```ts
66
+ type SvgIconMeta = {
67
+ svgPaths: string[]
68
+ svgViewBox: string
69
+ svgStrokeWidth: number
70
+ }
71
+ ```
72
+
73
+ Any icon component that carries these three properties (e.g. those created with `createSinglePathSVG`) can be passed directly — TypeScript's structural typing handles the rest.
74
+
75
+ ### Preview types
76
+
77
+ ```ts
78
+ type PreviewContent =
79
+ | {type: 'image'; uri: string; thumbUri?: string; aspectRatio: number}
80
+ | {type: 'video'; uri: string; poster?: string; aspectRatio: number} // not yet implemented
81
+ | {type: 'externalCard'; thumbUri?: string; title: string; url: string} // not yet implemented
82
+ ```
83
+
84
+ ## Platform behavior
85
+
86
+ | Platform | Behavior |
87
+ | -------- | ------------------------------------------------------------------- |
88
+ | iOS | Native `UIContextMenuInteraction` with peek preview and action menu |
89
+ | Android | Passthrough `View` wrapper (no context menu) |
90
+ | Web | Passthrough `View` wrapper (no context menu) |
91
+
92
+ ## iOS native architecture
93
+
94
+ ### Image loading
95
+
96
+ `ImagePreviewController` shares SDWebImage's `SDImageCache.shared` and `SDWebImageManager.shared` with expo-image, so cache hits are free:
97
+
98
+ 1. **Memory cache hit on fullsize?** Paint it immediately — zero latency.
99
+ 2. **Memory or disk cache hit on thumbnail?** Paint the thumb as a placeholder, then async-load the fullsize. Thumbs are small enough that a sync disk read is acceptable.
100
+ 3. **No cache hit?** Show nothing initially, async-load the fullsize.
101
+
102
+ For best results, prefetch the fullsize image into memory on press-in so it's ready by the time the peek animation starts:
103
+
104
+ ```tsx
105
+ <Pressable
106
+ onPressIn={() => {
107
+ InteractionManager.runAfterInteractions(() => {
108
+ Image.prefetch(fullsizeUri, 'memory')
109
+ })
110
+ }}>
111
+ {children}
112
+ </Pressable>
113
+ ```
114
+
115
+ This is the pattern used by social-app — `Image.prefetch(url, 'memory')` from expo-image writes into `SDImageCache.shared`, which the native preview controller reads from synchronously.
116
+
117
+ ### Known limitations
118
+
119
+ - **Carousel clipping**: When an image is inside a horizontal `FlatList`, the `UIScrollView`'s `clipsToBounds` clips the peek lift animation and its shadow.
120
+ - **Android/web**: No native implementation yet. Falls through to a plain `View` wrapper.
121
+ - **Video and external card previews**: Typed in `PreviewContent` but not implemented on the native side.
@@ -0,0 +1,6 @@
1
+ import { type NativeViewProps } from './types';
2
+ /**
3
+ * Android fallback: passthrough wrapper with no context menu interaction.
4
+ */
5
+ export default function NativeView({ children, style }: NativeViewProps): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=ExpoContextMenuNativeView.android.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoContextMenuNativeView.android.d.ts","sourceRoot":"","sources":["../src/ExpoContextMenuNativeView.android.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,eAAe,EAAC,MAAM,SAAS,CAAA;AAE5C;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAAC,QAAQ,EAAE,KAAK,EAAC,EAAE,eAAe,2CAEpE"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { View } from 'react-native';
3
+ /**
4
+ * Android fallback: passthrough wrapper with no context menu interaction.
5
+ */
6
+ export default function NativeView({ children, style }) {
7
+ return _jsx(View, { style: style, children: children });
8
+ }
9
+ //# sourceMappingURL=ExpoContextMenuNativeView.android.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoContextMenuNativeView.android.js","sourceRoot":"","sources":["../src/ExpoContextMenuNativeView.android.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,cAAc,CAAA;AAIjC;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAAC,QAAQ,EAAE,KAAK,EAAkB;IACnE,OAAO,KAAC,IAAI,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAQ,CAAA;AAC9C,CAAC","sourcesContent":["import {View} from 'react-native'\n\nimport {type NativeViewProps} from './types'\n\n/**\n * Android fallback: passthrough wrapper with no context menu interaction.\n */\nexport default function NativeView({children, style}: NativeViewProps) {\n return <View style={style}>{children}</View>\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import { type ComponentType } from 'react';
2
+ import { type NativeViewProps } from './types';
3
+ declare const NativeView: ComponentType<NativeViewProps>;
4
+ export default NativeView;
5
+ //# sourceMappingURL=ExpoContextMenuNativeView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoContextMenuNativeView.d.ts","sourceRoot":"","sources":["../src/ExpoContextMenuNativeView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,OAAO,CAAA;AAGxC,OAAO,EAAC,KAAK,eAAe,EAAC,MAAM,SAAS,CAAA;AAE5C,QAAA,MAAM,UAAU,EAAE,aAAa,CAAC,eAAe,CAE9C,CAAA;AAED,eAAe,UAAU,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { requireNativeViewManager } from 'expo-modules-core';
2
+ const NativeView = requireNativeViewManager('ExpoBlueskyPeekMenu');
3
+ export default NativeView;
4
+ //# sourceMappingURL=ExpoContextMenuNativeView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoContextMenuNativeView.js","sourceRoot":"","sources":["../src/ExpoContextMenuNativeView.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,wBAAwB,EAAC,MAAM,mBAAmB,CAAA;AAI1D,MAAM,UAAU,GAAmC,wBAAwB,CACzE,qBAAqB,CACtB,CAAA;AAED,eAAe,UAAU,CAAA","sourcesContent":["import {type ComponentType} from 'react'\nimport {requireNativeViewManager} from 'expo-modules-core'\n\nimport {type NativeViewProps} from './types'\n\nconst NativeView: ComponentType<NativeViewProps> = requireNativeViewManager(\n 'ExpoBlueskyPeekMenu',\n)\n\nexport default NativeView\n"]}
@@ -0,0 +1,7 @@
1
+ import { type NativeViewProps } from './types';
2
+ /**
3
+ * Web fallback: passthrough. Long-press is a no-op; tap handling is delegated
4
+ * to children.
5
+ */
6
+ export default function NativeView({ children, style }: NativeViewProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=ExpoContextMenuNativeView.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoContextMenuNativeView.web.d.ts","sourceRoot":"","sources":["../src/ExpoContextMenuNativeView.web.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,eAAe,EAAC,MAAM,SAAS,CAAA;AAE5C;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAAC,QAAQ,EAAE,KAAK,EAAC,EAAE,eAAe,2CAEpE"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { View } from 'react-native';
3
+ /**
4
+ * Web fallback: passthrough. Long-press is a no-op; tap handling is delegated
5
+ * to children.
6
+ */
7
+ export default function NativeView({ children, style }) {
8
+ return _jsx(View, { style: style, children: children });
9
+ }
10
+ //# sourceMappingURL=ExpoContextMenuNativeView.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoContextMenuNativeView.web.js","sourceRoot":"","sources":["../src/ExpoContextMenuNativeView.web.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,cAAc,CAAA;AAIjC;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAAC,QAAQ,EAAE,KAAK,EAAkB;IACnE,OAAO,KAAC,IAAI,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAQ,CAAA;AAC9C,CAAC","sourcesContent":["import {View} from 'react-native'\n\nimport {type NativeViewProps} from './types'\n\n/**\n * Web fallback: passthrough. Long-press is a no-op; tap handling is delegated\n * to children.\n */\nexport default function NativeView({children, style}: NativeViewProps) {\n return <View style={style}>{children}</View>\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import { type ReactNode } from 'react';
2
+ export type MenuProps = {
3
+ children: ReactNode;
4
+ };
5
+ export declare const Menu: import("./registry").TaggedComponent<MenuProps>;
6
+ //# sourceMappingURL=Menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Menu.d.ts","sourceRoot":"","sources":["../src/Menu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAA;AAIpC,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAUD,eAAO,MAAM,IAAI,iDAAwB,CAAA"}
package/build/Menu.js ADDED
@@ -0,0 +1,10 @@
1
+ import { tag } from './registry';
2
+ /**
3
+ * Sentinel: does not render. `Root` reads this element's children to collect
4
+ * menu items.
5
+ */
6
+ function MenuImpl(_) {
7
+ return null;
8
+ }
9
+ export const Menu = tag(MenuImpl, 'menu');
10
+ //# sourceMappingURL=Menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Menu.js","sourceRoot":"","sources":["../src/Menu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAC,GAAG,EAAC,MAAM,YAAY,CAAA;AAM9B;;;GAGG;AACH,SAAS,QAAQ,CAAC,CAAY;IAC5B,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA","sourcesContent":["import {type ReactNode} from 'react'\n\nimport {tag} from './registry'\n\nexport type MenuProps = {\n children: ReactNode\n}\n\n/**\n * Sentinel: does not render. `Root` reads this element's children to collect\n * menu items.\n */\nfunction MenuImpl(_: MenuProps): null {\n return null\n}\n\nexport const Menu = tag(MenuImpl, 'menu')\n"]}
@@ -0,0 +1,11 @@
1
+ import { type ReactNode } from 'react';
2
+ export type MenuItemProps = {
3
+ id: string;
4
+ destructive?: boolean;
5
+ disabled?: boolean;
6
+ onSelect: () => void;
7
+ /** Children must include a `MenuItemIcon` and a `MenuItemText`. */
8
+ children: ReactNode;
9
+ };
10
+ export declare const MenuItem: import("./registry").TaggedComponent<MenuItemProps>;
11
+ //# sourceMappingURL=MenuItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuItem.d.ts","sourceRoot":"","sources":["../src/MenuItem.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAA;AAIpC,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,mEAAmE;IACnE,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAUD,eAAO,MAAM,QAAQ,qDAA4B,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { tag } from './registry';
2
+ /**
3
+ * Sentinel: does not render. `Root` walks the children tree to extract icon +
4
+ * label, then ships a plain menu item spec to native.
5
+ */
6
+ function MenuItemImpl(_) {
7
+ return null;
8
+ }
9
+ export const MenuItem = tag(MenuItemImpl, 'item');
10
+ //# sourceMappingURL=MenuItem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuItem.js","sourceRoot":"","sources":["../src/MenuItem.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAC,GAAG,EAAC,MAAM,YAAY,CAAA;AAW9B;;;GAGG;AACH,SAAS,YAAY,CAAC,CAAgB;IACpC,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA","sourcesContent":["import {type ReactNode} from 'react'\n\nimport {tag} from './registry'\n\nexport type MenuItemProps = {\n id: string\n destructive?: boolean\n disabled?: boolean\n onSelect: () => void\n /** Children must include a `MenuItemIcon` and a `MenuItemText`. */\n children: ReactNode\n}\n\n/**\n * Sentinel: does not render. `Root` walks the children tree to extract icon +\n * label, then ships a plain menu item spec to native.\n */\nfunction MenuItemImpl(_: MenuItemProps): null {\n return null\n}\n\nexport const MenuItem = tag(MenuItemImpl, 'item')\n"]}
@@ -0,0 +1,6 @@
1
+ import { type MenuItemIconSource } from './types';
2
+ export type MenuItemIconProps = {
3
+ icon: MenuItemIconSource;
4
+ };
5
+ export declare const MenuItemIcon: import("./registry").TaggedComponent<MenuItemIconProps>;
6
+ //# sourceMappingURL=MenuItemIcon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuItemIcon.d.ts","sourceRoot":"","sources":["../src/MenuItemIcon.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,KAAK,kBAAkB,EAAC,MAAM,SAAS,CAAA;AAE/C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,kBAAkB,CAAA;CACzB,CAAA;AAWD,eAAO,MAAM,YAAY,yDAAqC,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { tag } from './registry';
2
+ /**
3
+ * Sentinel: does not render any React output. `Root` introspects this element
4
+ * during its collection pass to pull the SVG path data off the icon component,
5
+ * then ships the data to native.
6
+ */
7
+ function MenuItemIconImpl(_) {
8
+ return null;
9
+ }
10
+ export const MenuItemIcon = tag(MenuItemIconImpl, 'item-icon');
11
+ //# sourceMappingURL=MenuItemIcon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuItemIcon.js","sourceRoot":"","sources":["../src/MenuItemIcon.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,GAAG,EAAC,MAAM,YAAY,CAAA;AAO9B;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,CAAoB;IAC5C,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAA","sourcesContent":["import {tag} from './registry'\nimport {type MenuItemIconSource} from './types'\n\nexport type MenuItemIconProps = {\n icon: MenuItemIconSource\n}\n\n/**\n * Sentinel: does not render any React output. `Root` introspects this element\n * during its collection pass to pull the SVG path data off the icon component,\n * then ships the data to native.\n */\nfunction MenuItemIconImpl(_: MenuItemIconProps): null {\n return null\n}\n\nexport const MenuItemIcon = tag(MenuItemIconImpl, 'item-icon')\n"]}
@@ -0,0 +1,5 @@
1
+ export type MenuItemTextProps = {
2
+ children: string;
3
+ };
4
+ export declare const MenuItemText: import("./registry").TaggedComponent<MenuItemTextProps>;
5
+ //# sourceMappingURL=MenuItemText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuItemText.d.ts","sourceRoot":"","sources":["../src/MenuItemText.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAWD,eAAO,MAAM,YAAY,yDAAqC,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { tag } from './registry';
2
+ /**
3
+ * Sentinel: does not render. `Root` reads `children` as the menu item label.
4
+ * Keeping this a sentinel (vs. a real Text) mirrors how `Menu.ItemText` is
5
+ * used elsewhere while letting iOS draw the menu chrome natively.
6
+ */
7
+ function MenuItemTextImpl(_) {
8
+ return null;
9
+ }
10
+ export const MenuItemText = tag(MenuItemTextImpl, 'item-text');
11
+ //# sourceMappingURL=MenuItemText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuItemText.js","sourceRoot":"","sources":["../src/MenuItemText.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,GAAG,EAAC,MAAM,YAAY,CAAA;AAM9B;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,CAAoB;IAC5C,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAA","sourcesContent":["import {tag} from './registry'\n\nexport type MenuItemTextProps = {\n children: string\n}\n\n/**\n * Sentinel: does not render. `Root` reads `children` as the menu item label.\n * Keeping this a sentinel (vs. a real Text) mirrors how `Menu.ItemText` is\n * used elsewhere while letting iOS draw the menu chrome natively.\n */\nfunction MenuItemTextImpl(_: MenuItemTextProps): null {\n return null\n}\n\nexport const MenuItemText = tag(MenuItemTextImpl, 'item-text')\n"]}
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type StyleProp, type ViewStyle } from 'react-native';
3
+ export type RootProps = {
4
+ children: ReactNode;
5
+ style?: StyleProp<ViewStyle>;
6
+ };
7
+ export declare function Root({ children, style }: RootProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=Root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Root.d.ts","sourceRoot":"","sources":["../src/Root.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EAGf,MAAM,OAAO,CAAA;AACd,OAAO,EAAC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAC,MAAM,cAAc,CAAA;AAW3D,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,SAAS,CAAA;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAC7B,CAAA;AAED,wBAAgB,IAAI,CAAC,EAAC,QAAQ,EAAE,KAAK,EAAC,EAAE,SAAS,2CA8ChD"}
package/build/Root.js ADDED
@@ -0,0 +1,81 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Children, isValidElement, useCallback, useMemo, } from 'react';
3
+ import NativeView from './ExpoContextMenuNativeView';
4
+ import { kindOf } from './registry';
5
+ export function Root({ children, style }) {
6
+ const { trigger, menu } = collectTriggerAndMenu(children);
7
+ const { menuItems, selectById } = useMemo(() => {
8
+ const items = [];
9
+ const map = {};
10
+ if (menu) {
11
+ Children.forEach(menu.props.children, child => {
12
+ if (!isValidElement(child))
13
+ return;
14
+ if (kindOf(child.type) !== 'item')
15
+ return;
16
+ const spec = specFromItem(child);
17
+ if (!spec)
18
+ return;
19
+ items.push(spec.item);
20
+ map[spec.item.id] = spec.onSelect;
21
+ });
22
+ }
23
+ return { menuItems: items, selectById: map };
24
+ }, [menu]);
25
+ const handleItemPress = useCallback((e) => {
26
+ selectById[e.nativeEvent.id]?.();
27
+ }, [selectById]);
28
+ const onPreviewPress = trigger?.props.onPreviewPress;
29
+ const handlePreviewPress = useCallback(() => {
30
+ onPreviewPress?.();
31
+ }, [onPreviewPress]);
32
+ if (!trigger) {
33
+ return _jsx(_Fragment, { children: children });
34
+ }
35
+ return (_jsx(NativeView, { preview: trigger.props.preview, menuItems: menuItems, previewCornerRadius: trigger.props.borderRadius ?? 0, onItemPress: handleItemPress, onPreviewPress: handlePreviewPress, style: [style, trigger.props.style], children: trigger.props.children }));
36
+ }
37
+ function collectTriggerAndMenu(children) {
38
+ const result = {};
39
+ Children.forEach(children, child => {
40
+ if (!isValidElement(child))
41
+ return;
42
+ const kind = kindOf(child.type);
43
+ if (kind === 'trigger')
44
+ result.trigger = child;
45
+ else if (kind === 'menu')
46
+ result.menu = child;
47
+ });
48
+ return result;
49
+ }
50
+ function specFromItem(element) {
51
+ const { id, destructive, disabled, onSelect, children } = element.props;
52
+ let label = '';
53
+ let icon;
54
+ Children.forEach(children, child => {
55
+ if (!isValidElement(child))
56
+ return;
57
+ const kind = kindOf(child.type);
58
+ if (kind === 'item-text') {
59
+ const text = child.props.children;
60
+ if (typeof text === 'string')
61
+ label = text;
62
+ }
63
+ else if (kind === 'item-icon') {
64
+ const iconSource = child.props.icon;
65
+ if (iconSource?.svgPaths?.length) {
66
+ icon = {
67
+ paths: iconSource.svgPaths,
68
+ viewBox: iconSource.svgViewBox,
69
+ strokeWidth: iconSource.svgStrokeWidth,
70
+ };
71
+ }
72
+ }
73
+ });
74
+ if (!label)
75
+ return null;
76
+ return {
77
+ item: { id, label, destructive, disabled, icon },
78
+ onSelect,
79
+ };
80
+ }
81
+ //# sourceMappingURL=Root.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Root.js","sourceRoot":"","sources":["../src/Root.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Children,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useCallback,\n useMemo,\n} from 'react'\nimport {type StyleProp, type ViewStyle} from 'react-native'\n\nimport NativeView from './ExpoContextMenuNativeView'\nimport {type MenuProps} from './Menu'\nimport {type MenuItemProps} from './MenuItem'\nimport {type MenuItemIconProps} from './MenuItemIcon'\nimport {type MenuItemTextProps} from './MenuItemText'\nimport {kindOf} from './registry'\nimport {type TriggerProps} from './Trigger'\nimport {type MenuItemSpec} from './types'\n\nexport type RootProps = {\n children: ReactNode\n style?: StyleProp<ViewStyle>\n}\n\nexport function Root({children, style}: RootProps) {\n const {trigger, menu} = collectTriggerAndMenu(children)\n\n const {menuItems, selectById} = useMemo(() => {\n const items: MenuItemSpec[] = []\n const map: Record<string, () => void> = {}\n if (menu) {\n Children.forEach(menu.props.children, child => {\n if (!isValidElement(child)) return\n if (kindOf(child.type) !== 'item') return\n const spec = specFromItem(child as ReactElement<MenuItemProps>)\n if (!spec) return\n items.push(spec.item)\n map[spec.item.id] = spec.onSelect\n })\n }\n return {menuItems: items, selectById: map}\n }, [menu])\n\n const handleItemPress = useCallback(\n (e: {nativeEvent: {id: string}}) => {\n selectById[e.nativeEvent.id]?.()\n },\n [selectById],\n )\n\n const onPreviewPress = trigger?.props.onPreviewPress\n const handlePreviewPress = useCallback(() => {\n onPreviewPress?.()\n }, [onPreviewPress])\n\n if (!trigger) {\n return <>{children}</>\n }\n\n return (\n <NativeView\n preview={trigger.props.preview}\n menuItems={menuItems}\n previewCornerRadius={trigger.props.borderRadius ?? 0}\n onItemPress={handleItemPress}\n onPreviewPress={handlePreviewPress}\n style={[style, trigger.props.style]}>\n {trigger.props.children}\n </NativeView>\n )\n}\n\n// -----------------------------------------------------------------------------\n\ntype Collected = {\n trigger?: ReactElement<TriggerProps>\n menu?: ReactElement<MenuProps>\n}\n\nfunction collectTriggerAndMenu(children: ReactNode): Collected {\n const result: Collected = {}\n Children.forEach(children, child => {\n if (!isValidElement(child)) return\n const kind = kindOf(child.type)\n if (kind === 'trigger') result.trigger = child as ReactElement<TriggerProps>\n else if (kind === 'menu') result.menu = child as ReactElement<MenuProps>\n })\n return result\n}\n\nfunction specFromItem(\n element: ReactElement<MenuItemProps>,\n): {item: MenuItemSpec; onSelect: () => void} | null {\n const {id, destructive, disabled, onSelect, children} = element.props\n let label = ''\n let icon: MenuItemSpec['icon']\n Children.forEach(children, child => {\n if (!isValidElement(child)) return\n const kind = kindOf(child.type)\n if (kind === 'item-text') {\n const text = (child as ReactElement<MenuItemTextProps>).props.children\n if (typeof text === 'string') label = text\n } else if (kind === 'item-icon') {\n const iconSource = (child as ReactElement<MenuItemIconProps>).props.icon\n if (iconSource?.svgPaths?.length) {\n icon = {\n paths: iconSource.svgPaths,\n viewBox: iconSource.svgViewBox,\n strokeWidth: iconSource.svgStrokeWidth,\n }\n }\n }\n })\n if (!label) return null\n return {\n item: {id, label, destructive, disabled, icon},\n onSelect,\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type StyleProp, type ViewStyle } from 'react-native';
3
+ import { type PreviewContent } from './types';
4
+ export type TriggerProps = {
5
+ preview?: PreviewContent;
6
+ /** Fires when the user taps the expanded preview to "commit" into it. */
7
+ onPreviewPress?: () => void;
8
+ /** Border radius of the thumbnail being wrapped. Used natively to clip the
9
+ * targeted-preview lift animation. */
10
+ borderRadius?: number;
11
+ style?: StyleProp<ViewStyle>;
12
+ children: ReactNode;
13
+ };
14
+ export declare const Trigger: import("./registry").TaggedComponent<TriggerProps>;
15
+ //# sourceMappingURL=Trigger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Trigger.d.ts","sourceRoot":"","sources":["../src/Trigger.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAA;AACpC,OAAO,EAAC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAC,MAAM,cAAc,CAAA;AAG3D,OAAO,EAAC,KAAK,cAAc,EAAC,MAAM,SAAS,CAAA;AAE3C,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B;2CACuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAC5B,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAUD,eAAO,MAAM,OAAO,oDAA8B,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { tag } from './registry';
2
+ /**
3
+ * Sentinel: does not render. `Root` reads props + children off this element
4
+ * and hosts `children` inside the native context-menu view.
5
+ */
6
+ function TriggerImpl(_) {
7
+ return null;
8
+ }
9
+ export const Trigger = tag(TriggerImpl, 'trigger');
10
+ //# sourceMappingURL=Trigger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Trigger.js","sourceRoot":"","sources":["../src/Trigger.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAC,GAAG,EAAC,MAAM,YAAY,CAAA;AAc9B;;;GAGG;AACH,SAAS,WAAW,CAAC,CAAe;IAClC,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA","sourcesContent":["import {type ReactNode} from 'react'\nimport {type StyleProp, type ViewStyle} from 'react-native'\n\nimport {tag} from './registry'\nimport {type PreviewContent} from './types'\n\nexport type TriggerProps = {\n preview?: PreviewContent\n /** Fires when the user taps the expanded preview to \"commit\" into it. */\n onPreviewPress?: () => void\n /** Border radius of the thumbnail being wrapped. Used natively to clip the\n * targeted-preview lift animation. */\n borderRadius?: number\n style?: StyleProp<ViewStyle>\n children: ReactNode\n}\n\n/**\n * Sentinel: does not render. `Root` reads props + children off this element\n * and hosts `children` inside the native context-menu view.\n */\nfunction TriggerImpl(_: TriggerProps): null {\n return null\n}\n\nexport const Trigger = tag(TriggerImpl, 'trigger')\n"]}
@@ -0,0 +1,8 @@
1
+ export { Menu } from './Menu';
2
+ export { MenuItem } from './MenuItem';
3
+ export { MenuItemIcon } from './MenuItemIcon';
4
+ export { MenuItemText } from './MenuItemText';
5
+ export { Root } from './Root';
6
+ export { Trigger } from './Trigger';
7
+ export type { MenuItemIconSource, MenuItemSpec, PreviewContent, SvgIconMeta, } from './types';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAA;AACnC,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,YAAY,EACV,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,WAAW,GACZ,MAAM,SAAS,CAAA"}
package/build/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { Menu } from './Menu';
2
+ export { MenuItem } from './MenuItem';
3
+ export { MenuItemIcon } from './MenuItemIcon';
4
+ export { MenuItemText } from './MenuItemText';
5
+ export { Root } from './Root';
6
+ export { Trigger } from './Trigger';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAA;AACnC,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA","sourcesContent":["export {Menu} from './Menu'\nexport {MenuItem} from './MenuItem'\nexport {MenuItemIcon} from './MenuItemIcon'\nexport {MenuItemText} from './MenuItemText'\nexport {Root} from './Root'\nexport {Trigger} from './Trigger'\nexport type {\n MenuItemIconSource,\n MenuItemSpec,\n PreviewContent,\n SvgIconMeta,\n} from './types'\n"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Marker keys and type tags shared between `Root`, `Trigger`, `Menu`, and
3
+ * `MenuItem*`. `Root` walks its children looking for these tags so the
4
+ * composition API doesn't rely on string component names or display names.
5
+ */
6
+ export declare const CONTEXT_MENU_KIND = "__ExpoBlueskyPeekMenuKind__";
7
+ export type ContextMenuKind = 'trigger' | 'menu' | 'item' | 'item-icon' | 'item-text';
8
+ export type TaggedComponent<P> = React.FunctionComponent<P> & {
9
+ [CONTEXT_MENU_KIND]: ContextMenuKind;
10
+ };
11
+ export declare function tag<P>(component: React.FunctionComponent<P>, kind: ContextMenuKind): TaggedComponent<P>;
12
+ export declare function kindOf(type: unknown): ContextMenuKind | undefined;
13
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,gCAAgC,CAAA;AAE9D,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,MAAM,GACN,MAAM,GACN,WAAW,GACX,WAAW,CAAA;AAEf,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,GAAG;IAC5D,CAAC,iBAAiB,CAAC,EAAE,eAAe,CAAA;CACrC,CAAA;AAED,wBAAgB,GAAG,CAAC,CAAC,EACnB,SAAS,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,EACrC,IAAI,EAAE,eAAe,GACpB,eAAe,CAAC,CAAC,CAAC,CAGpB;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAKjE"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Marker keys and type tags shared between `Root`, `Trigger`, `Menu`, and
3
+ * `MenuItem*`. `Root` walks its children looking for these tags so the
4
+ * composition API doesn't rely on string component names or display names.
5
+ */
6
+ export const CONTEXT_MENU_KIND = '__ExpoBlueskyPeekMenuKind__';
7
+ export function tag(component, kind) {
8
+ ;
9
+ component[CONTEXT_MENU_KIND] = kind;
10
+ return component;
11
+ }
12
+ export function kindOf(type) {
13
+ if (type && typeof type === 'function') {
14
+ return type[CONTEXT_MENU_KIND];
15
+ }
16
+ return undefined;
17
+ }
18
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,6BAA6B,CAAA;AAa9D,MAAM,UAAU,GAAG,CACjB,SAAqC,EACrC,IAAqB;IAErB,CAAC;IAAC,SAAgC,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAA;IAC5D,OAAO,SAA+B,CAAA;AACxC,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,IAAa;IAClC,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;QACvC,OAAQ,IAAiC,CAAC,iBAAiB,CAAC,CAAA;IAC9D,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC","sourcesContent":["/**\n * Marker keys and type tags shared between `Root`, `Trigger`, `Menu`, and\n * `MenuItem*`. `Root` walks its children looking for these tags so the\n * composition API doesn't rely on string component names or display names.\n */\nexport const CONTEXT_MENU_KIND = '__ExpoBlueskyPeekMenuKind__'\n\nexport type ContextMenuKind =\n | 'trigger'\n | 'menu'\n | 'item'\n | 'item-icon'\n | 'item-text'\n\nexport type TaggedComponent<P> = React.FunctionComponent<P> & {\n [CONTEXT_MENU_KIND]: ContextMenuKind\n}\n\nexport function tag<P>(\n component: React.FunctionComponent<P>,\n kind: ContextMenuKind,\n): TaggedComponent<P> {\n ;(component as TaggedComponent<P>)[CONTEXT_MENU_KIND] = kind\n return component as TaggedComponent<P>\n}\n\nexport function kindOf(type: unknown): ContextMenuKind | undefined {\n if (type && typeof type === 'function') {\n return (type as TaggedComponent<unknown>)[CONTEXT_MENU_KIND]\n }\n return undefined\n}\n"]}
@@ -0,0 +1,70 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type StyleProp, type ViewStyle } from 'react-native';
3
+ /**
4
+ * The subset of SVG metadata needed by the native menu icon renderer.
5
+ * Any icon component that carries these three properties can be passed
6
+ * to `MenuItemIcon`. In practice this is satisfied by every icon
7
+ * created with `createSinglePathSVG` / `createMultiPathSVG`.
8
+ */
9
+ export type SvgIconMeta = {
10
+ svgPaths: string[];
11
+ svgViewBox: string;
12
+ svgStrokeWidth: number;
13
+ };
14
+ /**
15
+ * Content to show during the peek preview. Discriminated by `type`; the native
16
+ * side dispatches on it to build the right `UIViewController`.
17
+ *
18
+ * Only `image` is implemented on iOS today. `video` and `externalCard` are the
19
+ * planned follow-ups; leaving them in the type keeps the JS call-sites honest.
20
+ */
21
+ export type PreviewContent = {
22
+ type: 'image';
23
+ uri: string;
24
+ /** Thumb URL. When present, the native side paints it in as an instant
25
+ * placeholder (reading from the shared SDWebImage cache) while the
26
+ * fullsize loads — avoids the black flash on first peek. */
27
+ thumbUri?: string;
28
+ /** Aspect ratio as width / height. */
29
+ aspectRatio: number;
30
+ } | {
31
+ type: 'video';
32
+ uri: string;
33
+ poster?: string;
34
+ aspectRatio: number;
35
+ } | {
36
+ type: 'externalCard';
37
+ thumbUri?: string;
38
+ title: string;
39
+ description?: string;
40
+ url: string;
41
+ };
42
+ export type MenuItemSpec = {
43
+ id: string;
44
+ label: string;
45
+ destructive?: boolean;
46
+ disabled?: boolean;
47
+ icon?: {
48
+ paths: string[];
49
+ viewBox: string;
50
+ strokeWidth: number;
51
+ };
52
+ };
53
+ export type MenuItemIconSource = SvgIconMeta;
54
+ export type NativeViewProps = {
55
+ preview?: PreviewContent;
56
+ menuItems: MenuItemSpec[];
57
+ /** Named distinctly from `borderRadius`, which RN owns as a style prop. */
58
+ previewCornerRadius: number;
59
+ onItemPress: (e: {
60
+ nativeEvent: {
61
+ id: string;
62
+ };
63
+ }) => void;
64
+ onPreviewPress: (e: {
65
+ nativeEvent: {};
66
+ }) => void;
67
+ style?: StyleProp<ViewStyle>;
68
+ children?: ReactNode;
69
+ };
70
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAA;AACpC,OAAO,EAAC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAC,MAAM,cAAc,CAAA;AAE3D;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;CACvB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX;;iEAE6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAA;CACpB,GACD;IACE,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;CACpB,GACD;IACE,IAAI,EAAE,cAAc,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAEL,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE;QACL,KAAK,EAAE,MAAM,EAAE,CAAA;QACf,OAAO,EAAE,MAAM,CAAA;QACf,WAAW,EAAE,MAAM,CAAA;KACpB,CAAA;CACF,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAA;AAE5C,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,SAAS,EAAE,YAAY,EAAE,CAAA;IACzB,2EAA2E;IAC3E,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,CAAC,CAAC,EAAE;QAAC,WAAW,EAAE;YAAC,EAAE,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC,KAAK,IAAI,CAAA;IACrD,cAAc,EAAE,CAAC,CAAC,EAAE;QAAC,WAAW,EAAE,EAAE,CAAA;KAAC,KAAK,IAAI,CAAA;IAC9C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAC5B,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA"}