@loupfeed/react-native 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx","../src/tagging.ts"],"sourcesContent":["/**\n * @loupfeed/react-native — RN adapter with a native inspector overlay.\n *\n * There is no DOM, so:\n * - the Metro/Babel tagger (`@loupfeed/babel-plugin` with `attribute: 'fbId'`)\n * injects an opaque `fbId` prop on every JSX element;\n * - tagging threads that id onto the native view's `testID`/`accessibilityLabel`\n * AND registers a measurable ref (see ./tagging);\n * - the overlay hit-tests a tap against the registered views via\n * `measureInWindow` and reads the nearest `fbId`.\n *\n * Same opaque-id + server-manifest model as web; different carrier. Uses only\n * core RN APIs (no custom native module) so it runs in Expo Go.\n *\n * Works on BOTH JSX runtimes:\n * - automatic (React 17+/19 default) — set babel-preset-expo\n * `jsxImportSource: '@loupfeed/react-native'` so JSX routes through the\n * jsx-runtime shim (no `import React` needed); and\n * - classic — the `installTagging()` React.createElement patch (below).\n */\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\nimport { Dimensions, Modal, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';\nimport { captureFeedback, type FeedbackInput, type Recorder, type ReplayBuffer } from '@loupfeed/core';\nimport { installTagging, registry, type Measurable } from './tagging';\n\nexport { installTagging } from './tagging';\n\n// —— data layer shared with core (DOM-free) ——\nexport {\n init,\n getClient,\n close,\n captureFeedback,\n setUser,\n setContext,\n setTag,\n setTags,\n addBreadcrumb,\n withScope,\n getCurrentScope,\n setReplayRecorder,\n getActiveRecorder,\n} from '@loupfeed/core';\nexport type {\n InitOptions,\n FeedbackInput,\n FeedbackEvent,\n Breadcrumb,\n BreadcrumbLevel,\n User,\n Primitive,\n Recorder,\n ReplayBuffer,\n} from '@loupfeed/core';\n\ntype Rect = { x: number; y: number; w: number; h: number };\n\nfunction measure(m: Measurable): Promise<Rect> {\n return new Promise((resolve) => m.measureInWindow((x, y, w, h) => resolve({ x, y, w, h })));\n}\n\n/** Find the smallest (deepest) registered view whose window rect contains the point. */\nasync function hitTest(px: number, py: number): Promise<{ fbId: string; rect: Rect; tagName?: string } | null> {\n const measured = await Promise.all(\n [...registry.entries()].map(async ([fbId, v]) => ({ fbId, tagName: v.tagName, rect: await measure(v.instance) })),\n );\n const hits = measured.filter(\n (m) =>\n m.rect.w > 0 &&\n m.rect.h > 0 &&\n px >= m.rect.x &&\n px <= m.rect.x + m.rect.w &&\n py >= m.rect.y &&\n py <= m.rect.y + m.rect.h,\n );\n if (hits.length === 0) return null;\n hits.sort((a, b) => a.rect.w * a.rect.h - b.rect.w * b.rect.h);\n return hits[0]!;\n}\n\n// —— view-hierarchy (wireframe) recorder ——\n// The RN-native session replay: periodically serialize the registered view tree\n// (rects + masked text + basic style) into a rolling buffer; the dashboard\n// reconstructs a maskable wireframe. Pure JS (no native module, Expo Go/Snack\n// compatible), masked by default — unlike native screen recording.\n\nfunction maskText(t?: string): string | undefined {\n if (!t) return t;\n return t.replace(/\\S/g, '●'); // ● — privacy by default\n}\n\ntype WFView = { fb: string; x: number; y: number; w: number; h: number; tag?: string; text?: string; bg?: string; r?: number; color?: string };\ntype WFFrame = { t: number; views: WFView[] };\n\nlet wfFrames: WFFrame[] = [];\nlet wfTimer: ReturnType<typeof setInterval> | null = null;\nlet wfRecording = false;\nlet wfStartedAt = 0;\nlet wfBufferMs = 60_000;\n\nasync function captureWireframeFrame(): Promise<void> {\n const views = await Promise.all(\n [...registry.entries()].map(async ([fb, v]) => {\n const r = await measure(v.instance);\n return {\n fb,\n x: Math.round(r.x),\n y: Math.round(r.y),\n w: Math.round(r.w),\n h: Math.round(r.h),\n tag: v.tagName,\n text: maskText(v.text),\n bg: v.bg,\n r: v.radius,\n color: v.color,\n } as WFView;\n }),\n );\n wfFrames.push({ t: Date.now() - wfStartedAt, views: views.filter((vw) => vw.w > 0 && vw.h > 0) });\n const cutoff = Date.now() - wfStartedAt - wfBufferMs;\n while (wfFrames.length > 1 && (wfFrames[0]?.t ?? 0) < cutoff) wfFrames.shift();\n}\n\n/** RN view-hierarchy session recorder. Register with `setReplayRecorder(wireframeRecorder)`. */\nexport const wireframeRecorder: Recorder = {\n start() {\n if (wfRecording) return;\n wfRecording = true;\n wfStartedAt = Date.now();\n wfFrames = [];\n void captureWireframeFrame();\n wfTimer = setInterval(() => void captureWireframeFrame(), 500);\n },\n stop() {\n wfRecording = false;\n if (wfTimer) clearInterval(wfTimer);\n wfTimer = null;\n },\n isRecording() {\n return wfRecording;\n },\n async snapshot(): Promise<ReplayBuffer> {\n if (!wfRecording) return { replayId: '', durationMs: 0, kind: 'wireframe', frames: [] };\n await captureWireframeFrame();\n const { width, height } = Dimensions.get('window');\n // Drop empty frames (captured before refs mounted/measured) and rebase the\n // timeline so playback starts on the first populated frame, not a blank one.\n const populated = wfFrames.filter((f) => f.views.length > 0);\n const base = populated.length ? populated[0]!.t : 0;\n const frames = populated.map((f) => ({ t: f.t - base, views: f.views }));\n const durationMs = frames.length ? frames[frames.length - 1]!.t : 0;\n return {\n replayId: `wf_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`,\n durationMs,\n kind: 'wireframe',\n frames,\n viewport: { width, height },\n };\n },\n};\n\n// Patch at module load — before the app's first render.\ninstallTagging();\n\n/** Tooling/automation: the currently-registered tagged views. */\nexport function getRegistered(): Array<{ fbId: string; tagName?: string }> {\n return [...registry.entries()].map(([fbId, v]) => ({ fbId, tagName: v.tagName }));\n}\n\n/** Tooling/automation: measure a registered view's window rect. */\nexport async function measureFbId(fbId: string): Promise<Rect | null> {\n const v = registry.get(fbId);\n return v ? measure(v.instance) : null;\n}\n\ntype Mode = 'idle' | 'inspect' | 'compose';\ntype Target = { fbId: string; rect: Rect; tagName?: string };\n\ntype FeedbackApi = {\n openInspector(): void;\n closeInspector(): void;\n /** Capture at a window coordinate — what a tap in inspect mode does. Opens the composer. */\n inspectAt(px: number, py: number): Promise<void>;\n /** Submit the currently-inspected target (what the Send button does). */\n submitCurrent(text?: string): Promise<string>;\n /** Submit programmatically (no overlay). */\n submit(input: FeedbackInput): Promise<string>;\n isInspecting: boolean;\n};\n\nconst Ctx = createContext<FeedbackApi | null>(null);\n\nexport interface FeedbackRootProps {\n children: ReactNode;\n accentColor?: string;\n}\n\nexport function FeedbackRoot({ children, accentColor = '#6d28d9' }: FeedbackRootProps): JSX.Element {\n const [mode, setMode] = useState<Mode>('idle');\n const [target, setTarget] = useState<Target | null>(null);\n const [text, setText] = useState('');\n\n const reset = useCallback(() => {\n setMode('idle');\n setTarget(null);\n setText('');\n }, []);\n\n const inspectAt = useCallback(async (px: number, py: number) => {\n const hit = await hitTest(px, py);\n if (hit) {\n setTarget(hit);\n setMode('compose');\n }\n }, []);\n\n const onInspectTap = useCallback(\n (e: any) => {\n const ne = e.nativeEvent ?? {};\n void inspectAt(ne.pageX ?? ne.locationX ?? 0, ne.pageY ?? ne.locationY ?? 0);\n },\n [inspectAt],\n );\n\n const submitCurrent = useCallback(\n async (override?: string): Promise<string> => {\n const t = (override ?? text).trim();\n if (!target || !t) return '';\n const id = await captureFeedback({\n text: t,\n elementId: target.fbId,\n elementInfo: { tagName: target.tagName, rect: target.rect },\n });\n reset();\n return id;\n },\n [target, text, reset],\n );\n\n const api = useMemo<FeedbackApi>(\n () => ({\n openInspector: () => setMode('inspect'),\n closeInspector: () => reset(),\n inspectAt,\n submitCurrent,\n submit: (input) => captureFeedback(input),\n isInspecting: mode !== 'idle',\n }),\n [mode, reset, inspectAt, submitCurrent],\n );\n\n return (\n <Ctx.Provider value={api}>\n <View style={styles.flex}>\n {children}\n\n {target && mode === 'compose' && (\n <View\n pointerEvents=\"none\"\n style={[\n styles.highlight,\n { left: target.rect.x, top: target.rect.y, width: target.rect.w, height: target.rect.h, borderColor: accentColor },\n ]}\n />\n )}\n\n {mode === 'inspect' && (\n <View style={StyleSheet.absoluteFill} onStartShouldSetResponder={() => true} onResponderRelease={onInspectTap}>\n <View style={[styles.hint, { backgroundColor: accentColor }]}>\n <Text style={styles.hintText}>Tap an element to report</Text>\n </View>\n </View>\n )}\n\n <Pressable\n onPress={() => setMode((m) => (m === 'idle' ? 'inspect' : 'idle'))}\n style={[styles.launcher, { backgroundColor: mode !== 'idle' ? '#111827' : accentColor }]}\n testID=\"loupfeed-launcher\"\n >\n <Text style={styles.launcherText}>{mode === 'idle' ? 'Feedback' : 'Cancel'}</Text>\n </Pressable>\n\n <Modal visible={mode === 'compose'} transparent animationType=\"slide\" onRequestClose={reset}>\n <View style={styles.modalWrap}>\n <View style={styles.sheet}>\n <Text style={[styles.targetLabel, { color: accentColor }]}>\n {(target?.tagName ?? 'element') + ' · ' + (target?.fbId ?? '')}\n </Text>\n <TextInput\n value={text}\n onChangeText={setText}\n placeholder=\"What's wrong with this element?\"\n placeholderTextColor=\"#9ca3af\"\n multiline\n autoFocus\n style={styles.input}\n testID=\"loupfeed-input\"\n />\n <View style={styles.row}>\n <Pressable onPress={reset} style={styles.btnGhost}>\n <Text style={styles.btnGhostText}>Cancel</Text>\n </Pressable>\n <Pressable\n onPress={() => void submitCurrent()}\n style={[styles.btnPrimary, { backgroundColor: accentColor }]}\n testID=\"loupfeed-send\"\n >\n <Text style={styles.btnPrimaryText}>Send</Text>\n </Pressable>\n </View>\n </View>\n </View>\n </Modal>\n </View>\n </Ctx.Provider>\n );\n}\n\nexport function useFeedback(): FeedbackApi {\n const ctx = useContext(Ctx);\n if (!ctx) throw new Error('useFeedback() must be used within <FeedbackRoot>');\n return ctx;\n}\n\nconst styles = StyleSheet.create({\n flex: { flex: 1 },\n highlight: {\n position: 'absolute',\n borderWidth: 2,\n borderRadius: 4,\n backgroundColor: 'rgba(109,40,217,0.12)',\n },\n hint: {\n position: 'absolute',\n top: 56,\n alignSelf: 'center',\n paddingHorizontal: 12,\n paddingVertical: 6,\n borderRadius: 999,\n },\n hintText: { color: '#fff', fontWeight: '600', fontSize: 13 },\n launcher: {\n position: 'absolute',\n right: 16,\n bottom: 36,\n paddingHorizontal: 18,\n paddingVertical: 11,\n borderRadius: 999,\n shadowColor: '#000',\n shadowOpacity: 0.25,\n shadowRadius: 8,\n shadowOffset: { width: 0, height: 3 },\n elevation: 4,\n },\n launcherText: { color: '#fff', fontWeight: '700', fontSize: 14 },\n modalWrap: { flex: 1, justifyContent: 'flex-end', backgroundColor: 'rgba(0,0,0,0.35)' },\n sheet: { backgroundColor: '#fff', borderTopLeftRadius: 18, borderTopRightRadius: 18, padding: 18, paddingBottom: 34 },\n targetLabel: { fontFamily: 'Menlo', fontSize: 12, fontWeight: '700', marginBottom: 8 },\n input: {\n minHeight: 80,\n borderWidth: 1,\n borderColor: '#e4e4e7',\n borderRadius: 10,\n padding: 12,\n fontSize: 15,\n textAlignVertical: 'top',\n color: '#111827',\n },\n row: { flexDirection: 'row', justifyContent: 'flex-end', gap: 10, marginTop: 12 },\n btnGhost: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 10 },\n btnGhostText: { color: '#6b7280', fontWeight: '600', fontSize: 14 },\n btnPrimary: { paddingHorizontal: 18, paddingVertical: 10, borderRadius: 10 },\n btnPrimaryText: { color: '#fff', fontWeight: '700', fontSize: 14 },\n});\n","/**\n * Element tagging — shared by the classic createElement patch and the automatic\n * JSX runtime shim (jsx-runtime.ts). Threads the babel-injected `fbId` prop onto\n * the native view's testID/accessibilityLabel and registers a measurable handle\n * for hit-testing + the wireframe recorder.\n *\n * Runtime-agnostic so loupfeed works on BOTH:\n * - classic JSX (jsxRuntime: 'classic') → React.createElement patch, and\n * - automatic JSX (React 17+/19 default) → custom @jsxImportSource shim,\n * the latter being required by modern apps where files omit `import React`.\n */\nimport React from 'react';\nimport { StyleSheet } from 'react-native';\n\nexport type Measurable = {\n measureInWindow: (cb: (x: number, y: number, w: number, h: number) => void) => void;\n};\nexport type RegEntry = {\n instance: Measurable;\n tagName?: string;\n text?: string;\n bg?: string;\n radius?: number;\n color?: string;\n};\n\n/**\n * Shared registry of tagged, measurable views (keyed by fbId). Stored on\n * globalThis so the index entry (wireframe recorder / hit-test) and the\n * jsx-runtime shim share ONE instance even if the bundler duplicates this module\n * across the package's multiple entry points.\n */\nconst _g = globalThis as unknown as { __loupfeedRegistry__?: Map<string, RegEntry> };\nexport const registry: Map<string, RegEntry> = (_g.__loupfeedRegistry__ ??= new Map<string, RegEntry>());\n\nconst FORWARD_REF = Symbol.for('react.forward_ref');\nconst MEMO = Symbol.for('react.memo');\n\nfunction extractText(children: unknown): string | undefined {\n const arr = Array.isArray(children) ? children : children == null ? [] : [children];\n const t = arr\n .filter((c) => typeof c === 'string' || typeof c === 'number')\n .map(String)\n .join('');\n return t || undefined;\n}\n\n/**\n * Transform a tagged element's props: fbId → testID/accessibilityLabel + a ref\n * that (de)registers a measurable handle. Returns props unchanged when there's\n * no fbId. `childrenOverride` lets the classic patch pass its variadic children;\n * the automatic shim falls back to props.children.\n */\nexport function tagProps(type: any, props: any, childrenOverride?: unknown): any {\n if (!props || !props.fbId) return props;\n const fbId: string = props.fbId;\n const tagName = typeof type === 'string' ? type : type?.displayName || type?.name;\n const canRef =\n typeof type === 'string' ||\n (type != null && (type.$$typeof === FORWARD_REF || type.$$typeof === MEMO));\n\n const next: any = {\n ...props,\n testID: props.testID ?? fbId,\n accessibilityLabel: props.accessibilityLabel ?? fbId,\n };\n delete next.fbId;\n\n if (canRef) {\n const userRef = props.ref;\n const flat: any = props.style ? StyleSheet.flatten(props.style) : undefined;\n const text = extractText(childrenOverride !== undefined ? childrenOverride : props.children);\n next.ref = (instance: any) => {\n if (instance && typeof instance.measureInWindow === 'function') {\n registry.set(fbId, {\n instance,\n tagName,\n text,\n bg: flat?.backgroundColor,\n radius: typeof flat?.borderRadius === 'number' ? flat.borderRadius : undefined,\n color: flat?.color,\n });\n } else {\n registry.delete(fbId);\n }\n if (typeof userRef === 'function') userRef(instance);\n else if (userRef && typeof userRef === 'object') userRef.current = instance;\n };\n }\n return next;\n}\n\n/** Automatic JSX runtime entry point (used by jsx-runtime.ts / jsx-dev-runtime.ts). */\nexport function tagJsxProps(type: any, props: any): any {\n return tagProps(type, props);\n}\n\n/**\n * Classic JSX runtime: patch React.createElement. Idempotent across duplicated\n * bundles (flag on globalThis). Harmless (a no-op for tagging) under the\n * automatic runtime, where JSX never calls createElement.\n */\nexport function installTagging(): void {\n const gp = globalThis as unknown as { __loupfeedPatched__?: boolean };\n if (gp.__loupfeedPatched__) return;\n gp.__loupfeedPatched__ = true;\n const orig = React.createElement;\n (React as any).createElement = function (this: unknown, type: any, props: any, ...children: any[]) {\n if (props && props.fbId) {\n return orig.call(this, type, tagProps(type, props, children), ...children);\n }\n return orig.call(this, type, props, ...children);\n };\n}\n"],"mappings":"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","names":["import_react","import_react_native","React","import_core"]}
@@ -0,0 +1,66 @@
1
+ import { ReactNode } from 'react';
2
+ import { FeedbackInput, Recorder } from '@loupfeed/core';
3
+ export { Breadcrumb, BreadcrumbLevel, FeedbackEvent, FeedbackInput, InitOptions, Primitive, Recorder, ReplayBuffer, User, addBreadcrumb, captureFeedback, close, getActiveRecorder, getClient, getCurrentScope, init, setContext, setReplayRecorder, setTag, setTags, setUser, withScope } from '@loupfeed/core';
4
+
5
+ /**
6
+ * Classic JSX runtime: patch React.createElement. Idempotent across duplicated
7
+ * bundles (flag on globalThis). Harmless (a no-op for tagging) under the
8
+ * automatic runtime, where JSX never calls createElement.
9
+ */
10
+ declare function installTagging(): void;
11
+
12
+ /**
13
+ * @loupfeed/react-native — RN adapter with a native inspector overlay.
14
+ *
15
+ * There is no DOM, so:
16
+ * - the Metro/Babel tagger (`@loupfeed/babel-plugin` with `attribute: 'fbId'`)
17
+ * injects an opaque `fbId` prop on every JSX element;
18
+ * - tagging threads that id onto the native view's `testID`/`accessibilityLabel`
19
+ * AND registers a measurable ref (see ./tagging);
20
+ * - the overlay hit-tests a tap against the registered views via
21
+ * `measureInWindow` and reads the nearest `fbId`.
22
+ *
23
+ * Same opaque-id + server-manifest model as web; different carrier. Uses only
24
+ * core RN APIs (no custom native module) so it runs in Expo Go.
25
+ *
26
+ * Works on BOTH JSX runtimes:
27
+ * - automatic (React 17+/19 default) — set babel-preset-expo
28
+ * `jsxImportSource: '@loupfeed/react-native'` so JSX routes through the
29
+ * jsx-runtime shim (no `import React` needed); and
30
+ * - classic — the `installTagging()` React.createElement patch (below).
31
+ */
32
+
33
+ type Rect = {
34
+ x: number;
35
+ y: number;
36
+ w: number;
37
+ h: number;
38
+ };
39
+ /** RN view-hierarchy session recorder. Register with `setReplayRecorder(wireframeRecorder)`. */
40
+ declare const wireframeRecorder: Recorder;
41
+ /** Tooling/automation: the currently-registered tagged views. */
42
+ declare function getRegistered(): Array<{
43
+ fbId: string;
44
+ tagName?: string;
45
+ }>;
46
+ /** Tooling/automation: measure a registered view's window rect. */
47
+ declare function measureFbId(fbId: string): Promise<Rect | null>;
48
+ type FeedbackApi = {
49
+ openInspector(): void;
50
+ closeInspector(): void;
51
+ /** Capture at a window coordinate — what a tap in inspect mode does. Opens the composer. */
52
+ inspectAt(px: number, py: number): Promise<void>;
53
+ /** Submit the currently-inspected target (what the Send button does). */
54
+ submitCurrent(text?: string): Promise<string>;
55
+ /** Submit programmatically (no overlay). */
56
+ submit(input: FeedbackInput): Promise<string>;
57
+ isInspecting: boolean;
58
+ };
59
+ interface FeedbackRootProps {
60
+ children: ReactNode;
61
+ accentColor?: string;
62
+ }
63
+ declare function FeedbackRoot({ children, accentColor }: FeedbackRootProps): JSX.Element;
64
+ declare function useFeedback(): FeedbackApi;
65
+
66
+ export { FeedbackRoot, type FeedbackRootProps, getRegistered, installTagging, measureFbId, useFeedback, wireframeRecorder };
@@ -0,0 +1,66 @@
1
+ import { ReactNode } from 'react';
2
+ import { FeedbackInput, Recorder } from '@loupfeed/core';
3
+ export { Breadcrumb, BreadcrumbLevel, FeedbackEvent, FeedbackInput, InitOptions, Primitive, Recorder, ReplayBuffer, User, addBreadcrumb, captureFeedback, close, getActiveRecorder, getClient, getCurrentScope, init, setContext, setReplayRecorder, setTag, setTags, setUser, withScope } from '@loupfeed/core';
4
+
5
+ /**
6
+ * Classic JSX runtime: patch React.createElement. Idempotent across duplicated
7
+ * bundles (flag on globalThis). Harmless (a no-op for tagging) under the
8
+ * automatic runtime, where JSX never calls createElement.
9
+ */
10
+ declare function installTagging(): void;
11
+
12
+ /**
13
+ * @loupfeed/react-native — RN adapter with a native inspector overlay.
14
+ *
15
+ * There is no DOM, so:
16
+ * - the Metro/Babel tagger (`@loupfeed/babel-plugin` with `attribute: 'fbId'`)
17
+ * injects an opaque `fbId` prop on every JSX element;
18
+ * - tagging threads that id onto the native view's `testID`/`accessibilityLabel`
19
+ * AND registers a measurable ref (see ./tagging);
20
+ * - the overlay hit-tests a tap against the registered views via
21
+ * `measureInWindow` and reads the nearest `fbId`.
22
+ *
23
+ * Same opaque-id + server-manifest model as web; different carrier. Uses only
24
+ * core RN APIs (no custom native module) so it runs in Expo Go.
25
+ *
26
+ * Works on BOTH JSX runtimes:
27
+ * - automatic (React 17+/19 default) — set babel-preset-expo
28
+ * `jsxImportSource: '@loupfeed/react-native'` so JSX routes through the
29
+ * jsx-runtime shim (no `import React` needed); and
30
+ * - classic — the `installTagging()` React.createElement patch (below).
31
+ */
32
+
33
+ type Rect = {
34
+ x: number;
35
+ y: number;
36
+ w: number;
37
+ h: number;
38
+ };
39
+ /** RN view-hierarchy session recorder. Register with `setReplayRecorder(wireframeRecorder)`. */
40
+ declare const wireframeRecorder: Recorder;
41
+ /** Tooling/automation: the currently-registered tagged views. */
42
+ declare function getRegistered(): Array<{
43
+ fbId: string;
44
+ tagName?: string;
45
+ }>;
46
+ /** Tooling/automation: measure a registered view's window rect. */
47
+ declare function measureFbId(fbId: string): Promise<Rect | null>;
48
+ type FeedbackApi = {
49
+ openInspector(): void;
50
+ closeInspector(): void;
51
+ /** Capture at a window coordinate — what a tap in inspect mode does. Opens the composer. */
52
+ inspectAt(px: number, py: number): Promise<void>;
53
+ /** Submit the currently-inspected target (what the Send button does). */
54
+ submitCurrent(text?: string): Promise<string>;
55
+ /** Submit programmatically (no overlay). */
56
+ submit(input: FeedbackInput): Promise<string>;
57
+ isInspecting: boolean;
58
+ };
59
+ interface FeedbackRootProps {
60
+ children: ReactNode;
61
+ accentColor?: string;
62
+ }
63
+ declare function FeedbackRoot({ children, accentColor }: FeedbackRootProps): JSX.Element;
64
+ declare function useFeedback(): FeedbackApi;
65
+
66
+ export { FeedbackRoot, type FeedbackRootProps, getRegistered, installTagging, measureFbId, useFeedback, wireframeRecorder };
package/dist/index.js ADDED
@@ -0,0 +1,296 @@
1
+ import {
2
+ installTagging,
3
+ registry
4
+ } from "./chunk-NPNSWHPB.js";
5
+
6
+ // src/index.tsx
7
+ import {
8
+ createContext,
9
+ useCallback,
10
+ useContext,
11
+ useMemo,
12
+ useState
13
+ } from "react";
14
+ import { Dimensions, Modal, Pressable, StyleSheet, Text, TextInput, View } from "react-native";
15
+ import { captureFeedback } from "@loupfeed/core";
16
+ import {
17
+ init,
18
+ getClient,
19
+ close,
20
+ captureFeedback as captureFeedback2,
21
+ setUser,
22
+ setContext,
23
+ setTag,
24
+ setTags,
25
+ addBreadcrumb,
26
+ withScope,
27
+ getCurrentScope,
28
+ setReplayRecorder,
29
+ getActiveRecorder
30
+ } from "@loupfeed/core";
31
+ import { jsx, jsxs } from "react/jsx-runtime";
32
+ function measure(m) {
33
+ return new Promise((resolve) => m.measureInWindow((x, y, w, h) => resolve({ x, y, w, h })));
34
+ }
35
+ async function hitTest(px, py) {
36
+ const measured = await Promise.all(
37
+ [...registry.entries()].map(async ([fbId, v]) => ({ fbId, tagName: v.tagName, rect: await measure(v.instance) }))
38
+ );
39
+ const hits = measured.filter(
40
+ (m) => m.rect.w > 0 && m.rect.h > 0 && px >= m.rect.x && px <= m.rect.x + m.rect.w && py >= m.rect.y && py <= m.rect.y + m.rect.h
41
+ );
42
+ if (hits.length === 0) return null;
43
+ hits.sort((a, b) => a.rect.w * a.rect.h - b.rect.w * b.rect.h);
44
+ return hits[0];
45
+ }
46
+ function maskText(t) {
47
+ if (!t) return t;
48
+ return t.replace(/\S/g, "\u25CF");
49
+ }
50
+ var wfFrames = [];
51
+ var wfTimer = null;
52
+ var wfRecording = false;
53
+ var wfStartedAt = 0;
54
+ var wfBufferMs = 6e4;
55
+ async function captureWireframeFrame() {
56
+ const views = await Promise.all(
57
+ [...registry.entries()].map(async ([fb, v]) => {
58
+ const r = await measure(v.instance);
59
+ return {
60
+ fb,
61
+ x: Math.round(r.x),
62
+ y: Math.round(r.y),
63
+ w: Math.round(r.w),
64
+ h: Math.round(r.h),
65
+ tag: v.tagName,
66
+ text: maskText(v.text),
67
+ bg: v.bg,
68
+ r: v.radius,
69
+ color: v.color
70
+ };
71
+ })
72
+ );
73
+ wfFrames.push({ t: Date.now() - wfStartedAt, views: views.filter((vw) => vw.w > 0 && vw.h > 0) });
74
+ const cutoff = Date.now() - wfStartedAt - wfBufferMs;
75
+ while (wfFrames.length > 1 && (wfFrames[0]?.t ?? 0) < cutoff) wfFrames.shift();
76
+ }
77
+ var wireframeRecorder = {
78
+ start() {
79
+ if (wfRecording) return;
80
+ wfRecording = true;
81
+ wfStartedAt = Date.now();
82
+ wfFrames = [];
83
+ void captureWireframeFrame();
84
+ wfTimer = setInterval(() => void captureWireframeFrame(), 500);
85
+ },
86
+ stop() {
87
+ wfRecording = false;
88
+ if (wfTimer) clearInterval(wfTimer);
89
+ wfTimer = null;
90
+ },
91
+ isRecording() {
92
+ return wfRecording;
93
+ },
94
+ async snapshot() {
95
+ if (!wfRecording) return { replayId: "", durationMs: 0, kind: "wireframe", frames: [] };
96
+ await captureWireframeFrame();
97
+ const { width, height } = Dimensions.get("window");
98
+ const populated = wfFrames.filter((f) => f.views.length > 0);
99
+ const base = populated.length ? populated[0].t : 0;
100
+ const frames = populated.map((f) => ({ t: f.t - base, views: f.views }));
101
+ const durationMs = frames.length ? frames[frames.length - 1].t : 0;
102
+ return {
103
+ replayId: `wf_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`,
104
+ durationMs,
105
+ kind: "wireframe",
106
+ frames,
107
+ viewport: { width, height }
108
+ };
109
+ }
110
+ };
111
+ installTagging();
112
+ function getRegistered() {
113
+ return [...registry.entries()].map(([fbId, v]) => ({ fbId, tagName: v.tagName }));
114
+ }
115
+ async function measureFbId(fbId) {
116
+ const v = registry.get(fbId);
117
+ return v ? measure(v.instance) : null;
118
+ }
119
+ var Ctx = createContext(null);
120
+ function FeedbackRoot({ children, accentColor = "#6d28d9" }) {
121
+ const [mode, setMode] = useState("idle");
122
+ const [target, setTarget] = useState(null);
123
+ const [text, setText] = useState("");
124
+ const reset = useCallback(() => {
125
+ setMode("idle");
126
+ setTarget(null);
127
+ setText("");
128
+ }, []);
129
+ const inspectAt = useCallback(async (px, py) => {
130
+ const hit = await hitTest(px, py);
131
+ if (hit) {
132
+ setTarget(hit);
133
+ setMode("compose");
134
+ }
135
+ }, []);
136
+ const onInspectTap = useCallback(
137
+ (e) => {
138
+ const ne = e.nativeEvent ?? {};
139
+ void inspectAt(ne.pageX ?? ne.locationX ?? 0, ne.pageY ?? ne.locationY ?? 0);
140
+ },
141
+ [inspectAt]
142
+ );
143
+ const submitCurrent = useCallback(
144
+ async (override) => {
145
+ const t = (override ?? text).trim();
146
+ if (!target || !t) return "";
147
+ const id = await captureFeedback({
148
+ text: t,
149
+ elementId: target.fbId,
150
+ elementInfo: { tagName: target.tagName, rect: target.rect }
151
+ });
152
+ reset();
153
+ return id;
154
+ },
155
+ [target, text, reset]
156
+ );
157
+ const api = useMemo(
158
+ () => ({
159
+ openInspector: () => setMode("inspect"),
160
+ closeInspector: () => reset(),
161
+ inspectAt,
162
+ submitCurrent,
163
+ submit: (input) => captureFeedback(input),
164
+ isInspecting: mode !== "idle"
165
+ }),
166
+ [mode, reset, inspectAt, submitCurrent]
167
+ );
168
+ return /* @__PURE__ */ jsx(Ctx.Provider, { value: api, children: /* @__PURE__ */ jsxs(View, { style: styles.flex, children: [
169
+ children,
170
+ target && mode === "compose" && /* @__PURE__ */ jsx(
171
+ View,
172
+ {
173
+ pointerEvents: "none",
174
+ style: [
175
+ styles.highlight,
176
+ { left: target.rect.x, top: target.rect.y, width: target.rect.w, height: target.rect.h, borderColor: accentColor }
177
+ ]
178
+ }
179
+ ),
180
+ mode === "inspect" && /* @__PURE__ */ jsx(View, { style: StyleSheet.absoluteFill, onStartShouldSetResponder: () => true, onResponderRelease: onInspectTap, children: /* @__PURE__ */ jsx(View, { style: [styles.hint, { backgroundColor: accentColor }], children: /* @__PURE__ */ jsx(Text, { style: styles.hintText, children: "Tap an element to report" }) }) }),
181
+ /* @__PURE__ */ jsx(
182
+ Pressable,
183
+ {
184
+ onPress: () => setMode((m) => m === "idle" ? "inspect" : "idle"),
185
+ style: [styles.launcher, { backgroundColor: mode !== "idle" ? "#111827" : accentColor }],
186
+ testID: "loupfeed-launcher",
187
+ children: /* @__PURE__ */ jsx(Text, { style: styles.launcherText, children: mode === "idle" ? "Feedback" : "Cancel" })
188
+ }
189
+ ),
190
+ /* @__PURE__ */ jsx(Modal, { visible: mode === "compose", transparent: true, animationType: "slide", onRequestClose: reset, children: /* @__PURE__ */ jsx(View, { style: styles.modalWrap, children: /* @__PURE__ */ jsxs(View, { style: styles.sheet, children: [
191
+ /* @__PURE__ */ jsx(Text, { style: [styles.targetLabel, { color: accentColor }], children: (target?.tagName ?? "element") + " \xB7 " + (target?.fbId ?? "") }),
192
+ /* @__PURE__ */ jsx(
193
+ TextInput,
194
+ {
195
+ value: text,
196
+ onChangeText: setText,
197
+ placeholder: "What's wrong with this element?",
198
+ placeholderTextColor: "#9ca3af",
199
+ multiline: true,
200
+ autoFocus: true,
201
+ style: styles.input,
202
+ testID: "loupfeed-input"
203
+ }
204
+ ),
205
+ /* @__PURE__ */ jsxs(View, { style: styles.row, children: [
206
+ /* @__PURE__ */ jsx(Pressable, { onPress: reset, style: styles.btnGhost, children: /* @__PURE__ */ jsx(Text, { style: styles.btnGhostText, children: "Cancel" }) }),
207
+ /* @__PURE__ */ jsx(
208
+ Pressable,
209
+ {
210
+ onPress: () => void submitCurrent(),
211
+ style: [styles.btnPrimary, { backgroundColor: accentColor }],
212
+ testID: "loupfeed-send",
213
+ children: /* @__PURE__ */ jsx(Text, { style: styles.btnPrimaryText, children: "Send" })
214
+ }
215
+ )
216
+ ] })
217
+ ] }) }) })
218
+ ] }) });
219
+ }
220
+ function useFeedback() {
221
+ const ctx = useContext(Ctx);
222
+ if (!ctx) throw new Error("useFeedback() must be used within <FeedbackRoot>");
223
+ return ctx;
224
+ }
225
+ var styles = StyleSheet.create({
226
+ flex: { flex: 1 },
227
+ highlight: {
228
+ position: "absolute",
229
+ borderWidth: 2,
230
+ borderRadius: 4,
231
+ backgroundColor: "rgba(109,40,217,0.12)"
232
+ },
233
+ hint: {
234
+ position: "absolute",
235
+ top: 56,
236
+ alignSelf: "center",
237
+ paddingHorizontal: 12,
238
+ paddingVertical: 6,
239
+ borderRadius: 999
240
+ },
241
+ hintText: { color: "#fff", fontWeight: "600", fontSize: 13 },
242
+ launcher: {
243
+ position: "absolute",
244
+ right: 16,
245
+ bottom: 36,
246
+ paddingHorizontal: 18,
247
+ paddingVertical: 11,
248
+ borderRadius: 999,
249
+ shadowColor: "#000",
250
+ shadowOpacity: 0.25,
251
+ shadowRadius: 8,
252
+ shadowOffset: { width: 0, height: 3 },
253
+ elevation: 4
254
+ },
255
+ launcherText: { color: "#fff", fontWeight: "700", fontSize: 14 },
256
+ modalWrap: { flex: 1, justifyContent: "flex-end", backgroundColor: "rgba(0,0,0,0.35)" },
257
+ sheet: { backgroundColor: "#fff", borderTopLeftRadius: 18, borderTopRightRadius: 18, padding: 18, paddingBottom: 34 },
258
+ targetLabel: { fontFamily: "Menlo", fontSize: 12, fontWeight: "700", marginBottom: 8 },
259
+ input: {
260
+ minHeight: 80,
261
+ borderWidth: 1,
262
+ borderColor: "#e4e4e7",
263
+ borderRadius: 10,
264
+ padding: 12,
265
+ fontSize: 15,
266
+ textAlignVertical: "top",
267
+ color: "#111827"
268
+ },
269
+ row: { flexDirection: "row", justifyContent: "flex-end", gap: 10, marginTop: 12 },
270
+ btnGhost: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 10 },
271
+ btnGhostText: { color: "#6b7280", fontWeight: "600", fontSize: 14 },
272
+ btnPrimary: { paddingHorizontal: 18, paddingVertical: 10, borderRadius: 10 },
273
+ btnPrimaryText: { color: "#fff", fontWeight: "700", fontSize: 14 }
274
+ });
275
+ export {
276
+ FeedbackRoot,
277
+ addBreadcrumb,
278
+ captureFeedback2 as captureFeedback,
279
+ close,
280
+ getActiveRecorder,
281
+ getClient,
282
+ getCurrentScope,
283
+ getRegistered,
284
+ init,
285
+ installTagging,
286
+ measureFbId,
287
+ setContext,
288
+ setReplayRecorder,
289
+ setTag,
290
+ setTags,
291
+ setUser,
292
+ useFeedback,
293
+ wireframeRecorder,
294
+ withScope
295
+ };
296
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["/**\n * @loupfeed/react-native — RN adapter with a native inspector overlay.\n *\n * There is no DOM, so:\n * - the Metro/Babel tagger (`@loupfeed/babel-plugin` with `attribute: 'fbId'`)\n * injects an opaque `fbId` prop on every JSX element;\n * - tagging threads that id onto the native view's `testID`/`accessibilityLabel`\n * AND registers a measurable ref (see ./tagging);\n * - the overlay hit-tests a tap against the registered views via\n * `measureInWindow` and reads the nearest `fbId`.\n *\n * Same opaque-id + server-manifest model as web; different carrier. Uses only\n * core RN APIs (no custom native module) so it runs in Expo Go.\n *\n * Works on BOTH JSX runtimes:\n * - automatic (React 17+/19 default) — set babel-preset-expo\n * `jsxImportSource: '@loupfeed/react-native'` so JSX routes through the\n * jsx-runtime shim (no `import React` needed); and\n * - classic — the `installTagging()` React.createElement patch (below).\n */\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\nimport { Dimensions, Modal, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';\nimport { captureFeedback, type FeedbackInput, type Recorder, type ReplayBuffer } from '@loupfeed/core';\nimport { installTagging, registry, type Measurable } from './tagging';\n\nexport { installTagging } from './tagging';\n\n// —— data layer shared with core (DOM-free) ——\nexport {\n init,\n getClient,\n close,\n captureFeedback,\n setUser,\n setContext,\n setTag,\n setTags,\n addBreadcrumb,\n withScope,\n getCurrentScope,\n setReplayRecorder,\n getActiveRecorder,\n} from '@loupfeed/core';\nexport type {\n InitOptions,\n FeedbackInput,\n FeedbackEvent,\n Breadcrumb,\n BreadcrumbLevel,\n User,\n Primitive,\n Recorder,\n ReplayBuffer,\n} from '@loupfeed/core';\n\ntype Rect = { x: number; y: number; w: number; h: number };\n\nfunction measure(m: Measurable): Promise<Rect> {\n return new Promise((resolve) => m.measureInWindow((x, y, w, h) => resolve({ x, y, w, h })));\n}\n\n/** Find the smallest (deepest) registered view whose window rect contains the point. */\nasync function hitTest(px: number, py: number): Promise<{ fbId: string; rect: Rect; tagName?: string } | null> {\n const measured = await Promise.all(\n [...registry.entries()].map(async ([fbId, v]) => ({ fbId, tagName: v.tagName, rect: await measure(v.instance) })),\n );\n const hits = measured.filter(\n (m) =>\n m.rect.w > 0 &&\n m.rect.h > 0 &&\n px >= m.rect.x &&\n px <= m.rect.x + m.rect.w &&\n py >= m.rect.y &&\n py <= m.rect.y + m.rect.h,\n );\n if (hits.length === 0) return null;\n hits.sort((a, b) => a.rect.w * a.rect.h - b.rect.w * b.rect.h);\n return hits[0]!;\n}\n\n// —— view-hierarchy (wireframe) recorder ——\n// The RN-native session replay: periodically serialize the registered view tree\n// (rects + masked text + basic style) into a rolling buffer; the dashboard\n// reconstructs a maskable wireframe. Pure JS (no native module, Expo Go/Snack\n// compatible), masked by default — unlike native screen recording.\n\nfunction maskText(t?: string): string | undefined {\n if (!t) return t;\n return t.replace(/\\S/g, '●'); // ● — privacy by default\n}\n\ntype WFView = { fb: string; x: number; y: number; w: number; h: number; tag?: string; text?: string; bg?: string; r?: number; color?: string };\ntype WFFrame = { t: number; views: WFView[] };\n\nlet wfFrames: WFFrame[] = [];\nlet wfTimer: ReturnType<typeof setInterval> | null = null;\nlet wfRecording = false;\nlet wfStartedAt = 0;\nlet wfBufferMs = 60_000;\n\nasync function captureWireframeFrame(): Promise<void> {\n const views = await Promise.all(\n [...registry.entries()].map(async ([fb, v]) => {\n const r = await measure(v.instance);\n return {\n fb,\n x: Math.round(r.x),\n y: Math.round(r.y),\n w: Math.round(r.w),\n h: Math.round(r.h),\n tag: v.tagName,\n text: maskText(v.text),\n bg: v.bg,\n r: v.radius,\n color: v.color,\n } as WFView;\n }),\n );\n wfFrames.push({ t: Date.now() - wfStartedAt, views: views.filter((vw) => vw.w > 0 && vw.h > 0) });\n const cutoff = Date.now() - wfStartedAt - wfBufferMs;\n while (wfFrames.length > 1 && (wfFrames[0]?.t ?? 0) < cutoff) wfFrames.shift();\n}\n\n/** RN view-hierarchy session recorder. Register with `setReplayRecorder(wireframeRecorder)`. */\nexport const wireframeRecorder: Recorder = {\n start() {\n if (wfRecording) return;\n wfRecording = true;\n wfStartedAt = Date.now();\n wfFrames = [];\n void captureWireframeFrame();\n wfTimer = setInterval(() => void captureWireframeFrame(), 500);\n },\n stop() {\n wfRecording = false;\n if (wfTimer) clearInterval(wfTimer);\n wfTimer = null;\n },\n isRecording() {\n return wfRecording;\n },\n async snapshot(): Promise<ReplayBuffer> {\n if (!wfRecording) return { replayId: '', durationMs: 0, kind: 'wireframe', frames: [] };\n await captureWireframeFrame();\n const { width, height } = Dimensions.get('window');\n // Drop empty frames (captured before refs mounted/measured) and rebase the\n // timeline so playback starts on the first populated frame, not a blank one.\n const populated = wfFrames.filter((f) => f.views.length > 0);\n const base = populated.length ? populated[0]!.t : 0;\n const frames = populated.map((f) => ({ t: f.t - base, views: f.views }));\n const durationMs = frames.length ? frames[frames.length - 1]!.t : 0;\n return {\n replayId: `wf_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`,\n durationMs,\n kind: 'wireframe',\n frames,\n viewport: { width, height },\n };\n },\n};\n\n// Patch at module load — before the app's first render.\ninstallTagging();\n\n/** Tooling/automation: the currently-registered tagged views. */\nexport function getRegistered(): Array<{ fbId: string; tagName?: string }> {\n return [...registry.entries()].map(([fbId, v]) => ({ fbId, tagName: v.tagName }));\n}\n\n/** Tooling/automation: measure a registered view's window rect. */\nexport async function measureFbId(fbId: string): Promise<Rect | null> {\n const v = registry.get(fbId);\n return v ? measure(v.instance) : null;\n}\n\ntype Mode = 'idle' | 'inspect' | 'compose';\ntype Target = { fbId: string; rect: Rect; tagName?: string };\n\ntype FeedbackApi = {\n openInspector(): void;\n closeInspector(): void;\n /** Capture at a window coordinate — what a tap in inspect mode does. Opens the composer. */\n inspectAt(px: number, py: number): Promise<void>;\n /** Submit the currently-inspected target (what the Send button does). */\n submitCurrent(text?: string): Promise<string>;\n /** Submit programmatically (no overlay). */\n submit(input: FeedbackInput): Promise<string>;\n isInspecting: boolean;\n};\n\nconst Ctx = createContext<FeedbackApi | null>(null);\n\nexport interface FeedbackRootProps {\n children: ReactNode;\n accentColor?: string;\n}\n\nexport function FeedbackRoot({ children, accentColor = '#6d28d9' }: FeedbackRootProps): JSX.Element {\n const [mode, setMode] = useState<Mode>('idle');\n const [target, setTarget] = useState<Target | null>(null);\n const [text, setText] = useState('');\n\n const reset = useCallback(() => {\n setMode('idle');\n setTarget(null);\n setText('');\n }, []);\n\n const inspectAt = useCallback(async (px: number, py: number) => {\n const hit = await hitTest(px, py);\n if (hit) {\n setTarget(hit);\n setMode('compose');\n }\n }, []);\n\n const onInspectTap = useCallback(\n (e: any) => {\n const ne = e.nativeEvent ?? {};\n void inspectAt(ne.pageX ?? ne.locationX ?? 0, ne.pageY ?? ne.locationY ?? 0);\n },\n [inspectAt],\n );\n\n const submitCurrent = useCallback(\n async (override?: string): Promise<string> => {\n const t = (override ?? text).trim();\n if (!target || !t) return '';\n const id = await captureFeedback({\n text: t,\n elementId: target.fbId,\n elementInfo: { tagName: target.tagName, rect: target.rect },\n });\n reset();\n return id;\n },\n [target, text, reset],\n );\n\n const api = useMemo<FeedbackApi>(\n () => ({\n openInspector: () => setMode('inspect'),\n closeInspector: () => reset(),\n inspectAt,\n submitCurrent,\n submit: (input) => captureFeedback(input),\n isInspecting: mode !== 'idle',\n }),\n [mode, reset, inspectAt, submitCurrent],\n );\n\n return (\n <Ctx.Provider value={api}>\n <View style={styles.flex}>\n {children}\n\n {target && mode === 'compose' && (\n <View\n pointerEvents=\"none\"\n style={[\n styles.highlight,\n { left: target.rect.x, top: target.rect.y, width: target.rect.w, height: target.rect.h, borderColor: accentColor },\n ]}\n />\n )}\n\n {mode === 'inspect' && (\n <View style={StyleSheet.absoluteFill} onStartShouldSetResponder={() => true} onResponderRelease={onInspectTap}>\n <View style={[styles.hint, { backgroundColor: accentColor }]}>\n <Text style={styles.hintText}>Tap an element to report</Text>\n </View>\n </View>\n )}\n\n <Pressable\n onPress={() => setMode((m) => (m === 'idle' ? 'inspect' : 'idle'))}\n style={[styles.launcher, { backgroundColor: mode !== 'idle' ? '#111827' : accentColor }]}\n testID=\"loupfeed-launcher\"\n >\n <Text style={styles.launcherText}>{mode === 'idle' ? 'Feedback' : 'Cancel'}</Text>\n </Pressable>\n\n <Modal visible={mode === 'compose'} transparent animationType=\"slide\" onRequestClose={reset}>\n <View style={styles.modalWrap}>\n <View style={styles.sheet}>\n <Text style={[styles.targetLabel, { color: accentColor }]}>\n {(target?.tagName ?? 'element') + ' · ' + (target?.fbId ?? '')}\n </Text>\n <TextInput\n value={text}\n onChangeText={setText}\n placeholder=\"What's wrong with this element?\"\n placeholderTextColor=\"#9ca3af\"\n multiline\n autoFocus\n style={styles.input}\n testID=\"loupfeed-input\"\n />\n <View style={styles.row}>\n <Pressable onPress={reset} style={styles.btnGhost}>\n <Text style={styles.btnGhostText}>Cancel</Text>\n </Pressable>\n <Pressable\n onPress={() => void submitCurrent()}\n style={[styles.btnPrimary, { backgroundColor: accentColor }]}\n testID=\"loupfeed-send\"\n >\n <Text style={styles.btnPrimaryText}>Send</Text>\n </Pressable>\n </View>\n </View>\n </View>\n </Modal>\n </View>\n </Ctx.Provider>\n );\n}\n\nexport function useFeedback(): FeedbackApi {\n const ctx = useContext(Ctx);\n if (!ctx) throw new Error('useFeedback() must be used within <FeedbackRoot>');\n return ctx;\n}\n\nconst styles = StyleSheet.create({\n flex: { flex: 1 },\n highlight: {\n position: 'absolute',\n borderWidth: 2,\n borderRadius: 4,\n backgroundColor: 'rgba(109,40,217,0.12)',\n },\n hint: {\n position: 'absolute',\n top: 56,\n alignSelf: 'center',\n paddingHorizontal: 12,\n paddingVertical: 6,\n borderRadius: 999,\n },\n hintText: { color: '#fff', fontWeight: '600', fontSize: 13 },\n launcher: {\n position: 'absolute',\n right: 16,\n bottom: 36,\n paddingHorizontal: 18,\n paddingVertical: 11,\n borderRadius: 999,\n shadowColor: '#000',\n shadowOpacity: 0.25,\n shadowRadius: 8,\n shadowOffset: { width: 0, height: 3 },\n elevation: 4,\n },\n launcherText: { color: '#fff', fontWeight: '700', fontSize: 14 },\n modalWrap: { flex: 1, justifyContent: 'flex-end', backgroundColor: 'rgba(0,0,0,0.35)' },\n sheet: { backgroundColor: '#fff', borderTopLeftRadius: 18, borderTopRightRadius: 18, padding: 18, paddingBottom: 34 },\n targetLabel: { fontFamily: 'Menlo', fontSize: 12, fontWeight: '700', marginBottom: 8 },\n input: {\n minHeight: 80,\n borderWidth: 1,\n borderColor: '#e4e4e7',\n borderRadius: 10,\n padding: 12,\n fontSize: 15,\n textAlignVertical: 'top',\n color: '#111827',\n },\n row: { flexDirection: 'row', justifyContent: 'flex-end', gap: 10, marginTop: 12 },\n btnGhost: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 10 },\n btnGhostText: { color: '#6b7280', fontWeight: '600', fontSize: 14 },\n btnPrimary: { paddingHorizontal: 18, paddingVertical: 10, borderRadius: 10 },\n btnPrimaryText: { color: '#fff', fontWeight: '700', fontSize: 14 },\n});\n"],"mappings":"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","names":["captureFeedback"]}
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/jsx-dev-runtime.ts
31
+ var jsx_dev_runtime_exports = {};
32
+ __export(jsx_dev_runtime_exports, {
33
+ Fragment: () => import_jsx_dev_runtime.Fragment,
34
+ jsxDEV: () => jsxDEV
35
+ });
36
+ module.exports = __toCommonJS(jsx_dev_runtime_exports);
37
+ var import_jsx_dev_runtime = require("react/jsx-dev-runtime");
38
+
39
+ // src/tagging.ts
40
+ var import_react = __toESM(require("react"), 1);
41
+ var import_react_native = require("react-native");
42
+ var _g = globalThis;
43
+ var registry = _g.__loupfeedRegistry__ ?? (_g.__loupfeedRegistry__ = /* @__PURE__ */ new Map());
44
+ var FORWARD_REF = /* @__PURE__ */ Symbol.for("react.forward_ref");
45
+ var MEMO = /* @__PURE__ */ Symbol.for("react.memo");
46
+ function extractText(children) {
47
+ const arr = Array.isArray(children) ? children : children == null ? [] : [children];
48
+ const t = arr.filter((c) => typeof c === "string" || typeof c === "number").map(String).join("");
49
+ return t || void 0;
50
+ }
51
+ function tagProps(type, props, childrenOverride) {
52
+ if (!props || !props.fbId) return props;
53
+ const fbId = props.fbId;
54
+ const tagName = typeof type === "string" ? type : type?.displayName || type?.name;
55
+ const canRef = typeof type === "string" || type != null && (type.$$typeof === FORWARD_REF || type.$$typeof === MEMO);
56
+ const next = {
57
+ ...props,
58
+ testID: props.testID ?? fbId,
59
+ accessibilityLabel: props.accessibilityLabel ?? fbId
60
+ };
61
+ delete next.fbId;
62
+ if (canRef) {
63
+ const userRef = props.ref;
64
+ const flat = props.style ? import_react_native.StyleSheet.flatten(props.style) : void 0;
65
+ const text = extractText(childrenOverride !== void 0 ? childrenOverride : props.children);
66
+ next.ref = (instance) => {
67
+ if (instance && typeof instance.measureInWindow === "function") {
68
+ registry.set(fbId, {
69
+ instance,
70
+ tagName,
71
+ text,
72
+ bg: flat?.backgroundColor,
73
+ radius: typeof flat?.borderRadius === "number" ? flat.borderRadius : void 0,
74
+ color: flat?.color
75
+ });
76
+ } else {
77
+ registry.delete(fbId);
78
+ }
79
+ if (typeof userRef === "function") userRef(instance);
80
+ else if (userRef && typeof userRef === "object") userRef.current = instance;
81
+ };
82
+ }
83
+ return next;
84
+ }
85
+ function tagJsxProps(type, props) {
86
+ return tagProps(type, props);
87
+ }
88
+
89
+ // src/jsx-dev-runtime.ts
90
+ function jsxDEV(type, props, key, isStaticChildren, source, self) {
91
+ return (0, import_jsx_dev_runtime.jsxDEV)(
92
+ type,
93
+ props && props.fbId ? tagJsxProps(type, props) : props,
94
+ key,
95
+ isStaticChildren,
96
+ source,
97
+ self
98
+ );
99
+ }
100
+ // Annotate the CommonJS export names for ESM import in node:
101
+ 0 && (module.exports = {
102
+ Fragment,
103
+ jsxDEV
104
+ });
105
+ //# sourceMappingURL=jsx-dev-runtime.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jsx-dev-runtime.ts","../src/tagging.ts"],"sourcesContent":["/**\n * Dev counterpart of jsx-runtime.ts (used by the automatic runtime in dev builds,\n * `@jsxImportSource @loupfeed/react-native`). Wraps react/jsx-dev-runtime's jsxDEV.\n */\nimport { jsxDEV as _jsxDEV, Fragment } from 'react/jsx-dev-runtime';\nimport { tagJsxProps } from './tagging';\n\nexport { Fragment };\n\nexport function jsxDEV(\n type: any,\n props: any,\n key: any,\n isStaticChildren: any,\n source: any,\n self: any,\n): any {\n return _jsxDEV(\n type,\n props && props.fbId ? tagJsxProps(type, props) : props,\n key,\n isStaticChildren,\n source,\n self,\n );\n}\n","/**\n * Element tagging — shared by the classic createElement patch and the automatic\n * JSX runtime shim (jsx-runtime.ts). Threads the babel-injected `fbId` prop onto\n * the native view's testID/accessibilityLabel and registers a measurable handle\n * for hit-testing + the wireframe recorder.\n *\n * Runtime-agnostic so loupfeed works on BOTH:\n * - classic JSX (jsxRuntime: 'classic') → React.createElement patch, and\n * - automatic JSX (React 17+/19 default) → custom @jsxImportSource shim,\n * the latter being required by modern apps where files omit `import React`.\n */\nimport React from 'react';\nimport { StyleSheet } from 'react-native';\n\nexport type Measurable = {\n measureInWindow: (cb: (x: number, y: number, w: number, h: number) => void) => void;\n};\nexport type RegEntry = {\n instance: Measurable;\n tagName?: string;\n text?: string;\n bg?: string;\n radius?: number;\n color?: string;\n};\n\n/**\n * Shared registry of tagged, measurable views (keyed by fbId). Stored on\n * globalThis so the index entry (wireframe recorder / hit-test) and the\n * jsx-runtime shim share ONE instance even if the bundler duplicates this module\n * across the package's multiple entry points.\n */\nconst _g = globalThis as unknown as { __loupfeedRegistry__?: Map<string, RegEntry> };\nexport const registry: Map<string, RegEntry> = (_g.__loupfeedRegistry__ ??= new Map<string, RegEntry>());\n\nconst FORWARD_REF = Symbol.for('react.forward_ref');\nconst MEMO = Symbol.for('react.memo');\n\nfunction extractText(children: unknown): string | undefined {\n const arr = Array.isArray(children) ? children : children == null ? [] : [children];\n const t = arr\n .filter((c) => typeof c === 'string' || typeof c === 'number')\n .map(String)\n .join('');\n return t || undefined;\n}\n\n/**\n * Transform a tagged element's props: fbId → testID/accessibilityLabel + a ref\n * that (de)registers a measurable handle. Returns props unchanged when there's\n * no fbId. `childrenOverride` lets the classic patch pass its variadic children;\n * the automatic shim falls back to props.children.\n */\nexport function tagProps(type: any, props: any, childrenOverride?: unknown): any {\n if (!props || !props.fbId) return props;\n const fbId: string = props.fbId;\n const tagName = typeof type === 'string' ? type : type?.displayName || type?.name;\n const canRef =\n typeof type === 'string' ||\n (type != null && (type.$$typeof === FORWARD_REF || type.$$typeof === MEMO));\n\n const next: any = {\n ...props,\n testID: props.testID ?? fbId,\n accessibilityLabel: props.accessibilityLabel ?? fbId,\n };\n delete next.fbId;\n\n if (canRef) {\n const userRef = props.ref;\n const flat: any = props.style ? StyleSheet.flatten(props.style) : undefined;\n const text = extractText(childrenOverride !== undefined ? childrenOverride : props.children);\n next.ref = (instance: any) => {\n if (instance && typeof instance.measureInWindow === 'function') {\n registry.set(fbId, {\n instance,\n tagName,\n text,\n bg: flat?.backgroundColor,\n radius: typeof flat?.borderRadius === 'number' ? flat.borderRadius : undefined,\n color: flat?.color,\n });\n } else {\n registry.delete(fbId);\n }\n if (typeof userRef === 'function') userRef(instance);\n else if (userRef && typeof userRef === 'object') userRef.current = instance;\n };\n }\n return next;\n}\n\n/** Automatic JSX runtime entry point (used by jsx-runtime.ts / jsx-dev-runtime.ts). */\nexport function tagJsxProps(type: any, props: any): any {\n return tagProps(type, props);\n}\n\n/**\n * Classic JSX runtime: patch React.createElement. Idempotent across duplicated\n * bundles (flag on globalThis). Harmless (a no-op for tagging) under the\n * automatic runtime, where JSX never calls createElement.\n */\nexport function installTagging(): void {\n const gp = globalThis as unknown as { __loupfeedPatched__?: boolean };\n if (gp.__loupfeedPatched__) return;\n gp.__loupfeedPatched__ = true;\n const orig = React.createElement;\n (React as any).createElement = function (this: unknown, type: any, props: any, ...children: any[]) {\n if (props && props.fbId) {\n return orig.call(this, type, tagProps(type, props, children), ...children);\n }\n return orig.call(this, type, props, ...children);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,6BAA4C;;;ACO5C,mBAAkB;AAClB,0BAA2B;AAoB3B,IAAM,KAAK;AACJ,IAAM,WAAmC,GAAG,yBAAH,GAAG,uBAAyB,oBAAI,IAAsB;AAEtG,IAAM,cAAc,uBAAO,IAAI,mBAAmB;AAClD,IAAM,OAAO,uBAAO,IAAI,YAAY;AAEpC,SAAS,YAAY,UAAuC;AAC1D,QAAM,MAAM,MAAM,QAAQ,QAAQ,IAAI,WAAW,YAAY,OAAO,CAAC,IAAI,CAAC,QAAQ;AAClF,QAAM,IAAI,IACP,OAAO,CAAC,MAAM,OAAO,MAAM,YAAY,OAAO,MAAM,QAAQ,EAC5D,IAAI,MAAM,EACV,KAAK,EAAE;AACV,SAAO,KAAK;AACd;AAQO,SAAS,SAAS,MAAW,OAAY,kBAAiC;AAC/E,MAAI,CAAC,SAAS,CAAC,MAAM,KAAM,QAAO;AAClC,QAAM,OAAe,MAAM;AAC3B,QAAM,UAAU,OAAO,SAAS,WAAW,OAAO,MAAM,eAAe,MAAM;AAC7E,QAAM,SACJ,OAAO,SAAS,YACf,QAAQ,SAAS,KAAK,aAAa,eAAe,KAAK,aAAa;AAEvE,QAAM,OAAY;AAAA,IAChB,GAAG;AAAA,IACH,QAAQ,MAAM,UAAU;AAAA,IACxB,oBAAoB,MAAM,sBAAsB;AAAA,EAClD;AACA,SAAO,KAAK;AAEZ,MAAI,QAAQ;AACV,UAAM,UAAU,MAAM;AACtB,UAAM,OAAY,MAAM,QAAQ,+BAAW,QAAQ,MAAM,KAAK,IAAI;AAClE,UAAM,OAAO,YAAY,qBAAqB,SAAY,mBAAmB,MAAM,QAAQ;AAC3F,SAAK,MAAM,CAAC,aAAkB;AAC5B,UAAI,YAAY,OAAO,SAAS,oBAAoB,YAAY;AAC9D,iBAAS,IAAI,MAAM;AAAA,UACjB;AAAA,UACA;AAAA,UACA;AAAA,UACA,IAAI,MAAM;AAAA,UACV,QAAQ,OAAO,MAAM,iBAAiB,WAAW,KAAK,eAAe;AAAA,UACrE,OAAO,MAAM;AAAA,QACf,CAAC;AAAA,MACH,OAAO;AACL,iBAAS,OAAO,IAAI;AAAA,MACtB;AACA,UAAI,OAAO,YAAY,WAAY,SAAQ,QAAQ;AAAA,eAC1C,WAAW,OAAO,YAAY,SAAU,SAAQ,UAAU;AAAA,IACrE;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,YAAY,MAAW,OAAiB;AACtD,SAAO,SAAS,MAAM,KAAK;AAC7B;;;ADtFO,SAAS,OACd,MACA,OACA,KACA,kBACA,QACA,MACK;AACL,aAAO,uBAAAA;AAAA,IACL;AAAA,IACA,SAAS,MAAM,OAAO,YAAY,MAAM,KAAK,IAAI;AAAA,IACjD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":["_jsxDEV"]}