@verbumia/feedback 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.
Files changed (42) hide show
  1. package/CONTRACT.md +165 -0
  2. package/LICENSE +21 -0
  3. package/README.md +80 -0
  4. package/dist/chunk-5NA2TFPG.js +1 -0
  5. package/dist/chunk-5NA2TFPG.js.map +1 -0
  6. package/dist/chunk-OX4RJD5H.js +242 -0
  7. package/dist/chunk-OX4RJD5H.js.map +1 -0
  8. package/dist/client-CPEcvn23.d.cts +159 -0
  9. package/dist/client-CPEcvn23.d.ts +159 -0
  10. package/dist/core/index.cjs +272 -0
  11. package/dist/core/index.cjs.map +1 -0
  12. package/dist/core/index.d.cts +18 -0
  13. package/dist/core/index.d.ts +18 -0
  14. package/dist/core/index.js +16 -0
  15. package/dist/core/index.js.map +1 -0
  16. package/dist/keys-BySe1O6V.d.ts +25 -0
  17. package/dist/keys-Dg_nv16u.d.cts +25 -0
  18. package/dist/native/index.cjs +575 -0
  19. package/dist/native/index.cjs.map +1 -0
  20. package/dist/native/index.d.cts +54 -0
  21. package/dist/native/index.d.ts +54 -0
  22. package/dist/native/index.js +322 -0
  23. package/dist/native/index.js.map +1 -0
  24. package/dist/react/index.cjs +644 -0
  25. package/dist/react/index.cjs.map +1 -0
  26. package/dist/react/index.d.cts +55 -0
  27. package/dist/react/index.d.ts +55 -0
  28. package/dist/react/index.js +384 -0
  29. package/dist/react/index.js.map +1 -0
  30. package/dist/svelte/index.cjs +306 -0
  31. package/dist/svelte/index.cjs.map +1 -0
  32. package/dist/svelte/index.d.cts +38 -0
  33. package/dist/svelte/index.d.ts +38 -0
  34. package/dist/svelte/index.js +52 -0
  35. package/dist/svelte/index.js.map +1 -0
  36. package/dist/vue/index.cjs +426 -0
  37. package/dist/vue/index.cjs.map +1 -0
  38. package/dist/vue/index.d.cts +39 -0
  39. package/dist/vue/index.d.ts +39 -0
  40. package/dist/vue/index.js +172 -0
  41. package/dist/vue/index.js.map +1 -0
  42. package/package.json +108 -0
