@mahmulp/feedback-sdk 0.0.1
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/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/index.cjs +1714 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +252 -0
- package/dist/index.d.ts +252 -0
- package/dist/index.global.js +397 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +1701 -0
- package/dist/index.js.map +1 -0
- package/dist/mock.cjs +98 -0
- package/dist/mock.cjs.map +1 -0
- package/dist/mock.d.cts +111 -0
- package/dist/mock.d.ts +111 -0
- package/dist/mock.js +96 -0
- package/dist/mock.js.map +1 -0
- package/dist/svelte.d.ts +192 -0
- package/dist/svelte.js +1753 -0
- package/dist/svelte.js.map +1 -0
- package/package.json +89 -0
package/dist/mock.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types shared between the SDK, the API, and the dashboard.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: This file is the SDK's authoritative copy. The same shapes also live
|
|
5
|
+
* in `packages/shared-types/src/index.ts` for the API and dashboard. Keep
|
|
6
|
+
* them in sync when the wire format evolves.
|
|
7
|
+
*/
|
|
8
|
+
type FeedbackStatus = "open" | "resolved" | "archived";
|
|
9
|
+
/** Pin coordinates: percentage-based (for re-rendering) and pixel (fallback). */
|
|
10
|
+
interface FeedbackCoordinates {
|
|
11
|
+
/** 0..1, x position relative to the target element's bounding box. */
|
|
12
|
+
xPercent: number;
|
|
13
|
+
/** 0..1, y position relative to the target element's bounding box. */
|
|
14
|
+
yPercent: number;
|
|
15
|
+
/** Absolute x position in page pixels at capture time. */
|
|
16
|
+
xPx: number;
|
|
17
|
+
/** Absolute y position in page pixels at capture time. */
|
|
18
|
+
yPx: number;
|
|
19
|
+
}
|
|
20
|
+
interface ViewportInfo {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
devicePixelRatio: number;
|
|
24
|
+
}
|
|
25
|
+
interface FeedbackAuthor {
|
|
26
|
+
name: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
}
|
|
29
|
+
interface FeedbackComment {
|
|
30
|
+
id: string;
|
|
31
|
+
author: FeedbackAuthor;
|
|
32
|
+
body: string;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
}
|
|
35
|
+
/** A pin/feedback record as seen on the wire. */
|
|
36
|
+
interface Feedback {
|
|
37
|
+
id: string;
|
|
38
|
+
projectId: string;
|
|
39
|
+
pageUrl: string;
|
|
40
|
+
selector: string;
|
|
41
|
+
coordinates: FeedbackCoordinates;
|
|
42
|
+
viewport: ViewportInfo;
|
|
43
|
+
/** Storage key returned by the API after upload. Absent until a screenshot is uploaded. */
|
|
44
|
+
screenshotKey?: string;
|
|
45
|
+
status: FeedbackStatus;
|
|
46
|
+
thread: FeedbackComment[];
|
|
47
|
+
createdAt: string;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
}
|
|
50
|
+
/** Payload the SDK sends to create a new pin. The server fills in id/timestamps/status/thread. */
|
|
51
|
+
interface CreateFeedbackInput {
|
|
52
|
+
projectId: string;
|
|
53
|
+
pageUrl: string;
|
|
54
|
+
selector: string;
|
|
55
|
+
coordinates: FeedbackCoordinates;
|
|
56
|
+
viewport: ViewportInfo;
|
|
57
|
+
/** Optional first comment posted alongside the pin. */
|
|
58
|
+
comment?: {
|
|
59
|
+
author: FeedbackAuthor;
|
|
60
|
+
body: string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
interface ListFeedbackQuery {
|
|
64
|
+
projectId: string;
|
|
65
|
+
pageUrl?: string;
|
|
66
|
+
status?: FeedbackStatus;
|
|
67
|
+
}
|
|
68
|
+
interface ListFeedbackResult {
|
|
69
|
+
items: Feedback[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Transport contract used by the SDK to talk to "something that persists feedback".
|
|
73
|
+
* Default HTTP implementation lives in `transport.ts`. The mock transport in `mock.ts`
|
|
74
|
+
* is useful for local demos and tests.
|
|
75
|
+
*/
|
|
76
|
+
interface FeedbackTransport {
|
|
77
|
+
list(query: ListFeedbackQuery): Promise<ListFeedbackResult>;
|
|
78
|
+
create(input: CreateFeedbackInput): Promise<Feedback>;
|
|
79
|
+
reply(feedbackId: string, comment: {
|
|
80
|
+
author: FeedbackAuthor;
|
|
81
|
+
body: string;
|
|
82
|
+
}): Promise<Feedback>;
|
|
83
|
+
setStatus(feedbackId: string, status: FeedbackStatus): Promise<Feedback>;
|
|
84
|
+
/** Move a pin: persist new percent + pixel coordinates after a drag. */
|
|
85
|
+
move?(feedbackId: string, coordinates: FeedbackCoordinates): Promise<Feedback>;
|
|
86
|
+
/** Upload a screenshot blob for an already-created feedback. Returns the updated record. */
|
|
87
|
+
uploadScreenshot?(feedbackId: string, blob: Blob): Promise<Feedback>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* In-memory transport for local development / demos / tests.
|
|
92
|
+
*
|
|
93
|
+
* import { createMockTransport } from '@mahmulp/feedback-sdk/mock'
|
|
94
|
+
*
|
|
95
|
+
* No persistence, no network. Useful before the API is wired up.
|
|
96
|
+
*/
|
|
97
|
+
interface MockTransportOptions {
|
|
98
|
+
/** Optional initial feedback records (e.g. seeded from a fixture). */
|
|
99
|
+
initial?: Feedback[];
|
|
100
|
+
/** Override id generation (defaults to `fb_<timestamp>_<rand>`). */
|
|
101
|
+
generateId?: () => string;
|
|
102
|
+
/** Optional fake latency in ms, applied to every call. */
|
|
103
|
+
latencyMs?: number;
|
|
104
|
+
}
|
|
105
|
+
declare function createMockTransport(options?: MockTransportOptions): FeedbackTransport & {
|
|
106
|
+
/** Direct read of the in-memory store for tests / debugging. */
|
|
107
|
+
_all(): Feedback[];
|
|
108
|
+
_reset(): void;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { type MockTransportOptions, createMockTransport };
|
package/dist/mock.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/mock.ts
|
|
2
|
+
function createMockTransport(options = {}) {
|
|
3
|
+
const items = options.initial ? options.initial.map(clone) : [];
|
|
4
|
+
const genId = options.generateId ?? defaultGenerateId;
|
|
5
|
+
const latency = options.latencyMs ?? 0;
|
|
6
|
+
async function delay() {
|
|
7
|
+
if (latency > 0) await new Promise((r) => setTimeout(r, latency));
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
async list(query) {
|
|
11
|
+
await delay();
|
|
12
|
+
const filtered = items.filter((f) => query.projectId ? f.projectId === query.projectId : true).filter((f) => query.pageUrl ? f.pageUrl === query.pageUrl : true).filter((f) => query.status ? f.status === query.status : true).map(clone);
|
|
13
|
+
return { items: filtered };
|
|
14
|
+
},
|
|
15
|
+
async create(input) {
|
|
16
|
+
await delay();
|
|
17
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18
|
+
const id = genId();
|
|
19
|
+
const fb = {
|
|
20
|
+
id,
|
|
21
|
+
projectId: input.projectId,
|
|
22
|
+
pageUrl: input.pageUrl,
|
|
23
|
+
selector: input.selector,
|
|
24
|
+
coordinates: { ...input.coordinates },
|
|
25
|
+
viewport: { ...input.viewport },
|
|
26
|
+
status: "open",
|
|
27
|
+
thread: input.comment ? [
|
|
28
|
+
{
|
|
29
|
+
id: `cm_${id}_0`,
|
|
30
|
+
author: { ...input.comment.author },
|
|
31
|
+
body: input.comment.body,
|
|
32
|
+
createdAt: now
|
|
33
|
+
}
|
|
34
|
+
] : [],
|
|
35
|
+
createdAt: now,
|
|
36
|
+
updatedAt: now
|
|
37
|
+
};
|
|
38
|
+
items.push(fb);
|
|
39
|
+
return clone(fb);
|
|
40
|
+
},
|
|
41
|
+
async reply(feedbackId, comment) {
|
|
42
|
+
await delay();
|
|
43
|
+
const fb = items.find((f) => f.id === feedbackId);
|
|
44
|
+
if (!fb) throw new Error(`feedback not found: ${feedbackId}`);
|
|
45
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
46
|
+
fb.thread.push({
|
|
47
|
+
id: `cm_${feedbackId}_${fb.thread.length}`,
|
|
48
|
+
author: { ...comment.author },
|
|
49
|
+
body: comment.body,
|
|
50
|
+
createdAt: now
|
|
51
|
+
});
|
|
52
|
+
fb.updatedAt = now;
|
|
53
|
+
return clone(fb);
|
|
54
|
+
},
|
|
55
|
+
async setStatus(feedbackId, status) {
|
|
56
|
+
await delay();
|
|
57
|
+
const fb = items.find((f) => f.id === feedbackId);
|
|
58
|
+
if (!fb) throw new Error(`feedback not found: ${feedbackId}`);
|
|
59
|
+
fb.status = status;
|
|
60
|
+
fb.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
61
|
+
return clone(fb);
|
|
62
|
+
},
|
|
63
|
+
async move(feedbackId, coordinates) {
|
|
64
|
+
await delay();
|
|
65
|
+
const fb = items.find((f) => f.id === feedbackId);
|
|
66
|
+
if (!fb) throw new Error(`feedback not found: ${feedbackId}`);
|
|
67
|
+
fb.coordinates = { ...coordinates };
|
|
68
|
+
fb.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
69
|
+
return clone(fb);
|
|
70
|
+
},
|
|
71
|
+
async uploadScreenshot(feedbackId, _blob) {
|
|
72
|
+
await delay();
|
|
73
|
+
const fb = items.find((f) => f.id === feedbackId);
|
|
74
|
+
if (!fb) throw new Error(`feedback not found: ${feedbackId}`);
|
|
75
|
+
fb.screenshotKey = `screenshots/${feedbackId}.png`;
|
|
76
|
+
fb.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
77
|
+
return clone(fb);
|
|
78
|
+
},
|
|
79
|
+
_all() {
|
|
80
|
+
return items.map(clone);
|
|
81
|
+
},
|
|
82
|
+
_reset() {
|
|
83
|
+
items.length = 0;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function defaultGenerateId() {
|
|
88
|
+
return `fb_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
89
|
+
}
|
|
90
|
+
function clone(value) {
|
|
91
|
+
return JSON.parse(JSON.stringify(value));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { createMockTransport };
|
|
95
|
+
//# sourceMappingURL=mock.js.map
|
|
96
|
+
//# sourceMappingURL=mock.js.map
|
package/dist/mock.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mock.ts"],"names":[],"mappings":"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","file":"mock.js","sourcesContent":["import type {\n CreateFeedbackInput,\n Feedback,\n FeedbackAuthor,\n FeedbackCoordinates,\n FeedbackStatus,\n FeedbackTransport,\n ListFeedbackQuery,\n ListFeedbackResult,\n} from \"./types.js\";\n\n/**\n * In-memory transport for local development / demos / tests.\n *\n * import { createMockTransport } from '@mahmulp/feedback-sdk/mock'\n *\n * No persistence, no network. Useful before the API is wired up.\n */\n\nexport interface MockTransportOptions {\n /** Optional initial feedback records (e.g. seeded from a fixture). */\n initial?: Feedback[];\n /** Override id generation (defaults to `fb_<timestamp>_<rand>`). */\n generateId?: () => string;\n /** Optional fake latency in ms, applied to every call. */\n latencyMs?: number;\n}\n\nexport function createMockTransport(options: MockTransportOptions = {}): FeedbackTransport & {\n /** Direct read of the in-memory store for tests / debugging. */\n _all(): Feedback[];\n _reset(): void;\n} {\n const items: Feedback[] = options.initial ? options.initial.map(clone) : [];\n const genId = options.generateId ?? defaultGenerateId;\n const latency = options.latencyMs ?? 0;\n\n async function delay() {\n if (latency > 0) await new Promise((r) => setTimeout(r, latency));\n }\n\n return {\n async list(query: ListFeedbackQuery): Promise<ListFeedbackResult> {\n await delay();\n // When `projectId` is empty, the SDK is relying on the API to scope by\n // key — return every record. When set, scope by it.\n const filtered = items\n .filter((f) => (query.projectId ? f.projectId === query.projectId : true))\n .filter((f) => (query.pageUrl ? f.pageUrl === query.pageUrl : true))\n .filter((f) => (query.status ? f.status === query.status : true))\n .map(clone);\n return { items: filtered };\n },\n\n async create(input: CreateFeedbackInput): Promise<Feedback> {\n await delay();\n const now = new Date().toISOString();\n const id = genId();\n const fb: Feedback = {\n id,\n projectId: input.projectId,\n pageUrl: input.pageUrl,\n selector: input.selector,\n coordinates: { ...input.coordinates },\n viewport: { ...input.viewport },\n status: \"open\",\n thread: input.comment\n ? [\n {\n id: `cm_${id}_0`,\n author: { ...input.comment.author },\n body: input.comment.body,\n createdAt: now,\n },\n ]\n : [],\n createdAt: now,\n updatedAt: now,\n };\n items.push(fb);\n return clone(fb);\n },\n\n async reply(\n feedbackId: string,\n comment: { author: FeedbackAuthor; body: string }\n ): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n const now = new Date().toISOString();\n fb.thread.push({\n id: `cm_${feedbackId}_${fb.thread.length}`,\n author: { ...comment.author },\n body: comment.body,\n createdAt: now,\n });\n fb.updatedAt = now;\n return clone(fb);\n },\n\n async setStatus(feedbackId: string, status: FeedbackStatus): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n fb.status = status;\n fb.updatedAt = new Date().toISOString();\n return clone(fb);\n },\n\n async move(feedbackId: string, coordinates: FeedbackCoordinates): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n fb.coordinates = { ...coordinates };\n fb.updatedAt = new Date().toISOString();\n return clone(fb);\n },\n\n async uploadScreenshot(feedbackId: string, _blob: Blob): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n fb.screenshotKey = `screenshots/${feedbackId}.png`;\n fb.updatedAt = new Date().toISOString();\n return clone(fb);\n },\n\n _all() {\n return items.map(clone);\n },\n _reset() {\n items.length = 0;\n },\n };\n}\n\nfunction defaultGenerateId(): string {\n return `fb_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction clone<T>(value: T): T {\n // Structured clone is fine here; the records are plain JSON shapes.\n return JSON.parse(JSON.stringify(value)) as T;\n}\n"]}
|
package/dist/svelte.d.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types shared between the SDK, the API, and the dashboard.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: This file is the SDK's authoritative copy. The same shapes also live
|
|
5
|
+
* in `packages/shared-types/src/index.ts` for the API and dashboard. Keep
|
|
6
|
+
* them in sync when the wire format evolves.
|
|
7
|
+
*/
|
|
8
|
+
type FeedbackStatus = "open" | "resolved" | "archived";
|
|
9
|
+
/** Pin coordinates: percentage-based (for re-rendering) and pixel (fallback). */
|
|
10
|
+
interface FeedbackCoordinates {
|
|
11
|
+
/** 0..1, x position relative to the target element's bounding box. */
|
|
12
|
+
xPercent: number;
|
|
13
|
+
/** 0..1, y position relative to the target element's bounding box. */
|
|
14
|
+
yPercent: number;
|
|
15
|
+
/** Absolute x position in page pixels at capture time. */
|
|
16
|
+
xPx: number;
|
|
17
|
+
/** Absolute y position in page pixels at capture time. */
|
|
18
|
+
yPx: number;
|
|
19
|
+
}
|
|
20
|
+
interface ViewportInfo {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
devicePixelRatio: number;
|
|
24
|
+
}
|
|
25
|
+
interface FeedbackAuthor {
|
|
26
|
+
name: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
}
|
|
29
|
+
interface FeedbackComment {
|
|
30
|
+
id: string;
|
|
31
|
+
author: FeedbackAuthor;
|
|
32
|
+
body: string;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
}
|
|
35
|
+
/** A pin/feedback record as seen on the wire. */
|
|
36
|
+
interface Feedback {
|
|
37
|
+
id: string;
|
|
38
|
+
projectId: string;
|
|
39
|
+
pageUrl: string;
|
|
40
|
+
selector: string;
|
|
41
|
+
coordinates: FeedbackCoordinates;
|
|
42
|
+
viewport: ViewportInfo;
|
|
43
|
+
/** Storage key returned by the API after upload. Absent until a screenshot is uploaded. */
|
|
44
|
+
screenshotKey?: string;
|
|
45
|
+
status: FeedbackStatus;
|
|
46
|
+
thread: FeedbackComment[];
|
|
47
|
+
createdAt: string;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
}
|
|
50
|
+
/** Payload the SDK sends to create a new pin. The server fills in id/timestamps/status/thread. */
|
|
51
|
+
interface CreateFeedbackInput {
|
|
52
|
+
projectId: string;
|
|
53
|
+
pageUrl: string;
|
|
54
|
+
selector: string;
|
|
55
|
+
coordinates: FeedbackCoordinates;
|
|
56
|
+
viewport: ViewportInfo;
|
|
57
|
+
/** Optional first comment posted alongside the pin. */
|
|
58
|
+
comment?: {
|
|
59
|
+
author: FeedbackAuthor;
|
|
60
|
+
body: string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
interface ListFeedbackQuery {
|
|
64
|
+
projectId: string;
|
|
65
|
+
pageUrl?: string;
|
|
66
|
+
status?: FeedbackStatus;
|
|
67
|
+
}
|
|
68
|
+
interface ListFeedbackResult {
|
|
69
|
+
items: Feedback[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Transport contract used by the SDK to talk to "something that persists feedback".
|
|
73
|
+
* Default HTTP implementation lives in `transport.ts`. The mock transport in `mock.ts`
|
|
74
|
+
* is useful for local demos and tests.
|
|
75
|
+
*/
|
|
76
|
+
interface FeedbackTransport {
|
|
77
|
+
list(query: ListFeedbackQuery): Promise<ListFeedbackResult>;
|
|
78
|
+
create(input: CreateFeedbackInput): Promise<Feedback>;
|
|
79
|
+
reply(feedbackId: string, comment: {
|
|
80
|
+
author: FeedbackAuthor;
|
|
81
|
+
body: string;
|
|
82
|
+
}): Promise<Feedback>;
|
|
83
|
+
setStatus(feedbackId: string, status: FeedbackStatus): Promise<Feedback>;
|
|
84
|
+
/** Move a pin: persist new percent + pixel coordinates after a drag. */
|
|
85
|
+
move?(feedbackId: string, coordinates: FeedbackCoordinates): Promise<Feedback>;
|
|
86
|
+
/** Upload a screenshot blob for an already-created feedback. Returns the updated record. */
|
|
87
|
+
uploadScreenshot?(feedbackId: string, blob: Blob): Promise<Feedback>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface InitFeedbackOptions {
|
|
91
|
+
/** Base URL of the API (used when no `transport` is provided). */
|
|
92
|
+
apiUrl?: string;
|
|
93
|
+
/** Per-project ingest key (sent as `x-feedback-key` to the API). Required when using the HTTP transport. */
|
|
94
|
+
apiKey?: string;
|
|
95
|
+
/** Custom transport override. Useful for tests, mocks, or non-HTTP backends. */
|
|
96
|
+
transport?: FeedbackTransport;
|
|
97
|
+
/**
|
|
98
|
+
* Identifier used for client-side caching (author identity per project).
|
|
99
|
+
* Defaults to `apiKey`'s prefix when omitted; safe to leave unset.
|
|
100
|
+
*/
|
|
101
|
+
cacheNamespace?: string;
|
|
102
|
+
/** Start with feedback mode enabled. Defaults to `false` (caller toggles via UI). */
|
|
103
|
+
enabled?: boolean;
|
|
104
|
+
/** Override how the page URL is captured. Defaults to `window.location.pathname + search`. */
|
|
105
|
+
getPageUrl?: () => string;
|
|
106
|
+
/**
|
|
107
|
+
* Modifier key used to "select parent" while picking a target.
|
|
108
|
+
* Defaults to "Alt".
|
|
109
|
+
*/
|
|
110
|
+
selectParentModifier?: "Alt" | "Shift" | "Meta" | "Control";
|
|
111
|
+
/** Override how the author identity is read. Defaults to a localStorage cache. */
|
|
112
|
+
getAuthor?: () => FeedbackAuthor | null;
|
|
113
|
+
/** Override how the author identity is persisted after a successful submit. */
|
|
114
|
+
setAuthor?: (author: FeedbackAuthor) => void;
|
|
115
|
+
/** Capture a screenshot when a new pin is created. Defaults to `true`. */
|
|
116
|
+
captureScreenshots?: boolean;
|
|
117
|
+
/** Override the screenshot capture function. Returns null to skip silently. */
|
|
118
|
+
captureScreenshot?: () => Promise<Blob | null>;
|
|
119
|
+
/** Render the floating launcher widget. Defaults to `true`. */
|
|
120
|
+
showLauncher?: boolean;
|
|
121
|
+
/** Initial pin visibility. Defaults to `true`. */
|
|
122
|
+
pinsVisible?: boolean;
|
|
123
|
+
/** Called when a pin is clicked in display mode. Use to render a thread popover. */
|
|
124
|
+
onPinClick?: (feedback: Feedback) => void;
|
|
125
|
+
/** Called after a new pin is created. */
|
|
126
|
+
onPinCreate?: (feedback: Feedback) => void;
|
|
127
|
+
/** Called when an error happens during a SDK action. */
|
|
128
|
+
onError?: (err: unknown) => void;
|
|
129
|
+
}
|
|
130
|
+
interface FeedbackController {
|
|
131
|
+
/** Toggle / set feedback creation mode. */
|
|
132
|
+
setEnabled(enabled: boolean): void;
|
|
133
|
+
/** Read the current enabled state. */
|
|
134
|
+
isEnabled(): boolean;
|
|
135
|
+
/** Show or hide all pins (the floating launcher's eye toggle calls this). */
|
|
136
|
+
setPinsVisible(visible: boolean): void;
|
|
137
|
+
/** Show or hide the floating launcher widget. */
|
|
138
|
+
setLauncherVisible(visible: boolean): void;
|
|
139
|
+
/** Trigger a fresh fetch of feedback for the current project. */
|
|
140
|
+
refresh(): Promise<void>;
|
|
141
|
+
/** Tear down: remove DOM, listeners, and observers. Idempotent. */
|
|
142
|
+
destroy(): void;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Svelte adapter — thin lifecycle wrapper around the framework-agnostic core.
|
|
147
|
+
*
|
|
148
|
+
* import { feedback, feedbackEnabled } from '@mahmulp/feedback-sdk/svelte'
|
|
149
|
+
*
|
|
150
|
+
* Usage:
|
|
151
|
+
*
|
|
152
|
+
* <script lang="ts">
|
|
153
|
+
* import { feedback, feedbackEnabled } from '@mahmulp/feedback-sdk/svelte'
|
|
154
|
+
* const enabled = feedbackEnabled()
|
|
155
|
+
* </script>
|
|
156
|
+
*
|
|
157
|
+
* <div use:feedback={{ apiUrl: '/api', apiKey: 'mp_…' }}>
|
|
158
|
+
* <slot />
|
|
159
|
+
* </div>
|
|
160
|
+
*
|
|
161
|
+
* <button on:click={() => enabled.toggle()}>
|
|
162
|
+
* {$enabled ? 'Stop' : 'Start'} feedback
|
|
163
|
+
* </button>
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
type FeedbackActionParams = InitFeedbackOptions;
|
|
167
|
+
interface FeedbackAction {
|
|
168
|
+
destroy(): void;
|
|
169
|
+
update(params: FeedbackActionParams): void;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Svelte action: `use:feedback={{ apiUrl, apiKey }}`.
|
|
173
|
+
*
|
|
174
|
+
* The action is a no-op during SSR (`initFeedback` already returns an SSR-safe
|
|
175
|
+
* controller, but we still skip it to avoid even constructing the host).
|
|
176
|
+
*/
|
|
177
|
+
declare function feedback(node: HTMLElement, params: FeedbackActionParams): FeedbackAction;
|
|
178
|
+
interface EnabledStoreApi {
|
|
179
|
+
subscribe(run: (value: boolean) => void): () => void;
|
|
180
|
+
set(value: boolean): void;
|
|
181
|
+
toggle(): void;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Returns a writable-ish store of the "feedback mode" flag.
|
|
185
|
+
*
|
|
186
|
+
* const enabled = feedbackEnabled()
|
|
187
|
+
* $: console.log($enabled)
|
|
188
|
+
* enabled.toggle()
|
|
189
|
+
*/
|
|
190
|
+
declare function feedbackEnabled(): EnabledStoreApi;
|
|
191
|
+
|
|
192
|
+
export { type CreateFeedbackInput, type Feedback, type FeedbackAction, type FeedbackActionParams, type FeedbackController, type FeedbackStatus, type FeedbackTransport, type InitFeedbackOptions, feedback, feedbackEnabled };
|