@verbumia/feedback 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5RTFWOGT.js → chunk-7KWEI55W.js} +62 -3
- package/dist/chunk-7KWEI55W.js.map +1 -0
- package/dist/{client-qgDSbz3A.d.cts → client-D83qhH0O.d.cts} +73 -1
- package/dist/{client-qgDSbz3A.d.ts → client-D83qhH0O.d.ts} +73 -1
- package/dist/core/index.cjs +61 -2
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/{keys-CEWu0Htb.d.ts → keys-BpVB1kcI.d.ts} +1 -1
- package/dist/{keys-BhuK_fy1.d.cts → keys-D4oJtn64.d.cts} +1 -1
- package/dist/native/index.cjs +162 -23
- package/dist/native/index.cjs.map +1 -1
- package/dist/native/index.d.cts +48 -4
- package/dist/native/index.d.ts +48 -4
- package/dist/native/index.js +102 -22
- package/dist/native/index.js.map +1 -1
- package/dist/react/index.cjs +162 -23
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +119 -4
- package/dist/react/index.d.ts +119 -4
- package/dist/react/index.js +102 -22
- package/dist/react/index.js.map +1 -1
- package/dist/svelte/index.cjs +61 -2
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.d.cts +2 -2
- package/dist/svelte/index.d.ts +2 -2
- package/dist/svelte/index.js +1 -1
- package/dist/vue/index.cjs +61 -2
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.d.cts +2 -2
- package/dist/vue/index.d.ts +2 -2
- package/dist/vue/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5RTFWOGT.js.map +0 -1
package/dist/native/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { F as FeedbackClient, D as DeclaredKey } from '../client-
|
|
3
|
-
export { B as BatchResponse, a as FeedbackConfig, b as FeedbackError, c as FeedbackString, R as RatingInput, S as StringsResponse, d as SuggestionInput, T as TokenBundle } from '../client-
|
|
2
|
+
import { F as FeedbackClient, D as DeclaredKey } from '../client-D83qhH0O.js';
|
|
3
|
+
export { B as BatchResponse, a as FeedbackConfig, b as FeedbackError, c as FeedbackString, R as RatingInput, S as StringsResponse, d as SuggestionInput, T as TokenBundle } from '../client-D83qhH0O.js';
|
|
4
4
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
|
-
export { h as hasKeyRegistry, r as resolveKeys } from '../keys-
|
|
5
|
+
export { h as hasKeyRegistry, r as resolveKeys } from '../keys-BpVB1kcI.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* `@verbumia/feedback` (React Native / Expo) as a PLUGIN of the
|
|
@@ -16,6 +16,12 @@ export { h as hasKeyRegistry, r as resolveKeys } from '../keys-CEWu0Htb.js';
|
|
|
16
16
|
interface I18nPluginContext {
|
|
17
17
|
i18n?: {
|
|
18
18
|
language?: string;
|
|
19
|
+
/** 0.2.7 — direct i18next handle used by the `scope: "current-view"`
|
|
20
|
+
* snapshot path (see /react plugin for full notes). Optional. */
|
|
21
|
+
i18next?: {
|
|
22
|
+
language?: string;
|
|
23
|
+
changeLanguage?: (lng: string) => Promise<unknown>;
|
|
24
|
+
};
|
|
19
25
|
};
|
|
20
26
|
config: {
|
|
21
27
|
apiBase?: string;
|
|
@@ -34,9 +40,27 @@ interface I18nPlugin {
|
|
|
34
40
|
render?: () => ReactNode;
|
|
35
41
|
}
|
|
36
42
|
interface FeedbackController {
|
|
37
|
-
open
|
|
43
|
+
/** From 0.2.7 `open()` is async (Promise<void>) so the
|
|
44
|
+
* `scope: "current-view"` snapshot sequence can run before the modal
|
|
45
|
+
* mounts. Hosts that don't await still work; the modal mounts when
|
|
46
|
+
* the promise resolves. */
|
|
47
|
+
open: () => Promise<void>;
|
|
38
48
|
close: () => void;
|
|
39
49
|
client: FeedbackClient;
|
|
50
|
+
/** 0.2.7 — current ToS version. */
|
|
51
|
+
readonly tosVersion: string;
|
|
52
|
+
/** 0.2.7 — whether this end-user has a live session-token bundle. */
|
|
53
|
+
readonly hasAcceptedTos: boolean;
|
|
54
|
+
/** 0.2.7 — programmatically POST `/v1/feedback/tos` (idempotent). Used
|
|
55
|
+
* by hosts that build their own ToS page (`tos: "skip"`). */
|
|
56
|
+
acceptTos: () => Promise<void>;
|
|
57
|
+
/** 0.2.7 — addon-state-derived flag (see /react plugin docs).
|
|
58
|
+
* `null` before the first state fetch resolves. */
|
|
59
|
+
readonly isActive: boolean | null;
|
|
60
|
+
/** 0.2.7 — `state.enabledLanguages` (string[] | "all" | null). */
|
|
61
|
+
readonly enabledLanguages: string[] | "all" | null;
|
|
62
|
+
/** 0.2.7 — raw SKU code. */
|
|
63
|
+
readonly sku: "feedback_starter" | "feedback_unlimited" | null;
|
|
40
64
|
}
|
|
41
65
|
interface FeedbackPluginOptions {
|
|
42
66
|
language?: string;
|
|
@@ -53,6 +77,22 @@ interface FeedbackPluginOptions {
|
|
|
53
77
|
current: FeedbackController | null;
|
|
54
78
|
};
|
|
55
79
|
fetchImpl?: typeof fetch;
|
|
80
|
+
/** 0.2.7 — `"modal"` (default, BC) keeps the built-in ToS step;
|
|
81
|
+
* `"skip"` removes it (host owns acceptance via
|
|
82
|
+
* `controller.acceptTos()`); /strings 401s render via the existing
|
|
83
|
+
* error state until acceptance lands. */
|
|
84
|
+
tos?: "modal" | "skip";
|
|
85
|
+
/** 0.2.7 — host's project API key (`vrb_live_…`, scope project:read);
|
|
86
|
+
* enables addon-state fetch at setup time. */
|
|
87
|
+
apiKey?: string;
|
|
88
|
+
/** 0.2.7 — controls `controller.isActive` (see /react plugin docs). */
|
|
89
|
+
cta?: "auto" | "show" | "hide";
|
|
90
|
+
/** 0.2.7 — on-screen-key set the panel reads (see /react plugin
|
|
91
|
+
* docs). Default `"current-view"` (controller.open() resets the
|
|
92
|
+
* registry, force-emits a same-locale languageChanged, waits 1
|
|
93
|
+
* frame, snapshots). `"all"` preserves the pre-0.2.7 accumulator
|
|
94
|
+
* view. */
|
|
95
|
+
scope?: "current-view" | "all";
|
|
56
96
|
}
|
|
57
97
|
declare function feedbackPlugin(options: FeedbackPluginOptions): I18nPlugin;
|
|
58
98
|
|
|
@@ -61,6 +101,10 @@ declare function FeedbackModal(props: {
|
|
|
61
101
|
visible: boolean;
|
|
62
102
|
keys?: DeclaredKey[];
|
|
63
103
|
namespace?: string | string[];
|
|
104
|
+
/** 0.2.7 — `"skip"` removes the built-in ToS step; the host owns
|
|
105
|
+
* acceptance via `controller.acceptTos()`. A missing token surfaces
|
|
106
|
+
* as a 401-derived error in the existing error row. */
|
|
107
|
+
tos?: "modal" | "skip";
|
|
64
108
|
onClose: () => void;
|
|
65
109
|
}): react_jsx_runtime.JSX.Element;
|
|
66
110
|
|
package/dist/native/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
__require,
|
|
6
6
|
hasKeyRegistry,
|
|
7
7
|
resolveKeys
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-7KWEI55W.js";
|
|
9
9
|
|
|
10
10
|
// src/native/plugin.tsx
|
|
11
11
|
import { useSyncExternalStore } from "react";
|
|
@@ -44,8 +44,10 @@ var C = {
|
|
|
44
44
|
emeraldSoft: "#34d399"
|
|
45
45
|
};
|
|
46
46
|
function FeedbackModal(props) {
|
|
47
|
-
const { client, visible, keys, namespace, onClose } = props;
|
|
48
|
-
const [consented, setConsented] = useState(
|
|
47
|
+
const { client, visible, keys, namespace, tos = "modal", onClose } = props;
|
|
48
|
+
const [consented, setConsented] = useState(
|
|
49
|
+
tos === "skip" ? true : client.hasConsented
|
|
50
|
+
);
|
|
49
51
|
const [busy, setBusy] = useState(false);
|
|
50
52
|
const [error, setError] = useState(null);
|
|
51
53
|
const [strings, setStrings] = useState([]);
|
|
@@ -279,42 +281,73 @@ function StringRow(props) {
|
|
|
279
281
|
// src/native/plugin.tsx
|
|
280
282
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
281
283
|
function makeStore() {
|
|
282
|
-
let
|
|
284
|
+
let state = { isOpen: false, snapshotKeys: void 0 };
|
|
283
285
|
const listeners = /* @__PURE__ */ new Set();
|
|
284
286
|
return {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
open
|
|
289
|
-
|
|
287
|
+
getState: () => state,
|
|
288
|
+
setOpen(open, snapshotKeys) {
|
|
289
|
+
const next = {
|
|
290
|
+
isOpen: open,
|
|
291
|
+
snapshotKeys: open ? snapshotKeys : void 0
|
|
292
|
+
};
|
|
293
|
+
if (state.isOpen === next.isOpen && state.snapshotKeys === next.snapshotKeys) {
|
|
294
|
+
return;
|
|
290
295
|
}
|
|
296
|
+
state = next;
|
|
297
|
+
listeners.forEach((l) => l());
|
|
291
298
|
},
|
|
292
299
|
subscribe(l) {
|
|
293
300
|
listeners.add(l);
|
|
294
|
-
return () =>
|
|
301
|
+
return () => {
|
|
302
|
+
listeners.delete(l);
|
|
303
|
+
};
|
|
295
304
|
}
|
|
296
305
|
};
|
|
297
306
|
}
|
|
307
|
+
async function captureCurrentViewSnapshot(i18next) {
|
|
308
|
+
const reg = globalThis.__verbumia_key_registry__;
|
|
309
|
+
if (!reg) return [];
|
|
310
|
+
const lng = i18next?.language;
|
|
311
|
+
const change = i18next?.changeLanguage;
|
|
312
|
+
if (typeof change === "function" && typeof lng === "string" && lng) {
|
|
313
|
+
reg.reset?.();
|
|
314
|
+
try {
|
|
315
|
+
await change(lng);
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
await new Promise((r) => {
|
|
319
|
+
if (typeof requestAnimationFrame === "function") {
|
|
320
|
+
requestAnimationFrame(() => r());
|
|
321
|
+
} else {
|
|
322
|
+
setTimeout(() => r(), 16);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
return reg.snapshot();
|
|
326
|
+
}
|
|
327
|
+
return reg.snapshot();
|
|
328
|
+
}
|
|
298
329
|
function feedbackPlugin(options) {
|
|
299
330
|
const store = makeStore();
|
|
300
331
|
let client = null;
|
|
301
332
|
function Outlet() {
|
|
302
|
-
const
|
|
333
|
+
const state = useSyncExternalStore(
|
|
303
334
|
store.subscribe,
|
|
304
|
-
store.
|
|
305
|
-
store.
|
|
335
|
+
store.getState,
|
|
336
|
+
store.getState
|
|
306
337
|
);
|
|
307
338
|
if (!client) return null;
|
|
308
339
|
const c = client;
|
|
340
|
+
const panelKeys = options.keys ?? state.snapshotKeys;
|
|
309
341
|
return /* @__PURE__ */ jsx2(
|
|
310
342
|
FeedbackModal,
|
|
311
343
|
{
|
|
312
344
|
client: c,
|
|
313
|
-
visible: isOpen,
|
|
314
|
-
keys:
|
|
345
|
+
visible: state.isOpen,
|
|
346
|
+
keys: panelKeys,
|
|
315
347
|
namespace: options.namespace,
|
|
348
|
+
tos: options.tos ?? "modal",
|
|
316
349
|
onClose: () => {
|
|
317
|
-
store.
|
|
350
|
+
store.setOpen(false);
|
|
318
351
|
void c.flush();
|
|
319
352
|
}
|
|
320
353
|
}
|
|
@@ -324,24 +357,71 @@ function feedbackPlugin(options) {
|
|
|
324
357
|
name: "@verbumia/feedback",
|
|
325
358
|
setup(ctx) {
|
|
326
359
|
const initialLanguage = options.language ?? ctx.i18n?.language ?? ctx.config.defaultLocale;
|
|
360
|
+
const tos = options.tos ?? "modal";
|
|
327
361
|
client = new FeedbackClient({
|
|
328
362
|
apiBase: options.apiBase ?? ctx.config.apiBase ?? "https://api.verbumia.dev",
|
|
329
363
|
projectId: options.projectId ?? ctx.config.projectUuid,
|
|
330
364
|
language: initialLanguage,
|
|
331
365
|
endUserId: options.endUserId,
|
|
332
|
-
fetchImpl: options.fetchImpl
|
|
366
|
+
fetchImpl: options.fetchImpl,
|
|
367
|
+
apiKey: options.apiKey,
|
|
368
|
+
autoAcceptTos: tos !== "skip"
|
|
333
369
|
});
|
|
370
|
+
const clientRef = client;
|
|
371
|
+
let addonState = null;
|
|
372
|
+
const cta = options.cta ?? "auto";
|
|
373
|
+
const scope = options.scope ?? "current-view";
|
|
374
|
+
const ctxI18next = ctx.i18n?.i18next;
|
|
375
|
+
const refreshState = async () => {
|
|
376
|
+
try {
|
|
377
|
+
const next = await clientRef.getAddonState();
|
|
378
|
+
if (next !== null) addonState = next;
|
|
379
|
+
} catch {
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
if (options.apiKey) void refreshState();
|
|
334
383
|
let langUnsub;
|
|
335
384
|
if (typeof ctx.onLanguageChange === "function") {
|
|
336
|
-
langUnsub = ctx.onLanguageChange((lng) =>
|
|
385
|
+
langUnsub = ctx.onLanguageChange((lng) => {
|
|
386
|
+
clientRef.setLanguage(lng);
|
|
387
|
+
void refreshState();
|
|
388
|
+
});
|
|
337
389
|
}
|
|
338
390
|
const controller = {
|
|
339
|
-
open: () =>
|
|
391
|
+
open: async () => {
|
|
392
|
+
if (scope === "current-view") {
|
|
393
|
+
const snapshot = await captureCurrentViewSnapshot(ctxI18next);
|
|
394
|
+
store.setOpen(true, snapshot);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
store.setOpen(true);
|
|
398
|
+
},
|
|
340
399
|
close: () => {
|
|
341
|
-
store.
|
|
342
|
-
void
|
|
400
|
+
store.setOpen(false);
|
|
401
|
+
void clientRef?.flush();
|
|
402
|
+
},
|
|
403
|
+
client: clientRef,
|
|
404
|
+
get tosVersion() {
|
|
405
|
+
return clientRef.tosVersion;
|
|
406
|
+
},
|
|
407
|
+
get hasAcceptedTos() {
|
|
408
|
+
return clientRef.hasAcceptedTos;
|
|
343
409
|
},
|
|
344
|
-
|
|
410
|
+
acceptTos: async () => {
|
|
411
|
+
await clientRef.acceptTos();
|
|
412
|
+
if (!options.apiKey) await refreshState();
|
|
413
|
+
},
|
|
414
|
+
get isActive() {
|
|
415
|
+
if (cta === "show") return true;
|
|
416
|
+
if (cta === "hide") return false;
|
|
417
|
+
return addonState ? addonState.isActive : null;
|
|
418
|
+
},
|
|
419
|
+
get enabledLanguages() {
|
|
420
|
+
return addonState ? addonState.enabledLanguages : null;
|
|
421
|
+
},
|
|
422
|
+
get sku() {
|
|
423
|
+
return addonState ? addonState.sku : null;
|
|
424
|
+
}
|
|
345
425
|
};
|
|
346
426
|
options.onReady?.(controller);
|
|
347
427
|
if (options.controllerRef) options.controllerRef.current = controller;
|
package/dist/native/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/native/plugin.tsx","../../src/native/panel.tsx"],"sourcesContent":["/**\n * `@verbumia/feedback` (React Native / Expo) as a PLUGIN of the\n * `@verbumia/*-i18n` provider — same architecture as the web plugin\n * (task 599): no second context; isolated sibling outlet; imperative\n * controller for the host CTA; sessionId is server-minted. Native uses\n * an RN <Modal> (its own overlay) toggled by a private store, so the\n * host app never re-renders when feedback opens.\n */\nimport { useSyncExternalStore, type ReactNode } from \"react\";\n\nimport { FeedbackClient } from \"../core/client\";\nimport type { DeclaredKey } from \"../core/types\";\nimport { FeedbackModal } from \"./panel\";\n\nexport interface I18nPluginContext {\n i18n?: { language?: string };\n config: {\n apiBase?: string;\n projectUuid: string;\n defaultLocale: string;\n };\n /** #806 — subscribe to runtime language changes (see\n * VerbumiaPluginContext in `@verbumia/react-i18next` ≥1.0.5). Returns\n * an unsubscribe fn the plugin MUST call from teardown. Optional so a\n * pre-1.0.5 react-i18next falls back gracefully. */\n onLanguageChange?: (cb: (lng: string) => void) => () => void;\n}\nexport interface I18nPlugin {\n name: string;\n setup?: (ctx: I18nPluginContext) => void | (() => void);\n render?: () => ReactNode;\n}\n\nexport interface FeedbackController {\n open: () => void;\n close: () => void;\n client: FeedbackClient;\n}\n\nexport interface FeedbackPluginOptions {\n // No `tosVersion` — SDK build-time constant (SDK_TOS_VERSION, task 616).\n language?: string;\n apiBase?: string;\n projectId?: string;\n endUserId?: string;\n keys?: DeclaredKey[];\n /** Optional namespace filter (CONTRACT v6, additive). Single ns or\n * list; applied AFTER rendered-scoping (`rendered ∩ namespace`).\n * Unset ⇒ all resolved keys (v5 behaviour). */\n namespace?: string | string[];\n onReady?: (controller: FeedbackController) => void;\n controllerRef?: { current: FeedbackController | null };\n fetchImpl?: typeof fetch;\n}\n\nfunction makeStore() {\n let open = false;\n const listeners = new Set<() => void>();\n return {\n isOpen: () => open,\n set(v: boolean) {\n if (open !== v) {\n open = v;\n listeners.forEach((l) => l());\n }\n },\n subscribe(l: () => void) {\n listeners.add(l);\n return () => listeners.delete(l);\n },\n };\n}\n\nexport function feedbackPlugin(options: FeedbackPluginOptions): I18nPlugin {\n const store = makeStore();\n let client: FeedbackClient | null = null;\n\n function Outlet() {\n const isOpen = useSyncExternalStore(\n store.subscribe,\n store.isOpen,\n store.isOpen,\n );\n if (!client) return null;\n const c = client;\n return (\n <FeedbackModal\n client={c}\n visible={isOpen}\n keys={options.keys}\n namespace={options.namespace}\n onClose={() => {\n store.set(false);\n void c.flush();\n }}\n />\n );\n }\n\n return {\n name: \"@verbumia/feedback\",\n setup(ctx) {\n // #806 SeedSower lang-change init source — same precedence as the\n // /react plugin: explicit option, then current i18n language,\n // then boot defaultLocale.\n const initialLanguage =\n options.language ?? ctx.i18n?.language ?? ctx.config.defaultLocale;\n client = new FeedbackClient({\n apiBase:\n options.apiBase ?? ctx.config.apiBase ?? \"https://api.verbumia.dev\",\n projectId: options.projectId ?? ctx.config.projectUuid,\n language: initialLanguage,\n endUserId: options.endUserId,\n fetchImpl: options.fetchImpl,\n });\n // #806 — re-sync the client on runtime language changes. Optional;\n // missing field on a non-Verbumia host falls back gracefully.\n let langUnsub: (() => void) | undefined;\n if (typeof ctx.onLanguageChange === \"function\") {\n langUnsub = ctx.onLanguageChange((lng) => client?.setLanguage(lng));\n }\n const controller: FeedbackController = {\n open: () => store.set(true),\n close: () => {\n store.set(false);\n void client?.flush();\n },\n client,\n };\n options.onReady?.(controller);\n if (options.controllerRef) options.controllerRef.current = controller;\n return () => {\n langUnsub?.();\n if (options.controllerRef) options.controllerRef.current = null;\n void client?.flush();\n };\n },\n render: () => <Outlet />,\n };\n}\n","import { createElement, useCallback, useEffect, useState, type ComponentType, type ReactNode } from \"react\";\nimport {\n KeyboardAvoidingView,\n Modal,\n Platform,\n ScrollView,\n Text,\n TextInput,\n TouchableOpacity,\n View,\n} from \"react-native\";\n\nimport type { FeedbackClient } from \"../core/client\";\nimport { resolveKeys } from \"../core/keys\";\nimport type { DeclaredKey, FeedbackString } from \"../core/types\";\n\n/**\n * Soft-resolve `react-native-safe-area-context`'s `SafeAreaView` (#806\n * SeedSower Android panel-layout bug). When the host installed it (Expo\n * apps ship it by default; bare RN apps that follow the standard setup do\n * too), the panel's bottom edge respects the system gesture bar / home\n * indicator. When it isn't installed (web bundles, RN apps that opted\n * out), fall back to a plain `View` — same children, same style, no\n * insets. The dependency is listed as an OPTIONAL `peerDependency` so the\n * npm install never warns on web-only consumers.\n */\ntype SafeAreaProps = { edges?: ReadonlyArray<string>; style?: unknown; children?: ReactNode };\n// Local `require` declaration so we don't pull @types/node into this\n// package. Metro / Node both inject `require` at module scope; in the\n// ESM build path tsup polyfills it via createRequire(import.meta.url).\ndeclare const require: (id: string) => unknown;\nlet SafeAreaView: ComponentType<SafeAreaProps>;\ntry {\n const mod = require(\"react-native-safe-area-context\") as {\n SafeAreaView: ComponentType<SafeAreaProps>;\n };\n SafeAreaView = mod.SafeAreaView;\n} catch {\n SafeAreaView = ({ children, style }: SafeAreaProps) =>\n createElement(\n View as ComponentType<{ style?: unknown; children?: ReactNode }>,\n { style },\n children,\n );\n}\n\nconst C = {\n bg: \"#0b0f0e\",\n panel: \"#111714\",\n border: \"#1f2a25\",\n text: \"#e7f5ef\",\n dim: \"#8aa79b\",\n emerald: \"#10b981\",\n emeraldSoft: \"#34d399\",\n};\n\nexport function FeedbackModal(props: {\n client: FeedbackClient;\n visible: boolean;\n keys?: DeclaredKey[];\n namespace?: string | string[];\n onClose: () => void;\n}) {\n const { client, visible, keys, namespace, onClose } = props;\n const [consented, setConsented] = useState(client.hasConsented);\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [strings, setStrings] = useState<FeedbackString[]>([]);\n\n const loadStrings = useCallback(async () => {\n setBusy(true);\n setError(null);\n try {\n // On-screen scoping is NORMATIVE (spec ltm 373, CONTRACT v5): show\n // ONLY rendered keys. Empty -> \"no strings on this view\"; never\n // fall back to fetching the whole project.\n const resolved = resolveKeys(keys, namespace);\n if (!resolved.length) {\n // #806 SeedSower P1 — same diagnostic as the web panel: an empty\n // registry resolve with no explicit `keys` prop almost always\n // means the host's `useTranslation` imports went to\n // `react-i18next` instead of `@verbumia/react-i18next`.\n if (!keys || keys.length === 0) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[verbumia/feedback] registry empty — are your useTranslation imports coming from @verbumia/react-i18next?\",\n );\n }\n setStrings([]);\n return;\n }\n const res = await client.getStrings({ keys: resolved });\n setStrings(res.strings);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Failed to load strings\");\n } finally {\n setBusy(false);\n }\n }, [client, keys, namespace]);\n\n useEffect(() => {\n if (visible && consented) void loadStrings();\n }, [visible, consented, loadStrings]);\n\n const accept = useCallback(async () => {\n setBusy(true);\n try {\n await client.acceptTos();\n setConsented(true);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Could not accept the terms\");\n } finally {\n setBusy(false);\n }\n }, [client]);\n\n return (\n <Modal\n visible={visible}\n transparent\n animationType=\"slide\"\n onRequestClose={onClose}\n >\n {/*\n #806 SeedSower native panel layout — KeyboardAvoidingView wraps the\n whole overlay so the suggestion TextInput isn't covered by the soft\n keyboard. `padding` on iOS / `height` on Android matches the RN\n community convention; flex:1 keeps the overlay full-screen above\n the keyboard reservation.\n */}\n <KeyboardAvoidingView\n style={{ flex: 1 }}\n behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n >\n <View style={{ flex: 1, backgroundColor: \"rgba(0,0,0,.55)\", justifyContent: \"flex-end\" }}>\n {/*\n Panel sizing — `height:'85%'` gives a CONCRETE 85% of the\n modal's parent (the full-screen overlay), which is what the\n ToS step / strings ScrollView need to flex against. The prior\n `maxHeight:'85%'` was a ceiling only: without a height/flex\n child, the panel collapsed to its ToS intrinsic content (~15%\n of screen) on RN 0.77.3 Android. `maxHeight` is kept as a\n backstop in case a future child wants to render shorter (it\n never exceeds 85%; it can render shorter if the content does).\n\n SafeAreaView edges={['bottom']} keeps the CTA off the Android\n gesture bar / iOS home indicator. Falls back to plain View\n when react-native-safe-area-context isn't installed.\n */}\n <SafeAreaView\n edges={[\"bottom\"]}\n style={{\n height: \"85%\",\n maxHeight: \"85%\",\n backgroundColor: C.bg,\n borderTopWidth: 1,\n borderColor: C.border,\n }}\n >\n <View style={{ flex: 1, padding: 18 }}>\n <View\n style={{\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n marginBottom: 12,\n }}\n >\n <Text style={{ color: C.emeraldSoft, fontWeight: \"700\", fontSize: 16 }}>\n Translation feedback\n </Text>\n <TouchableOpacity onPress={onClose} accessibilityLabel=\"Close\">\n <Text style={{ color: C.dim, fontSize: 20 }}>×</Text>\n </TouchableOpacity>\n </View>\n\n {error ? (\n <Text style={{ color: \"#f87171\", marginBottom: 8 }}>{error}</Text>\n ) : null}\n\n {!consented ? (\n <View>\n <Text style={{ color: C.text, lineHeight: 21 }}>\n Help improve the translations in this app. Your ratings and\n suggestions are sent to the app owner via Verbumia. Don’t\n submit personal or sensitive information.\n </Text>\n <TouchableOpacity\n disabled={busy}\n onPress={accept}\n style={{\n marginTop: 14,\n backgroundColor: C.emerald,\n borderRadius: 8,\n padding: 14,\n }}\n >\n <Text style={{ color: \"#03110c\", fontWeight: \"700\", textAlign: \"center\" }}>\n {busy ? \"…\" : `I accept the terms (v${client.tosVersion})`}\n </Text>\n </TouchableOpacity>\n </View>\n ) : (\n // flex:1 makes the ScrollView fill the remaining panel\n // height after the header; without it the list collapsed\n // to its intrinsic height and the panel left a large empty\n // area below the last row (the second half of the\n // SeedSower repro).\n <ScrollView style={{ flex: 1 }}>\n {!strings.length ? (\n <Text style={{ color: C.dim }}>\n {busy ? \"Loading…\" : \"No strings to review on this view.\"}\n </Text>\n ) : (\n strings.map((s) => (\n <StringRow\n key={`${s.namespace}:${s.key}`}\n s={s}\n client={client}\n />\n ))\n )}\n </ScrollView>\n )}\n </View>\n </SafeAreaView>\n </View>\n </KeyboardAvoidingView>\n </Modal>\n );\n}\n\nfunction StringRow(props: { s: FeedbackString; client: FeedbackClient }) {\n const { s, client } = props;\n const [mine, setMine] = useState<number | null>(s.my_rating);\n const [show, setShow] = useState(false);\n const [text, setText] = useState(\"\");\n const [sent, setSent] = useState(false);\n\n return (\n <View\n style={{\n backgroundColor: C.panel,\n borderWidth: 1,\n borderColor: C.border,\n borderRadius: 10,\n padding: 12,\n marginBottom: 10,\n }}\n >\n <Text style={{ color: C.dim, fontSize: 12 }}>\n {s.namespace} · {s.key}\n </Text>\n <Text style={{ color: C.text, fontSize: 15, marginVertical: 6 }}>\n {s.value}\n </Text>\n <View style={{ flexDirection: \"row\" }}>\n {[1, 2, 3, 4, 5].map((n) => (\n <TouchableOpacity\n key={n}\n accessibilityLabel={`${n} stars`}\n onPress={() => {\n setMine(n);\n client.rate({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n stars: n,\n });\n }}\n >\n <Text\n style={{\n fontSize: 22,\n marginRight: 4,\n color: mine && n <= mine ? C.emeraldSoft : C.border,\n }}\n >\n ★\n </Text>\n </TouchableOpacity>\n ))}\n <TouchableOpacity\n onPress={() => setShow(!show)}\n style={{ marginLeft: \"auto\" }}\n >\n <Text style={{ color: C.emeraldSoft }}>\n {sent ? \"Suggested ✓\" : \"Suggest\"}\n </Text>\n </TouchableOpacity>\n </View>\n {show ? (\n <View style={{ marginTop: 10 }}>\n <TextInput\n value={text}\n onChangeText={setText}\n multiline\n numberOfLines={3}\n placeholder=\"Your suggested translation…\"\n placeholderTextColor={C.dim}\n style={{\n backgroundColor: C.bg,\n color: C.text,\n borderWidth: 1,\n borderColor: C.border,\n borderRadius: 6,\n padding: 8,\n }}\n />\n <TouchableOpacity\n onPress={() => {\n if (!text.trim()) return;\n client.suggest({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n suggested_text: text.trim(),\n });\n setSent(true);\n setShow(false);\n setText(\"\");\n }}\n style={{\n marginTop: 6,\n backgroundColor: C.emerald,\n borderRadius: 6,\n padding: 10,\n }}\n >\n <Text style={{ color: \"#03110c\", fontWeight: \"700\", textAlign: \"center\" }}>\n Send suggestion\n </Text>\n </TouchableOpacity>\n </View>\n ) : null}\n </View>\n );\n}\n"],"mappings":"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","names":["jsx"]}
|
|
1
|
+
{"version":3,"sources":["../../src/native/plugin.tsx","../../src/native/panel.tsx"],"sourcesContent":["/**\n * `@verbumia/feedback` (React Native / Expo) as a PLUGIN of the\n * `@verbumia/*-i18n` provider — same architecture as the web plugin\n * (task 599): no second context; isolated sibling outlet; imperative\n * controller for the host CTA; sessionId is server-minted. Native uses\n * an RN <Modal> (its own overlay) toggled by a private store, so the\n * host app never re-renders when feedback opens.\n */\nimport { useSyncExternalStore, type ReactNode } from \"react\";\n\nimport { FeedbackClient } from \"../core/client\";\nimport type { DeclaredKey, FeedbackAddonState } from \"../core/types\";\nimport { FeedbackModal } from \"./panel\";\n\nexport interface I18nPluginContext {\n i18n?: {\n language?: string;\n /** 0.2.7 — direct i18next handle used by the `scope: \"current-view\"`\n * snapshot path (see /react plugin for full notes). Optional. */\n i18next?: {\n language?: string;\n changeLanguage?: (lng: string) => Promise<unknown>;\n };\n };\n config: {\n apiBase?: string;\n projectUuid: string;\n defaultLocale: string;\n };\n /** #806 — subscribe to runtime language changes (see\n * VerbumiaPluginContext in `@verbumia/react-i18next` ≥1.0.5). Returns\n * an unsubscribe fn the plugin MUST call from teardown. Optional so a\n * pre-1.0.5 react-i18next falls back gracefully. */\n onLanguageChange?: (cb: (lng: string) => void) => () => void;\n}\nexport interface I18nPlugin {\n name: string;\n setup?: (ctx: I18nPluginContext) => void | (() => void);\n render?: () => ReactNode;\n}\n\nexport interface FeedbackController {\n /** From 0.2.7 `open()` is async (Promise<void>) so the\n * `scope: \"current-view\"` snapshot sequence can run before the modal\n * mounts. Hosts that don't await still work; the modal mounts when\n * the promise resolves. */\n open: () => Promise<void>;\n close: () => void;\n client: FeedbackClient;\n /** 0.2.7 — current ToS version. */\n readonly tosVersion: string;\n /** 0.2.7 — whether this end-user has a live session-token bundle. */\n readonly hasAcceptedTos: boolean;\n /** 0.2.7 — programmatically POST `/v1/feedback/tos` (idempotent). Used\n * by hosts that build their own ToS page (`tos: \"skip\"`). */\n acceptTos: () => Promise<void>;\n /** 0.2.7 — addon-state-derived flag (see /react plugin docs).\n * `null` before the first state fetch resolves. */\n readonly isActive: boolean | null;\n /** 0.2.7 — `state.enabledLanguages` (string[] | \"all\" | null). */\n readonly enabledLanguages: string[] | \"all\" | null;\n /** 0.2.7 — raw SKU code. */\n readonly sku: \"feedback_starter\" | \"feedback_unlimited\" | null;\n}\n\nexport interface FeedbackPluginOptions {\n // No `tosVersion` — SDK build-time constant (SDK_TOS_VERSION, task 616).\n language?: string;\n apiBase?: string;\n projectId?: string;\n endUserId?: string;\n keys?: DeclaredKey[];\n /** Optional namespace filter (CONTRACT v6, additive). Single ns or\n * list; applied AFTER rendered-scoping (`rendered ∩ namespace`).\n * Unset ⇒ all resolved keys (v5 behaviour). */\n namespace?: string | string[];\n onReady?: (controller: FeedbackController) => void;\n controllerRef?: { current: FeedbackController | null };\n fetchImpl?: typeof fetch;\n /** 0.2.7 — `\"modal\"` (default, BC) keeps the built-in ToS step;\n * `\"skip\"` removes it (host owns acceptance via\n * `controller.acceptTos()`); /strings 401s render via the existing\n * error state until acceptance lands. */\n tos?: \"modal\" | \"skip\";\n /** 0.2.7 — host's project API key (`vrb_live_…`, scope project:read);\n * enables addon-state fetch at setup time. */\n apiKey?: string;\n /** 0.2.7 — controls `controller.isActive` (see /react plugin docs). */\n cta?: \"auto\" | \"show\" | \"hide\";\n /** 0.2.7 — on-screen-key set the panel reads (see /react plugin\n * docs). Default `\"current-view\"` (controller.open() resets the\n * registry, force-emits a same-locale languageChanged, waits 1\n * frame, snapshots). `\"all\"` preserves the pre-0.2.7 accumulator\n * view. */\n scope?: \"current-view\" | \"all\";\n}\n\n/** 0.2.7 panel store — see /react plugin docs. */\ntype PanelState = {\n isOpen: boolean;\n snapshotKeys: DeclaredKey[] | undefined;\n};\nfunction makeStore() {\n let state: PanelState = { isOpen: false, snapshotKeys: undefined };\n const listeners = new Set<() => void>();\n return {\n getState: (): PanelState => state,\n setOpen(open: boolean, snapshotKeys?: DeclaredKey[]): void {\n const next: PanelState = {\n isOpen: open,\n snapshotKeys: open ? snapshotKeys : undefined,\n };\n if (\n state.isOpen === next.isOpen &&\n state.snapshotKeys === next.snapshotKeys\n ) {\n return;\n }\n state = next;\n listeners.forEach((l) => l());\n },\n subscribe(l: () => void): () => void {\n listeners.add(l);\n return () => {\n listeners.delete(l);\n };\n },\n };\n}\n\n/** 0.2.7 — `scope: \"current-view\"` snapshot. See /react plugin for\n * the full rationale + known limitation. On native (Hermes) we yield\n * via `setTimeout(16)` since `requestAnimationFrame` is rarely\n * injected by Hermes runtimes. */\nasync function captureCurrentViewSnapshot(\n i18next:\n | {\n language?: string;\n changeLanguage?: (lng: string) => Promise<unknown>;\n }\n | undefined,\n): Promise<DeclaredKey[]> {\n type Reg = {\n snapshot: () => DeclaredKey[];\n reset?: () => void;\n };\n const reg = (globalThis as Record<string, unknown>)\n .__verbumia_key_registry__ as Reg | undefined;\n if (!reg) return [];\n const lng = i18next?.language;\n const change = i18next?.changeLanguage;\n if (typeof change === \"function\" && typeof lng === \"string\" && lng) {\n reg.reset?.();\n try {\n await change(lng);\n } catch {\n // best-effort\n }\n await new Promise<void>((r) => {\n if (typeof requestAnimationFrame === \"function\") {\n requestAnimationFrame(() => r());\n } else {\n setTimeout(() => r(), 16);\n }\n });\n return reg.snapshot();\n }\n return reg.snapshot();\n}\n\nexport function feedbackPlugin(options: FeedbackPluginOptions): I18nPlugin {\n const store = makeStore();\n let client: FeedbackClient | null = null;\n\n function Outlet() {\n const state = useSyncExternalStore(\n store.subscribe,\n store.getState,\n store.getState,\n );\n if (!client) return null;\n const c = client;\n // Precedence: options.keys > scope='current-view' snapshot > registry.\n const panelKeys = options.keys ?? state.snapshotKeys;\n return (\n <FeedbackModal\n client={c}\n visible={state.isOpen}\n keys={panelKeys}\n namespace={options.namespace}\n tos={options.tos ?? \"modal\"}\n onClose={() => {\n store.setOpen(false);\n void c.flush();\n }}\n />\n );\n }\n\n return {\n name: \"@verbumia/feedback\",\n setup(ctx) {\n // #806 SeedSower lang-change init source — same precedence as the\n // /react plugin: explicit option, then current i18n language,\n // then boot defaultLocale.\n const initialLanguage =\n options.language ?? ctx.i18n?.language ?? ctx.config.defaultLocale;\n // 0.2.7 ToS: `tos: \"skip\"` flips `autoAcceptTos: false` so the\n // client refuses to silently POST /v1/feedback/tos on the user's\n // behalf — the host owns acceptance via `controller.acceptTos()`.\n const tos: \"modal\" | \"skip\" = options.tos ?? \"modal\";\n client = new FeedbackClient({\n apiBase:\n options.apiBase ?? ctx.config.apiBase ?? \"https://api.verbumia.dev\",\n projectId: options.projectId ?? ctx.config.projectUuid,\n language: initialLanguage,\n endUserId: options.endUserId,\n fetchImpl: options.fetchImpl,\n apiKey: options.apiKey,\n autoAcceptTos: tos !== \"skip\",\n });\n const clientRef = client;\n // 0.2.7 addon-state cache — same semantics as the /react plugin.\n let addonState: FeedbackAddonState | null = null;\n const cta: \"auto\" | \"show\" | \"hide\" = options.cta ?? \"auto\";\n // 0.2.7 scope (default \"current-view\" — see plugin docs).\n const scope: \"current-view\" | \"all\" = options.scope ?? \"current-view\";\n const ctxI18next = ctx.i18n?.i18next;\n const refreshState = async (): Promise<void> => {\n try {\n const next = await clientRef.getAddonState();\n if (next !== null) addonState = next;\n } catch {\n // Pre-acceptTos + no apiKey path — defer until acceptTos().\n }\n };\n if (options.apiKey) void refreshState();\n // #806 — re-sync the client + refresh addon-state on lang change.\n let langUnsub: (() => void) | undefined;\n if (typeof ctx.onLanguageChange === \"function\") {\n langUnsub = ctx.onLanguageChange((lng) => {\n clientRef.setLanguage(lng);\n void refreshState();\n });\n }\n const controller: FeedbackController = {\n open: async (): Promise<void> => {\n if (scope === \"current-view\") {\n const snapshot = await captureCurrentViewSnapshot(ctxI18next);\n store.setOpen(true, snapshot);\n return;\n }\n store.setOpen(true);\n },\n close: () => {\n store.setOpen(false);\n void clientRef?.flush();\n },\n client: clientRef,\n get tosVersion(): string {\n return clientRef.tosVersion;\n },\n get hasAcceptedTos(): boolean {\n return clientRef.hasAcceptedTos;\n },\n acceptTos: async (): Promise<void> => {\n await clientRef.acceptTos();\n if (!options.apiKey) await refreshState();\n },\n get isActive(): boolean | null {\n if (cta === \"show\") return true;\n if (cta === \"hide\") return false;\n return addonState ? addonState.isActive : null;\n },\n get enabledLanguages(): string[] | \"all\" | null {\n return addonState ? addonState.enabledLanguages : null;\n },\n get sku(): \"feedback_starter\" | \"feedback_unlimited\" | null {\n return addonState ? addonState.sku : null;\n },\n };\n options.onReady?.(controller);\n if (options.controllerRef) options.controllerRef.current = controller;\n return () => {\n langUnsub?.();\n if (options.controllerRef) options.controllerRef.current = null;\n void client?.flush();\n };\n },\n render: () => <Outlet />,\n };\n}\n","import { createElement, useCallback, useEffect, useState, type ComponentType, type ReactNode } from \"react\";\nimport {\n KeyboardAvoidingView,\n Modal,\n Platform,\n ScrollView,\n Text,\n TextInput,\n TouchableOpacity,\n View,\n} from \"react-native\";\n\nimport type { FeedbackClient } from \"../core/client\";\nimport { resolveKeys } from \"../core/keys\";\nimport type { DeclaredKey, FeedbackString } from \"../core/types\";\n\n/**\n * Soft-resolve `react-native-safe-area-context`'s `SafeAreaView` (#806\n * SeedSower Android panel-layout bug). When the host installed it (Expo\n * apps ship it by default; bare RN apps that follow the standard setup do\n * too), the panel's bottom edge respects the system gesture bar / home\n * indicator. When it isn't installed (web bundles, RN apps that opted\n * out), fall back to a plain `View` — same children, same style, no\n * insets. The dependency is listed as an OPTIONAL `peerDependency` so the\n * npm install never warns on web-only consumers.\n */\ntype SafeAreaProps = { edges?: ReadonlyArray<string>; style?: unknown; children?: ReactNode };\n// Local `require` declaration so we don't pull @types/node into this\n// package. Metro / Node both inject `require` at module scope; in the\n// ESM build path tsup polyfills it via createRequire(import.meta.url).\ndeclare const require: (id: string) => unknown;\nlet SafeAreaView: ComponentType<SafeAreaProps>;\ntry {\n const mod = require(\"react-native-safe-area-context\") as {\n SafeAreaView: ComponentType<SafeAreaProps>;\n };\n SafeAreaView = mod.SafeAreaView;\n} catch {\n SafeAreaView = ({ children, style }: SafeAreaProps) =>\n createElement(\n View as ComponentType<{ style?: unknown; children?: ReactNode }>,\n { style },\n children,\n );\n}\n\nconst C = {\n bg: \"#0b0f0e\",\n panel: \"#111714\",\n border: \"#1f2a25\",\n text: \"#e7f5ef\",\n dim: \"#8aa79b\",\n emerald: \"#10b981\",\n emeraldSoft: \"#34d399\",\n};\n\nexport function FeedbackModal(props: {\n client: FeedbackClient;\n visible: boolean;\n keys?: DeclaredKey[];\n namespace?: string | string[];\n /** 0.2.7 — `\"skip\"` removes the built-in ToS step; the host owns\n * acceptance via `controller.acceptTos()`. A missing token surfaces\n * as a 401-derived error in the existing error row. */\n tos?: \"modal\" | \"skip\";\n onClose: () => void;\n}) {\n const { client, visible, keys, namespace, tos = \"modal\", onClose } = props;\n const [consented, setConsented] = useState(\n tos === \"skip\" ? true : client.hasConsented,\n );\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [strings, setStrings] = useState<FeedbackString[]>([]);\n\n const loadStrings = useCallback(async () => {\n setBusy(true);\n setError(null);\n try {\n // On-screen scoping is NORMATIVE (spec ltm 373, CONTRACT v5): show\n // ONLY rendered keys. Empty -> \"no strings on this view\"; never\n // fall back to fetching the whole project.\n const resolved = resolveKeys(keys, namespace);\n if (!resolved.length) {\n // #806 SeedSower P1 — same diagnostic as the web panel: an empty\n // registry resolve with no explicit `keys` prop almost always\n // means the host's `useTranslation` imports went to\n // `react-i18next` instead of `@verbumia/react-i18next`.\n if (!keys || keys.length === 0) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[verbumia/feedback] registry empty — are your useTranslation imports coming from @verbumia/react-i18next?\",\n );\n }\n setStrings([]);\n return;\n }\n const res = await client.getStrings({ keys: resolved });\n setStrings(res.strings);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Failed to load strings\");\n } finally {\n setBusy(false);\n }\n }, [client, keys, namespace]);\n\n useEffect(() => {\n if (visible && consented) void loadStrings();\n }, [visible, consented, loadStrings]);\n\n const accept = useCallback(async () => {\n setBusy(true);\n try {\n await client.acceptTos();\n setConsented(true);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Could not accept the terms\");\n } finally {\n setBusy(false);\n }\n }, [client]);\n\n return (\n <Modal\n visible={visible}\n transparent\n animationType=\"slide\"\n onRequestClose={onClose}\n >\n {/*\n #806 SeedSower native panel layout — KeyboardAvoidingView wraps the\n whole overlay so the suggestion TextInput isn't covered by the soft\n keyboard. `padding` on iOS / `height` on Android matches the RN\n community convention; flex:1 keeps the overlay full-screen above\n the keyboard reservation.\n */}\n <KeyboardAvoidingView\n style={{ flex: 1 }}\n behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n >\n <View style={{ flex: 1, backgroundColor: \"rgba(0,0,0,.55)\", justifyContent: \"flex-end\" }}>\n {/*\n Panel sizing — `height:'85%'` gives a CONCRETE 85% of the\n modal's parent (the full-screen overlay), which is what the\n ToS step / strings ScrollView need to flex against. The prior\n `maxHeight:'85%'` was a ceiling only: without a height/flex\n child, the panel collapsed to its ToS intrinsic content (~15%\n of screen) on RN 0.77.3 Android. `maxHeight` is kept as a\n backstop in case a future child wants to render shorter (it\n never exceeds 85%; it can render shorter if the content does).\n\n SafeAreaView edges={['bottom']} keeps the CTA off the Android\n gesture bar / iOS home indicator. Falls back to plain View\n when react-native-safe-area-context isn't installed.\n */}\n <SafeAreaView\n edges={[\"bottom\"]}\n style={{\n height: \"85%\",\n maxHeight: \"85%\",\n backgroundColor: C.bg,\n borderTopWidth: 1,\n borderColor: C.border,\n }}\n >\n <View style={{ flex: 1, padding: 18 }}>\n <View\n style={{\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n marginBottom: 12,\n }}\n >\n <Text style={{ color: C.emeraldSoft, fontWeight: \"700\", fontSize: 16 }}>\n Translation feedback\n </Text>\n <TouchableOpacity onPress={onClose} accessibilityLabel=\"Close\">\n <Text style={{ color: C.dim, fontSize: 20 }}>×</Text>\n </TouchableOpacity>\n </View>\n\n {error ? (\n <Text style={{ color: \"#f87171\", marginBottom: 8 }}>{error}</Text>\n ) : null}\n\n {!consented ? (\n <View>\n <Text style={{ color: C.text, lineHeight: 21 }}>\n Help improve the translations in this app. Your ratings and\n suggestions are sent to the app owner via Verbumia. Don’t\n submit personal or sensitive information.\n </Text>\n <TouchableOpacity\n disabled={busy}\n onPress={accept}\n style={{\n marginTop: 14,\n backgroundColor: C.emerald,\n borderRadius: 8,\n padding: 14,\n }}\n >\n <Text style={{ color: \"#03110c\", fontWeight: \"700\", textAlign: \"center\" }}>\n {busy ? \"…\" : `I accept the terms (v${client.tosVersion})`}\n </Text>\n </TouchableOpacity>\n </View>\n ) : (\n // flex:1 makes the ScrollView fill the remaining panel\n // height after the header; without it the list collapsed\n // to its intrinsic height and the panel left a large empty\n // area below the last row (the second half of the\n // SeedSower repro).\n <ScrollView style={{ flex: 1 }}>\n {!strings.length ? (\n <Text style={{ color: C.dim }}>\n {busy ? \"Loading…\" : \"No strings to review on this view.\"}\n </Text>\n ) : (\n strings.map((s) => (\n <StringRow\n key={`${s.namespace}:${s.key}`}\n s={s}\n client={client}\n />\n ))\n )}\n </ScrollView>\n )}\n </View>\n </SafeAreaView>\n </View>\n </KeyboardAvoidingView>\n </Modal>\n );\n}\n\nfunction StringRow(props: { s: FeedbackString; client: FeedbackClient }) {\n const { s, client } = props;\n const [mine, setMine] = useState<number | null>(s.my_rating);\n const [show, setShow] = useState(false);\n const [text, setText] = useState(\"\");\n const [sent, setSent] = useState(false);\n\n return (\n <View\n style={{\n backgroundColor: C.panel,\n borderWidth: 1,\n borderColor: C.border,\n borderRadius: 10,\n padding: 12,\n marginBottom: 10,\n }}\n >\n <Text style={{ color: C.dim, fontSize: 12 }}>\n {s.namespace} · {s.key}\n </Text>\n <Text style={{ color: C.text, fontSize: 15, marginVertical: 6 }}>\n {s.value}\n </Text>\n <View style={{ flexDirection: \"row\" }}>\n {[1, 2, 3, 4, 5].map((n) => (\n <TouchableOpacity\n key={n}\n accessibilityLabel={`${n} stars`}\n onPress={() => {\n setMine(n);\n client.rate({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n stars: n,\n });\n }}\n >\n <Text\n style={{\n fontSize: 22,\n marginRight: 4,\n color: mine && n <= mine ? C.emeraldSoft : C.border,\n }}\n >\n ★\n </Text>\n </TouchableOpacity>\n ))}\n <TouchableOpacity\n onPress={() => setShow(!show)}\n style={{ marginLeft: \"auto\" }}\n >\n <Text style={{ color: C.emeraldSoft }}>\n {sent ? \"Suggested ✓\" : \"Suggest\"}\n </Text>\n </TouchableOpacity>\n </View>\n {show ? (\n <View style={{ marginTop: 10 }}>\n <TextInput\n value={text}\n onChangeText={setText}\n multiline\n numberOfLines={3}\n placeholder=\"Your suggested translation…\"\n placeholderTextColor={C.dim}\n style={{\n backgroundColor: C.bg,\n color: C.text,\n borderWidth: 1,\n borderColor: C.border,\n borderRadius: 6,\n padding: 8,\n }}\n />\n <TouchableOpacity\n onPress={() => {\n if (!text.trim()) return;\n client.suggest({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n suggested_text: text.trim(),\n });\n setSent(true);\n setShow(false);\n setText(\"\");\n }}\n style={{\n marginTop: 6,\n backgroundColor: C.emerald,\n borderRadius: 6,\n padding: 10,\n }}\n >\n <Text style={{ color: \"#03110c\", fontWeight: \"700\", textAlign: \"center\" }}>\n Send suggestion\n </Text>\n </TouchableOpacity>\n </View>\n ) : null}\n </View>\n );\n}\n"],"mappings":"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","names":["jsx"]}
|