@@ -0,0 +1,172 @@
1
+ import {
2
+ FeedbackClient,
3
+ FeedbackError,
4
+ resolveKeys
5
+ } from "../chunk-OX4RJD5H.js";
6
+
7
+ // src/vue/index.ts
8
+ import { defineComponent, h, ref, Teleport } from "vue";
9
+ var C = {
10
+ bg: "#0b0f0e",
11
+ panel: "#111714",
12
+ border: "#1f2a25",
13
+ text: "#e7f5ef",
14
+ dim: "#8aa79b",
15
+ emerald: "#10b981",
16
+ emeraldSoft: "#34d399"
17
+ };
18
+ function createFeedback(config) {
19
+ const client = new FeedbackClient({
20
+ apiBase: config.apiBase,
21
+ projectId: config.projectId,
22
+ language: config.language,
23
+ endUserId: config.endUserId,
24
+ fetchImpl: config.fetchImpl
25
+ });
26
+ const isOpen = ref(false);
27
+ const controller = {
28
+ open: () => {
29
+ isOpen.value = true;
30
+ },
31
+ close: () => {
32
+ isOpen.value = false;
33
+ void client.flush();
34
+ },
35
+ client
36
+ };
37
+ const FeedbackPanel = defineComponent({
38
+ name: "VerbumiaFeedbackPanel",
39
+ setup() {
40
+ const strings = ref([]);
41
+ const busy = ref(false);
42
+ const consented = ref(client.hasConsented);
43
+ const error = ref(null);
44
+ async function load() {
45
+ busy.value = true;
46
+ error.value = null;
47
+ try {
48
+ if (!consented.value) {
49
+ await client.acceptTos();
50
+ consented.value = true;
51
+ }
52
+ const resolved = resolveKeys(config.keys);
53
+ const r = await client.getStrings({
54
+ keys: resolved.length ? resolved : void 0
55
+ });
56
+ strings.value = r.strings;
57
+ } catch (e) {
58
+ error.value = e instanceof Error ? e.message : "Failed to load";
59
+ } finally {
60
+ busy.value = false;
61
+ }
62
+ }
63
+ return () => {
64
+ if (!isOpen.value) return null;
65
+ if (!strings.value.length && !busy.value && !error.value) void load();
66
+ return h(
67
+ Teleport,
68
+ { to: "body" },
69
+ h(
70
+ "div",
71
+ {
72
+ role: "dialog",
73
+ style: {
74
+ position: "fixed",
75
+ inset: 0,
76
+ zIndex: 2147483600,
77
+ background: "rgba(0,0,0,.55)",
78
+ display: "flex",
79
+ justifyContent: "flex-end"
80
+ },
81
+ onClick: controller.close
82
+ },
83
+ [
84
+ h(
85
+ "div",
86
+ {
87
+ onClick: (e) => e.stopPropagation(),
88
+ style: {
89
+ width: "min(420px,100%)",
90
+ height: "100%",
91
+ background: C.bg,
92
+ color: C.text,
93
+ borderLeft: `1px solid ${C.border}`,
94
+ padding: "18px",
95
+ fontFamily: "system-ui,sans-serif",
96
+ overflowY: "auto"
97
+ }
98
+ },
99
+ [
100
+ h(
101
+ "strong",
102
+ { style: { color: C.emeraldSoft } },
103
+ "Translation feedback"
104
+ ),
105
+ error.value ? h("p", { style: { color: "#f87171" } }, error.value) : null,
106
+ ...strings.value.map(
107
+ (s) => h(
108
+ "div",
109
+ {
110
+ key: `${s.namespace}:${s.key}`,
111
+ style: {
112
+ background: C.panel,
113
+ border: `1px solid ${C.border}`,
114
+ borderRadius: "10px",
115
+ padding: "12px",
116
+ margin: "10px 0"
117
+ }
118
+ },
119
+ [
120
+ h(
121
+ "div",
122
+ { style: { fontSize: "12px", color: C.dim } },
123
+ `${s.namespace} \xB7 ${s.key}`
124
+ ),
125
+ h("div", { style: { margin: "6px 0" } }, s.value),
126
+ h(
127
+ "div",
128
+ {},
129
+ [1, 2, 3, 4, 5].map(
130
+ (n) => h(
131
+ "button",
132
+ {
133
+ key: n,
134
+ "aria-label": `${n} stars`,
135
+ onClick: () => client.rate({
136
+ namespace: s.namespace,
137
+ key: s.key,
138
+ language: client.language,
139
+ translation_hash: s.translation_hash,
140
+ stars: n
141
+ }),
142
+ style: {
143
+ background: "transparent",
144
+ border: "none",
145
+ cursor: "pointer",
146
+ fontSize: "20px",
147
+ color: C.border
148
+ }
149
+ },
150
+ "\u2605"
151
+ )
152
+ )
153
+ )
154
+ ]
155
+ )
156
+ )
157
+ ]
158
+ )
159
+ ]
160
+ )
161
+ );
162
+ };
163
+ }
164
+ });
165
+ return { client, isOpen, controller, FeedbackPanel };
166
+ }
167
+ export {
168
+ FeedbackClient,
169
+ FeedbackError,
170
+ createFeedback
171
+ };
172
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/vue/index.ts"],"sourcesContent":["/**\n * `@verbumia/feedback/vue` — idiomatic Vue 3 adapter over the FROZEN\n * /core FeedbackClient (task 611, master-approved). The host-i18n\n * plugin-slot half is descoped (no @verbumia/vue-i18n source) so this is\n * a standalone composable: it takes explicit config, owns an ISOLATED\n * reactive open-state (a closure-scoped ref — opening it re-renders only\n * the panel, never the host app), and exposes an imperative controller.\n * sessionId is server-minted (no client groupingKey). Mirrors the React\n * plugin principles without a second app-wide context.\n */\nimport { defineComponent, h, ref, Teleport, type Ref } from \"vue\";\n\nimport { FeedbackClient } from \"../core/client\";\nimport { resolveKeys } from \"../core/keys\";\nimport type { DeclaredKey, FeedbackString } from \"../core/types\";\n\nexport interface VueFeedbackConfig {\n apiBase: string;\n projectId: string;\n language: string;\n // No tosVersion — SDK build-time constant (SDK_TOS_VERSION, task 616).\n endUserId?: string;\n keys?: DeclaredKey[];\n fetchImpl?: typeof fetch;\n}\n\nexport interface FeedbackController {\n open: () => void;\n close: () => void;\n client: FeedbackClient;\n}\n\nconst C = {\n bg: \"#0b0f0e\", panel: \"#111714\", border: \"#1f2a25\",\n text: \"#e7f5ef\", dim: \"#8aa79b\", emerald: \"#10b981\", emeraldSoft: \"#34d399\",\n};\n\nexport interface VueFeedback {\n client: FeedbackClient;\n /** Reactive open-state (isolated; not app-wide provide/inject). */\n isOpen: Ref<boolean>;\n controller: FeedbackController;\n /** Functional panel component — mount once near the app root. */\n FeedbackPanel: ReturnType<typeof defineComponent>;\n}\n\nexport function createFeedback(config: VueFeedbackConfig): VueFeedback {\n const client = new FeedbackClient({\n apiBase: config.apiBase,\n projectId: config.projectId,\n language: config.language,\n endUserId: config.endUserId,\n fetchImpl: config.fetchImpl,\n });\n const isOpen = ref(false);\n const controller: FeedbackController = {\n open: () => {\n isOpen.value = true;\n },\n close: () => {\n isOpen.value = false;\n void client.flush();\n },\n client,\n };\n\n const FeedbackPanel = defineComponent({\n name: \"VerbumiaFeedbackPanel\",\n setup() {\n const strings = ref<FeedbackString[]>([]);\n const busy = ref(false);\n const consented = ref(client.hasConsented);\n const error = ref<string | null>(null);\n\n async function load() {\n busy.value = true;\n error.value = null;\n try {\n if (!consented.value) {\n await client.acceptTos();\n consented.value = true;\n }\n const resolved = resolveKeys(config.keys);\n const r = await client.getStrings({\n keys: resolved.length ? resolved : undefined,\n });\n strings.value = r.strings;\n } catch (e) {\n error.value = e instanceof Error ? e.message : \"Failed to load\";\n } finally {\n busy.value = false;\n }\n }\n\n return () => {\n if (!isOpen.value) return null;\n if (!strings.value.length && !busy.value && !error.value) void load();\n return h(\n Teleport,\n { to: \"body\" },\n h(\n \"div\",\n {\n role: \"dialog\",\n style: {\n position: \"fixed\", inset: 0, zIndex: 2147483600,\n background: \"rgba(0,0,0,.55)\", display: \"flex\",\n justifyContent: \"flex-end\",\n },\n onClick: controller.close,\n },\n [\n h(\n \"div\",\n {\n onClick: (e: Event) => e.stopPropagation(),\n style: {\n width: \"min(420px,100%)\", height: \"100%\",\n background: C.bg, color: C.text,\n borderLeft: `1px solid ${C.border}`, padding: \"18px\",\n fontFamily: \"system-ui,sans-serif\", overflowY: \"auto\",\n },\n },\n [\n h(\"strong\", { style: { color: C.emeraldSoft } },\n \"Translation feedback\"),\n error.value\n ? h(\"p\", { style: { color: \"#f87171\" } }, error.value)\n : null,\n ...strings.value.map((s) =>\n h(\n \"div\",\n {\n key: `${s.namespace}:${s.key}`,\n style: {\n background: C.panel,\n border: `1px solid ${C.border}`,\n borderRadius: \"10px\", padding: \"12px\",\n margin: \"10px 0\",\n },\n },\n [\n h(\"div\", { style: { fontSize: \"12px\", color: C.dim } },\n `${s.namespace} · ${s.key}`),\n h(\"div\", { style: { margin: \"6px 0\" } }, s.value),\n h(\n \"div\",\n {},\n [1, 2, 3, 4, 5].map((n) =>\n h(\n \"button\",\n {\n key: n,\n \"aria-label\": `${n} stars`,\n onClick: () =>\n client.rate({\n namespace: s.namespace, key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n stars: n,\n }),\n style: {\n background: \"transparent\", border: \"none\",\n cursor: \"pointer\", fontSize: \"20px\",\n color: C.border,\n },\n },\n \"★\",\n ),\n ),\n ),\n ],\n ),\n ),\n ],\n ),\n ],\n ),\n );\n };\n },\n });\n\n return { client, isOpen, controller, FeedbackPanel };\n}\n\nexport { FeedbackClient } from \"../core/client\";\nexport type {\n DeclaredKey, FeedbackString, RatingInput, SuggestionInput,\n TokenBundle, StringsResponse, BatchResponse,\n} from \"../core/types\";\nexport { FeedbackError } from \"../core/types\";\n"],"mappings":";;;;;;;AAUA,SAAS,iBAAiB,GAAG,KAAK,gBAA0B;AAsB5D,IAAM,IAAI;AAAA,EACR,IAAI;AAAA,EAAW,OAAO;AAAA,EAAW,QAAQ;AAAA,EACzC,MAAM;AAAA,EAAW,KAAK;AAAA,EAAW,SAAS;AAAA,EAAW,aAAa;AACpE;AAWO,SAAS,eAAe,QAAwC;AACrE,QAAM,SAAS,IAAI,eAAe;AAAA,IAChC,SAAS,OAAO;AAAA,IAChB,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO;AAAA,IACjB,WAAW,OAAO;AAAA,IAClB,WAAW,OAAO;AAAA,EACpB,CAAC;AACD,QAAM,SAAS,IAAI,KAAK;AACxB,QAAM,aAAiC;AAAA,IACrC,MAAM,MAAM;AACV,aAAO,QAAQ;AAAA,IACjB;AAAA,IACA,OAAO,MAAM;AACX,aAAO,QAAQ;AACf,WAAK,OAAO,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,EACF;AAEA,QAAM,gBAAgB,gBAAgB;AAAA,IACpC,MAAM;AAAA,IACN,QAAQ;AACN,YAAM,UAAU,IAAsB,CAAC,CAAC;AACxC,YAAM,OAAO,IAAI,KAAK;AACtB,YAAM,YAAY,IAAI,OAAO,YAAY;AACzC,YAAM,QAAQ,IAAmB,IAAI;AAErC,qBAAe,OAAO;AACpB,aAAK,QAAQ;AACb,cAAM,QAAQ;AACd,YAAI;AACF,cAAI,CAAC,UAAU,OAAO;AACpB,kBAAM,OAAO,UAAU;AACvB,sBAAU,QAAQ;AAAA,UACpB;AACA,gBAAM,WAAW,YAAY,OAAO,IAAI;AACxC,gBAAM,IAAI,MAAM,OAAO,WAAW;AAAA,YAChC,MAAM,SAAS,SAAS,WAAW;AAAA,UACrC,CAAC;AACD,kBAAQ,QAAQ,EAAE;AAAA,QACpB,SAAS,GAAG;AACV,gBAAM,QAAQ,aAAa,QAAQ,EAAE,UAAU;AAAA,QACjD,UAAE;AACA,eAAK,QAAQ;AAAA,QACf;AAAA,MACF;AAEA,aAAO,MAAM;AACX,YAAI,CAAC,OAAO,MAAO,QAAO;AAC1B,YAAI,CAAC,QAAQ,MAAM,UAAU,CAAC,KAAK,SAAS,CAAC,MAAM,MAAO,MAAK,KAAK;AACpE,eAAO;AAAA,UACL;AAAA,UACA,EAAE,IAAI,OAAO;AAAA,UACb;AAAA,YACE;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,UAAU;AAAA,gBAAS,OAAO;AAAA,gBAAG,QAAQ;AAAA,gBACrC,YAAY;AAAA,gBAAmB,SAAS;AAAA,gBACxC,gBAAgB;AAAA,cAClB;AAAA,cACA,SAAS,WAAW;AAAA,YACtB;AAAA,YACA;AAAA,cACE;AAAA,gBACE;AAAA,gBACA;AAAA,kBACE,SAAS,CAAC,MAAa,EAAE,gBAAgB;AAAA,kBACzC,OAAO;AAAA,oBACL,OAAO;AAAA,oBAAmB,QAAQ;AAAA,oBAClC,YAAY,EAAE;AAAA,oBAAI,OAAO,EAAE;AAAA,oBAC3B,YAAY,aAAa,EAAE,MAAM;AAAA,oBAAI,SAAS;AAAA,oBAC9C,YAAY;AAAA,oBAAwB,WAAW;AAAA,kBACjD;AAAA,gBACF;AAAA,gBACA;AAAA,kBACE;AAAA,oBAAE;AAAA,oBAAU,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE;AAAA,oBAC5C;AAAA,kBAAsB;AAAA,kBACxB,MAAM,QACF,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,UAAU,EAAE,GAAG,MAAM,KAAK,IACnD;AAAA,kBACJ,GAAG,QAAQ,MAAM;AAAA,oBAAI,CAAC,MACpB;AAAA,sBACE;AAAA,sBACA;AAAA,wBACE,KAAK,GAAG,EAAE,SAAS,IAAI,EAAE,GAAG;AAAA,wBAC5B,OAAO;AAAA,0BACL,YAAY,EAAE;AAAA,0BACd,QAAQ,aAAa,EAAE,MAAM;AAAA,0BAC7B,cAAc;AAAA,0BAAQ,SAAS;AAAA,0BAC/B,QAAQ;AAAA,wBACV;AAAA,sBACF;AAAA,sBACA;AAAA,wBACE;AAAA,0BAAE;AAAA,0BAAO,EAAE,OAAO,EAAE,UAAU,QAAQ,OAAO,EAAE,IAAI,EAAE;AAAA,0BACnD,GAAG,EAAE,SAAS,SAAM,EAAE,GAAG;AAAA,wBAAE;AAAA,wBAC7B,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,QAAQ,EAAE,GAAG,EAAE,KAAK;AAAA,wBAChD;AAAA,0BACE;AAAA,0BACA,CAAC;AAAA,0BACD,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE;AAAA,4BAAI,CAAC,MACnB;AAAA,8BACE;AAAA,8BACA;AAAA,gCACE,KAAK;AAAA,gCACL,cAAc,GAAG,CAAC;AAAA,gCAClB,SAAS,MACP,OAAO,KAAK;AAAA,kCACV,WAAW,EAAE;AAAA,kCAAW,KAAK,EAAE;AAAA,kCAC/B,UAAU,OAAO;AAAA,kCACjB,kBAAkB,EAAE;AAAA,kCACpB,OAAO;AAAA,gCACT,CAAC;AAAA,gCACH,OAAO;AAAA,kCACL,YAAY;AAAA,kCAAe,QAAQ;AAAA,kCACnC,QAAQ;AAAA,kCAAW,UAAU;AAAA,kCAC7B,OAAO,EAAE;AAAA,gCACX;AAAA,8BACF;AAAA,8BACA;AAAA,4BACF;AAAA,0BACF;AAAA,wBACF;AAAA,sBACF;AAAA,oBACF;AAAA,kBACF;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,EAAE,QAAQ,QAAQ,YAAY,cAAc;AACrD;","names":[]}
package/package.json ADDED
@@ -0,0 +1,108 @@
1
+ {
2
+ "name": "@verbumia/feedback",
3
+ "version": "0.1.0",
4
+ "description": "Verbumia End-User Translation Evaluation widget — let your end users rate and suggest translations. React (web) + React Native / Expo.",
5
+ "license": "MIT",
6
+ "homepage": "https://verbumia.ca",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/verbumia/verbumia-feedback.git"
10
+ },
11
+ "keywords": [
12
+ "i18n",
13
+ "translations",
14
+ "feedback",
15
+ "ratings",
16
+ "react",
17
+ "react-native",
18
+ "expo",
19
+ "verbumia"
20
+ ],
21
+ "author": "Verbumia",
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "exports": {
25
+ "./react": {
26
+ "types": "./dist/react/index.d.ts",
27
+ "import": "./dist/react/index.js",
28
+ "require": "./dist/react/index.cjs"
29
+ },
30
+ "./native": {
31
+ "types": "./dist/native/index.d.ts",
32
+ "import": "./dist/native/index.js",
33
+ "require": "./dist/native/index.cjs"
34
+ },
35
+ "./core": {
36
+ "types": "./dist/core/index.d.ts",
37
+ "import": "./dist/core/index.js",
38
+ "require": "./dist/core/index.cjs"
39
+ },
40
+ "./vue": {
41
+ "types": "./dist/vue/index.d.ts",
42
+ "import": "./dist/vue/index.js",
43
+ "require": "./dist/vue/index.cjs"
44
+ },
45
+ "./svelte": {
46
+ "types": "./dist/svelte/index.d.ts",
47
+ "import": "./dist/svelte/index.js",
48
+ "require": "./dist/svelte/index.cjs"
49
+ }
50
+ },
51
+ "files": [
52
+ "dist",
53
+ "README.md",
54
+ "CONTRACT.md",
55
+ "LICENSE"
56
+ ],
57
+ "engines": {
58
+ "node": ">=18"
59
+ },
60
+ "peerDependencies": {
61
+ "@verbumia/react-i18next": ">=0.6.0",
62
+ "react": ">=18",
63
+ "react-dom": ">=18",
64
+ "svelte": ">=4",
65
+ "vue": ">=3.3"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "react-dom": {
69
+ "optional": true
70
+ },
71
+ "react-native": {
72
+ "optional": true
73
+ },
74
+ "@verbumia/react-i18next": {
75
+ "optional": true
76
+ },
77
+ "vue": {
78
+ "optional": true
79
+ },
80
+ "svelte": {
81
+ "optional": true
82
+ }
83
+ },
84
+ "devDependencies": {
85
+ "@types/react": "^18.3.0",
86
+ "@types/react-dom": "^18.3.0",
87
+ "happy-dom": "^15.0.0",
88
+ "react": "^18.3.0",
89
+ "react-dom": "^18.3.0",
90
+ "svelte": "^4.2.20",
91
+ "tsup": "^8.3.0",
92
+ "typescript": "^5.5.0",
93
+ "vitest": "^2.1.0",
94
+ "vue": "^3.5.34"
95
+ },
96
+ "scripts": {
97
+ "build": "tsup",
98
+ "test": "vitest run",
99
+ "typecheck": "tsc --noEmit",
100
+ "prepublishOnly": "pnpm typecheck && pnpm test && pnpm build",
101
+ "pack:dry-run": "pnpm pack --dry-run"
102
+ },
103
+ "publishConfig": {
104
+ "access": "public",
105
+ "registry": "https://registry.npmjs.org/",
106
+ "provenance": true
107
+ }
108
+ }