@heedkit/sdk-vue 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HeedKit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # @heedkit/sdk-vue
2
+
3
+ Vue 3 SDK for [HeedKit](https://heedkit.com). Drop-in feature requests,
4
+ voting, comments — themed from your console.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npm i @heedkit/sdk-vue
10
+ ```
11
+
12
+ ## Wire the plugin
13
+
14
+ ```ts
15
+ // main.ts
16
+ import { createApp } from "vue";
17
+ import { createHeedKit } from "@heedkit/sdk-vue";
18
+ import App from "./App.vue";
19
+
20
+ createApp(App)
21
+ .use(createHeedKit({
22
+ projectKey: "fh_xxx",
23
+ user: { externalId: "user-123", email: "alice@example.com", name: "Alice" },
24
+ }))
25
+ .mount("#app");
26
+ ```
27
+
28
+ ## Mount the widget anywhere
29
+
30
+ ```vue
31
+ <script setup lang="ts">
32
+ import { FeedbackButton } from "@heedkit/sdk-vue";
33
+ </script>
34
+
35
+ <template>
36
+ <YourApp />
37
+ <FeedbackButton label="Feedback" />
38
+ </template>
39
+ ```
40
+
41
+ `FeedbackButton` is a thin Vue wrapper that mounts the shared JS widget into
42
+ `document.body` — same UI as every other HeedKit SDK.
43
+
44
+ ## Imperative open / close
45
+
46
+ ```vue
47
+ <script setup lang="ts">
48
+ import { ref } from "vue";
49
+ import { FeedbackButton } from "@heedkit/sdk-vue";
50
+
51
+ const btn = ref<InstanceType<typeof FeedbackButton> | null>(null);
52
+ function openPanel() { btn.value?.open(); }
53
+ </script>
54
+
55
+ <template>
56
+ <FeedbackButton ref="btn" :hide-launcher="true" />
57
+ <button @click="openPanel">Custom trigger</button>
58
+ </template>
59
+ ```
60
+
61
+ ## Headless
62
+
63
+ Inject the underlying client for custom UI:
64
+
65
+ ```ts
66
+ import { inject } from "vue";
67
+ import { HEEDKIT_KEY } from "@heedkit/sdk-vue";
68
+
69
+ const fh = inject(HEEDKIT_KEY)!;
70
+ await fh.client.list({ sort: "top" });
71
+ ```
72
+
73
+ ## License
74
+
75
+ MIT
@@ -0,0 +1,9 @@
1
+ type __VLS_Props = {
2
+ label?: string;
3
+ hideLauncher?: boolean;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {
6
+ open: () => void | undefined;
7
+ close: () => void | undefined;
8
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ export default _default;
@@ -0,0 +1,110 @@
1
+ export type Visibility = "public" | "private";
2
+ export type Interaction = "upvote" | "downvote" | "plus_one" | "like";
3
+ export type KindInteractions = Partial<Record<Interaction, boolean>>;
4
+ export type ShowCounts = Partial<Record<FeatureKind, boolean>>;
5
+ export type GroupMode = "tabs" | "list";
6
+ export type Theme = {
7
+ primary?: string;
8
+ primaryDark?: string;
9
+ radius?: number;
10
+ mode?: "light" | "dark" | "system";
11
+ font_family?: string;
12
+ font_size?: "sm" | "md" | "lg";
13
+ group_mode?: GroupMode;
14
+ show_counts?: ShowCounts;
15
+ fontFamily?: string;
16
+ };
17
+ export type EndUser = {
18
+ externalId?: string;
19
+ email?: string;
20
+ name?: string;
21
+ avatarUrl?: string;
22
+ platform?: string;
23
+ };
24
+ export type FeatureKind = "feature_request" | "bug_report" | "improvement" | "appreciation" | "other";
25
+ export type Feature = {
26
+ id: string;
27
+ title: string;
28
+ description: string;
29
+ status: "open" | "planned" | "in_progress" | "shipped" | "declined";
30
+ kind: FeatureKind;
31
+ visibility: Visibility;
32
+ on_roadmap: boolean;
33
+ tag: string | null;
34
+ vote_count: number;
35
+ voted: boolean;
36
+ platform: string | null;
37
+ author_name: string | null;
38
+ created_at: string;
39
+ };
40
+ export type Comment = {
41
+ id: string;
42
+ body: string;
43
+ author_name: string | null;
44
+ is_internal: boolean;
45
+ created_at: string;
46
+ };
47
+ export type HeedKitConfig = {
48
+ projectKey: string;
49
+ apiUrl?: string;
50
+ user?: EndUser;
51
+ };
52
+ export type ProjectConfig = {
53
+ name: string;
54
+ theme: Theme;
55
+ enabled_kinds: FeatureKind[];
56
+ kind_visibility: Record<FeatureKind, Visibility>;
57
+ kind_interactions: Record<FeatureKind, KindInteractions>;
58
+ is_public_roadmap?: boolean;
59
+ };
60
+ export type InitResult = {
61
+ end_user_id: string;
62
+ project: ProjectConfig;
63
+ };
64
+ /**
65
+ * Stable per-browser identifier persisted in localStorage. When the customer
66
+ * doesn't pass `externalId`, we still want votes/submissions to stick to the
67
+ * same EndUser across page loads — otherwise every refresh would create a new
68
+ * anonymous account.
69
+ *
70
+ * Returns null on the server (SSR), so callers should fall back to a fresh id.
71
+ */
72
+ export declare function getOrCreateDeviceId(): string | null;
73
+ export declare class HeedKitClient {
74
+ private apiUrl;
75
+ private projectKey;
76
+ private endUserId;
77
+ private theme;
78
+ private projectName;
79
+ private enabledKinds;
80
+ private kindVisibility;
81
+ private kindInteractions;
82
+ constructor(config: HeedKitConfig);
83
+ init(user?: EndUser): Promise<InitResult>;
84
+ getTheme(): Theme;
85
+ getEnabledKinds(): FeatureKind[];
86
+ getKindVisibility(): Partial<Record<FeatureKind, Visibility>>;
87
+ getKindInteractions(): Partial<Record<FeatureKind, Partial<Record<Interaction, boolean>>>>;
88
+ getProjectName(): string;
89
+ getEndUserId(): string | null;
90
+ getInteractionsFor(kind: FeatureKind): Interaction[];
91
+ list(opts?: {
92
+ status?: string;
93
+ kind?: FeatureKind;
94
+ sort?: "top" | "new";
95
+ }): Promise<Feature[]>;
96
+ submit(input: {
97
+ title: string;
98
+ description?: string;
99
+ tag?: string;
100
+ kind?: FeatureKind;
101
+ }): Promise<Feature>;
102
+ vote(featureId: string): Promise<{
103
+ voted: boolean;
104
+ vote_count: number;
105
+ }>;
106
+ listComments(featureId: string): Promise<Comment[]>;
107
+ comment(featureId: string, body: string): Promise<Comment>;
108
+ private ensureInit;
109
+ private request;
110
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,114 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const w=require("vue"),ae="https://api.heedkit.com",V="heedkit.device_id";function re(){var e;try{if(typeof window>"u"||!window.localStorage)return null;const t=window.localStorage.getItem(V);if(t)return t;const i="dev_"+(((e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))??Math.random().toString(36).slice(2));return window.localStorage.setItem(V,i),i}catch{return null}}function W(e){return{id:String(e.id),title:e.title,description:e.description??"",status:e.status,kind:e.kind,visibility:e.visibility,on_roadmap:e.on_roadmap??!1,tag:e.tag??null,vote_count:e.vote_count??0,voted:e.voted??!1,platform:e.platform??null,author_name:e.author_name??e.author??null,created_at:e.created_at}}function Y(e){return{id:String(e.id),body:e.body,author_name:e.author_name??e.author??null,is_internal:e.is_internal??!1,created_at:e.created_at}}class O{constructor(t){this.endUserId=null,this.theme={},this.projectName="",this.enabledKinds=[],this.kindVisibility={},this.kindInteractions={},this.apiUrl=t.apiUrl||ae,this.projectKey=t.projectKey}async init(t={}){const o={external_id:t.externalId??re()??void 0,email:t.email,name:t.name,avatar_url:t.avatarUrl,platform:t.platform||"web"},n=await this.request("/sdk/init","POST",o);this.endUserId=n.end_user_id;const s=n.project??n;return this.theme=s.theme||{},this.projectName=s.name??s.project_name??"",this.enabledKinds=s.enabled_kinds||[],this.kindVisibility=s.kind_visibility||{},this.kindInteractions=s.kind_interactions||{},n}getTheme(){return this.theme}getEnabledKinds(){return this.enabledKinds}getKindVisibility(){return this.kindVisibility}getKindInteractions(){return this.kindInteractions}getProjectName(){return this.projectName}getEndUserId(){return this.endUserId}getInteractionsFor(t){const i=this.kindInteractions[t]||{};return["upvote","downvote","plus_one","like"].filter(o=>i[o])}async list(t={}){this.ensureInit();const i=new URLSearchParams({end_user_id:this.endUserId});t.status&&i.set("status",t.status),t.kind&&i.set("kind",t.kind),t.sort&&i.set("sort",t.sort);const o=await this.request(`/sdk/features?${i}`,"GET");return(Array.isArray(o)?o:o.features??[]).map(s=>W(s))}async submit(t){this.ensureInit();const i=await this.request("/sdk/features","POST",{end_user_id:this.endUserId,title:t.title,description:t.description||"",tag:t.tag||null,kind:t.kind||"feature_request"});return W(i)}async vote(t){return this.ensureInit(),this.request(`/sdk/features/${t}/vote`,"POST",{end_user_id:this.endUserId})}async listComments(t){const i=await this.request(`/sdk/features/${t}/comments`,"GET");return(Array.isArray(i)?i:i.comments??[]).map(n=>Y(n))}async comment(t,i){this.ensureInit();const o=await this.request(`/sdk/features/${t}/comments`,"POST",{end_user_id:this.endUserId,body:i});return Y(o)}ensureInit(){if(!this.endUserId)throw new Error("HeedKit not initialized — call init() first")}async request(t,i,o){const n=await fetch(`${this.apiUrl}${t}`,{method:i,headers:{"Content-Type":"application/json","X-Project-Key":this.projectKey},body:o?JSON.stringify(o):void 0});if(!n.ok){let s=`HTTP ${n.status}`;try{const h=await n.json();s=h.error||h.detail||s}catch{}throw new Error(s)}return n.json()}}const B=Symbol("heedkit");function ie(e){return{install(t){const i=new O(e),o=w.ref(!1),n=w.ref({});i.init(e.user||{}).then(()=>{n.value=i.getTheme(),o.value=!0}),t.provide(B,{client:i,ready:o,theme:n,projectKey:e.projectKey,apiUrl:e.apiUrl,user:e.user})}}}const P={feature_request:{label:"Features",placeholder:"What should we build?",tabIcon:"💡"},bug_report:{label:"Bugs",placeholder:"What's broken?",tabIcon:"🐞"},improvement:{label:"Improvements",placeholder:"What could be better?",tabIcon:"✨"},appreciation:{label:"Appreciation",placeholder:"What did you love?",tabIcon:"❤️"},other:{label:"Other",placeholder:"Tell us anything",tabIcon:"💬"}},oe={upvote:{icon:"▲",label:"Upvote"},downvote:{icon:"▼",label:"Downvote"},plus_one:{icon:"+1",label:"+1"},like:{icon:"♥",label:"Like"}},R={sm:"13px",md:"14px",lg:"16px"},G="heedkit-styles",se=`
2
+ .fk-launcher {
3
+ position: fixed; bottom: 24px; right: 24px; z-index: 2147483645;
4
+ background: var(--fh-primary); color: #fff; border: 0; border-radius: 999px;
5
+ padding: 12px 18px; font-weight: 600; font-size: var(--fh-fs); cursor: pointer;
6
+ box-shadow: 0 10px 24px rgba(0,0,0,.18); font-family: var(--fh-font);
7
+ transition: transform .15s ease;
8
+ }
9
+ .fk-launcher:hover { transform: translateY(-1px); }
10
+ .fk-overlay {
11
+ position: fixed; inset: 0; z-index: 2147483646; display: flex;
12
+ align-items: center; justify-content: center; padding: 16px;
13
+ background: rgba(0,0,0,.45); backdrop-filter: blur(2px);
14
+ font-family: var(--fh-font); font-size: var(--fh-fs);
15
+ }
16
+ .fk-panel {
17
+ width: 100%; max-width: 520px; max-height: 85vh; display: flex; flex-direction: column;
18
+ background: var(--fh-bg); color: var(--fh-fg);
19
+ border-radius: calc(var(--fh-radius) * 1.5); overflow: hidden;
20
+ box-shadow: 0 20px 60px rgba(0,0,0,.3);
21
+ }
22
+ .fk-head { padding: 18px 20px 12px; border-bottom: 1px solid var(--fh-border); }
23
+ .fk-titlerow { display:flex; justify-content:space-between; align-items:center; }
24
+ .fk-title { font-size: calc(var(--fh-fs) + 6px); font-weight: 700; }
25
+ .fk-close {
26
+ background: transparent; border: 0; color: var(--fh-muted);
27
+ font-size: 22px; cursor: pointer; line-height: 1; padding: 0 4px;
28
+ }
29
+ .fk-modes { display:flex; gap: 6px; margin-top: 12px; }
30
+ .fk-mode {
31
+ border: 0; background: transparent; padding: 6px 12px; border-radius: 999px;
32
+ font-size: calc(var(--fh-fs) - 1px); font-weight: 600; cursor: pointer;
33
+ color: var(--fh-muted); font-family: inherit;
34
+ }
35
+ .fk-mode[data-active="true"] { background: var(--fh-primary); color: #fff; }
36
+ .fk-tabs {
37
+ display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 20px; border-bottom: 1px solid var(--fh-border);
38
+ }
39
+ .fk-tab {
40
+ border: 0; background: var(--fh-row); color: var(--fh-fg);
41
+ padding: 6px 12px; border-radius: 999px;
42
+ font-size: calc(var(--fh-fs) - 1px); font-weight: 500; cursor: pointer; font-family: inherit;
43
+ display: inline-flex; align-items: center; gap: 6px;
44
+ }
45
+ .fk-tab[data-active="true"] { background: var(--fh-primary); color: #fff; }
46
+ .fk-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
47
+ .fk-empty { text-align: center; padding: 32px; opacity: .6; }
48
+ .fk-loading { text-align: center; padding: 32px; opacity: .6; }
49
+ .fk-row {
50
+ display: flex; gap: 12px; padding: 12px; margin-bottom: 8px;
51
+ background: var(--fh-row); border-radius: var(--fh-radius);
52
+ }
53
+ .fk-actions { display:flex; flex-direction: column; gap: 4px; }
54
+ .fk-act {
55
+ border: 1px solid var(--fh-border); background: transparent;
56
+ color: var(--fh-fg); border-radius: calc(var(--fh-radius) - 4px);
57
+ min-width: 44px; padding: 6px 8px; cursor: pointer;
58
+ display: flex; flex-direction: column; align-items: center; gap: 2px;
59
+ font-weight: 600; font-size: calc(var(--fh-fs) - 1px); font-family: inherit;
60
+ }
61
+ .fk-act[data-voted="true"] {
62
+ border: 2px solid var(--fh-primary);
63
+ background: color-mix(in srgb, var(--fh-primary) 14%, transparent);
64
+ color: var(--fh-primary);
65
+ }
66
+ .fk-act[disabled] { cursor: default; opacity: .85; }
67
+ .fk-act .fk-glyph { font-size: calc(var(--fh-fs) + 1px); line-height: 1; }
68
+ .fk-meta { flex:1; min-width:0; cursor: pointer; }
69
+ .fk-item-title { font-weight: 600; }
70
+ .fk-item-desc { opacity: .7; font-size: calc(var(--fh-fs) - 1px); margin-top: 4px; }
71
+ .fk-item-badges { display:flex; gap:6px; margin-top:6px; flex-wrap: wrap; }
72
+ .fk-badge {
73
+ font-size: calc(var(--fh-fs) - 3px); padding: 2px 8px; border-radius: 999px;
74
+ background: var(--fh-border); color: var(--fh-muted); text-transform: uppercase; letter-spacing: .04em;
75
+ }
76
+ .fk-badge[data-status="planned"] { background: rgba(59,130,246,.15); color: rgb(37,99,235); }
77
+ .fk-badge[data-status="in_progress"] { background: rgba(234,179,8,.18); color: rgb(161,98,7); }
78
+ .fk-badge[data-status="shipped"] { background: rgba(34,197,94,.18); color: rgb(22,101,52); }
79
+ .fk-comments { margin-top: 10px; border-top: 1px solid var(--fh-border); padding-top: 10px; }
80
+ .fk-comment { padding: 6px 0; border-top: 1px dashed var(--fh-border); font-size: calc(var(--fh-fs) - 1px); }
81
+ .fk-comment:first-child { border-top: 0; }
82
+ .fk-comment-author { font-weight: 600; }
83
+ .fk-form { display: flex; flex-direction: column; gap: 12px; }
84
+ .fk-label { font-size: calc(var(--fh-fs) - 1px); font-weight: 500; }
85
+ .fk-input, .fk-textarea {
86
+ width: 100%; padding: 10px 12px; margin-top: 4px;
87
+ border-radius: calc(var(--fh-radius) - 2px);
88
+ border: 1px solid var(--fh-input-border); background: var(--fh-input-bg);
89
+ color: var(--fh-fg); font-size: var(--fh-fs); font-family: inherit;
90
+ box-sizing: border-box;
91
+ }
92
+ .fk-textarea { resize: vertical; min-height: 96px; }
93
+ .fk-input:focus, .fk-textarea:focus { outline: 2px solid var(--fh-primary); outline-offset: 1px; }
94
+ .fk-submit {
95
+ background: var(--fh-primary); color: #fff; border: 0;
96
+ padding: 12px 14px; border-radius: var(--fh-radius);
97
+ font-weight: 600; font-size: var(--fh-fs); cursor: pointer; font-family: inherit;
98
+ }
99
+ .fk-submit[disabled] { opacity: .6; cursor: not-allowed; }
100
+ .fk-segmented {
101
+ display: inline-flex; gap: 4px; padding: 4px; margin-top: 6px;
102
+ background: var(--fh-row); border-radius: 999px;
103
+ }
104
+ .fk-seg {
105
+ border: 0; background: transparent; cursor: pointer;
106
+ padding: 6px 12px; border-radius: 999px; font-size: calc(var(--fh-fs) - 2px);
107
+ font-weight: 500; color: var(--fh-muted); font-family: inherit;
108
+ }
109
+ .fk-seg[data-active="true"] {
110
+ background: var(--fh-bg); color: var(--fh-fg);
111
+ box-shadow: 0 1px 2px rgba(0,0,0,.06), 0 2px 8px rgba(0,0,0,.04);
112
+ }
113
+ .fk-error { color: rgb(220,38,38); font-size: calc(var(--fh-fs) - 1px); }
114
+ `;function de(){if(document.getElementById(G))return;const e=document.createElement("style");e.id=G,e.textContent=se,document.head.appendChild(e)}function le(e){const t=e.mode||"light";return t==="system"?typeof window<"u"&&window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t==="dark"?"dark":"light"}function J(e,t){const i=t.primary||"#0D9488",o=`${t.radius??12}px`,n=le(t)==="dark",s=t.font_family||t.fontFamily||"system-ui, -apple-system, sans-serif",h=R[t.font_size??"md"]??R.md,m={"--fh-primary":i,"--fh-radius":o,"--fh-font":s,"--fh-fs":h,"--fh-bg":n?"#0F172A":"#FFFFFF","--fh-fg":n?"#F1F5F9":"#0F172A","--fh-muted":n?"#94A3B8":"#64748B","--fh-row":n?"#1E293B":"#F8FAFC","--fh-border":n?"#1E293B":"#E2E8F0","--fh-input-bg":n?"#0F172A":"#FFFFFF","--fh-input-border":n?"#334155":"#CBD5E1"};for(const[b,C]of Object.entries(m))e.style.setProperty(b,C)}function a(e,t,i){const o=document.createElement(e);if(t)for(const[n,s]of Object.entries(t))n==="class"?o.className=s:o.setAttribute(n,s);if(i)for(const n of i)n!=null&&o.appendChild(typeof n=="string"?document.createTextNode(n):n);return o}function X(e){de();const t=e.container||document.body,i=new O(e),o=i.init(e.user||{}).catch(b=>(console.warn("[HeedKit] widget init failed; launcher disabled.",b),null));let n=null,s=null;function h(){n&&(n.remove(),n=null)}async function m(){await o&&(n||(n=ce(i,i.getTheme(),h),t.appendChild(n)))}return e.hideLauncher||o.then(b=>{b&&(s=a("button",{class:"fk-launcher",type:"button"},[e.label||"Feedback"]),J(s,i.getTheme()),s.addEventListener("click",m),t.appendChild(s))}),{client:i,open:m,close:h,destroy(){h(),s==null||s.remove()}}}function ce(e,t,i){const o=a("div",{class:"fk-overlay",role:"dialog"});J(o,t),o.addEventListener("click",r=>{r.target===o&&i()});const n=a("div",{class:"fk-panel"});o.appendChild(n);const s=e.getEnabledKinds(),h=t.group_mode||"tabs";let m="browse",b=h==="tabs"&&s.length>0?s[0]:"all",C=[];const F=a("div",{class:"fk-head"}),K=a("div",{class:"fk-titlerow"});K.appendChild(a("div",{class:"fk-title"},[e.getProjectName()||"Feedback"]));const N=a("button",{class:"fk-close",type:"button","aria-label":"Close"},["×"]);N.addEventListener("click",i),K.appendChild(N),F.appendChild(K);const D=a("div",{class:"fk-modes"}),T=a("button",{class:"fk-mode",type:"button"},["Browse"]),A=a("button",{class:"fk-mode",type:"button"},["Suggest"]);D.append(T,A),F.appendChild(D),n.appendChild(F);let k=null;if(h==="tabs"&&s.length>0){k=a("div",{class:"fk-tabs"});const r=a("button",{class:"fk-tab",type:"button"},["All"]);r.dataset.kind="all",k.appendChild(r);for(const p of s){const d=P[p],l=a("button",{class:"fk-tab",type:"button"},[a("span",{},[d.tabIcon]),a("span",{},[d.label])]);l.dataset.kind=p,k.appendChild(l)}n.appendChild(k),k.addEventListener("click",p=>{const d=p.target.closest("[data-kind]");d&&(b=d.dataset.kind,L(),j())})}const v=a("div",{class:"fk-body"});n.appendChild(v);function H(){T.setAttribute("data-active",String(m==="browse")),A.setAttribute("data-active",String(m==="suggest")),k&&(k.style.display=m==="browse"?"":"none")}function L(){if(k)for(const r of Array.from(k.children))r.setAttribute("data-active",String(r.dataset.kind===String(b)))}async function j(){v.replaceChildren(a("div",{class:"fk-loading"},["Loading…"]));try{const r={sort:"top"};b!=="all"&&(r.kind=b),C=await e.list(r)}catch(r){v.replaceChildren(a("div",{class:"fk-empty"},[`Failed to load: ${r.message}`]));return}M()}async function Z(r,p){const d=await e.vote(r.id);r.voted=d.voted,r.vote_count=d.vote_count,M()}function Q(r){const p=e.getInteractionsFor(r.kind),d=(e.getTheme().show_counts||{})[r.kind]!==!1,l=a("div",{class:"fk-actions"});if(p.length===0){if(d){const c=a("button",{class:"fk-act",type:"button",disabled:"true"},[a("span",{},[String(r.vote_count)])]);l.appendChild(c)}return l}for(const c of p){const f=oe[c],u=a("button",{class:"fk-act",type:"button","aria-label":f.label},[a("span",{class:"fk-glyph"},[f.icon]),...d?[a("span",{},[String(r.vote_count)])]:[]]);(c==="upvote"||c==="like"||c==="plus_one")&&u.setAttribute("data-voted",String(r.voted)),u.addEventListener("click",g=>{g.stopPropagation(),Z(r)}),l.appendChild(u)}return l}function M(){if(v.innerHTML="",C.length===0){v.appendChild(a("div",{class:"fk-empty"},["No items yet — be the first!"]));return}for(const r of C)v.appendChild(ee(r))}function ee(r){const p=a("div",{class:"fk-row"});p.appendChild(Q(r));const d=a("div",{class:"fk-meta"});d.appendChild(a("div",{class:"fk-item-title"},[r.title])),r.description&&d.appendChild(a("div",{class:"fk-item-desc"},[r.description]));const l=a("div",{class:"fk-item-badges"});if(r.status&&r.status!=="open"){const u=a("span",{class:"fk-badge"},[r.status.replace("_"," ")]);u.setAttribute("data-status",r.status),l.appendChild(u)}r.tag&&l.appendChild(a("span",{class:"fk-badge"},[r.tag])),l.children.length&&d.appendChild(l);let c=!1;const f=a("div",{class:"fk-comments"});return f.style.display="none",d.appendChild(f),d.addEventListener("click",async()=>{const u=f.style.display==="none";if(f.style.display=u?"":"none",u&&!c){c=!0,f.replaceChildren(a("div",{class:"fk-loading"},["Loading…"]));try{const g=await e.listComments(r.id);f.replaceChildren(...q(r,g))}catch(g){f.replaceChildren(a("div",{class:"fk-error"},[g.message]))}}}),p.appendChild(d),p}function q(r,p){const d=[];if(p.length===0)d.push(a("div",{class:"fk-empty"},["No replies yet."]));else for(const f of p)d.push(a("div",{class:"fk-comment"},[a("span",{class:"fk-comment-author"},[f.author_name||"Anonymous"])," — ",f.body]));const l=a("textarea",{class:"fk-textarea",placeholder:"Add a reply…",rows:"2"}),c=a("button",{class:"fk-submit",type:"button"},["Reply"]);return c.addEventListener("click",async f=>{var u;if(f.stopPropagation(),!!l.value.trim()){c.disabled=!0;try{const g=await e.comment(r.id,l.value);l.value="",c.disabled=!1;const S=await e.listComments(r.id),x=(u=c.parentElement)==null?void 0:u.parentElement;x&&x.replaceChildren(...q(r,S))}catch(g){c.disabled=!1,alert(g.message)}}}),d.push(a("div",{},[l,c])),d}function te(){v.innerHTML="";const p=e.getEnabledKinds().map(y=>({value:y,...P[y]})),d=p.length>0?p:[{value:"other",...P.other}];let l=d[0].value;const c=a("form",{class:"fk-form"}),f=a("label",{class:"fk-label"},["What's this about?"]),u=a("div",{class:"fk-segmented"}),g=[];for(const y of d){const _=a("button",{class:"fk-seg",type:"button"},[y.label]);_.setAttribute("data-active",String(y.value===l)),_.addEventListener("click",()=>{l=y.value,g.forEach((U,ne)=>U.setAttribute("data-active",String(d[ne].value===l))),x.placeholder=d.find(U=>U.value===l).placeholder}),g.push(_),u.appendChild(_)}f.appendChild(u);const S=a("label",{class:"fk-label"},["Title"]),x=a("input",{class:"fk-input",type:"text",placeholder:d[0].placeholder,required:"true"});S.appendChild(x);const $=a("label",{class:"fk-label"},["Description"]),z=a("textarea",{class:"fk-textarea",placeholder:"Any extra context helps.",rows:"4"});$.appendChild(z);const I=a("button",{class:"fk-submit",type:"submit"},["Submit"]);c.append(f,S,$,I),v.appendChild(c),c.addEventListener("submit",async y=>{if(y.preventDefault(),!!x.value.trim()){I.disabled=!0,I.textContent="Submitting…";try{await e.submit({title:x.value,description:z.value,kind:l}),x.value="",z.value="",E("browse"),k&&(b=l,L()),await j()}catch(_){I.disabled=!1,I.textContent="Submit",alert(_.message)}}})}function E(r){m=r,H(),m==="browse"?j():te()}return T.addEventListener("click",()=>E("browse")),A.addEventListener("click",()=>E("suggest")),H(),L(),E("browse"),o}const pe=w.defineComponent({__name:"FeedbackButton",props:{label:{},hideLauncher:{type:Boolean}},setup(e,{expose:t}){const i=e,o=w.inject(B);if(!o)throw new Error("Install the HeedKit plugin first: app.use(createHeedKit(...))");let n=null;function s(){n||(n=X({projectKey:o.projectKey,apiUrl:o.apiUrl,user:o.user,label:i.label,hideLauncher:i.hideLauncher}))}return w.watch(()=>o.ready.value,h=>{h&&s()},{immediate:!0}),w.onMounted(()=>{o.ready.value&&s()}),w.onBeforeUnmount(()=>{n==null||n.destroy(),n=null}),t({open:()=>n==null?void 0:n.open(),close:()=>n==null?void 0:n.close()}),(h,m)=>null}});exports.FeedbackButton=pe;exports.HEEDKIT_KEY=B;exports.HeedKitClient=O;exports.createHeedKit=ie;exports.mount=X;
@@ -0,0 +1,5 @@
1
+ export { createHeedKit, HEEDKIT_KEY, type HeedKitInjection } from "./plugin";
2
+ export { HeedKitClient } from "./client";
3
+ export { mount, type Widget } from "./widget";
4
+ export type { Comment, EndUser, Feature, FeatureKind, HeedKitConfig, GroupMode, InitResult, Interaction, KindInteractions, ShowCounts, Theme, Visibility, } from "./client";
5
+ export { default as FeedbackButton } from "./FeedbackButton.vue";
package/dist/index.js ADDED
@@ -0,0 +1,584 @@
1
+ import { ref as q, defineComponent as ne, inject as ae, watch as re, onMounted as ie, onBeforeUnmount as se } from "vue";
2
+ const oe = "https://api.heedkit.com", H = "heedkit.device_id";
3
+ function de() {
4
+ var e;
5
+ try {
6
+ if (typeof window > "u" || !window.localStorage) return null;
7
+ const t = window.localStorage.getItem(H);
8
+ if (t) return t;
9
+ const i = "dev_" + (((e = crypto == null ? void 0 : crypto.randomUUID) == null ? void 0 : e.call(crypto)) ?? Math.random().toString(36).slice(2));
10
+ return window.localStorage.setItem(H, i), i;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function $(e) {
16
+ return {
17
+ id: String(e.id),
18
+ title: e.title,
19
+ description: e.description ?? "",
20
+ status: e.status,
21
+ kind: e.kind,
22
+ visibility: e.visibility,
23
+ on_roadmap: e.on_roadmap ?? !1,
24
+ tag: e.tag ?? null,
25
+ vote_count: e.vote_count ?? 0,
26
+ voted: e.voted ?? !1,
27
+ platform: e.platform ?? null,
28
+ author_name: e.author_name ?? e.author ?? null,
29
+ created_at: e.created_at
30
+ };
31
+ }
32
+ function V(e) {
33
+ return {
34
+ id: String(e.id),
35
+ body: e.body,
36
+ author_name: e.author_name ?? e.author ?? null,
37
+ // The SDK endpoint only ever returns public comments.
38
+ is_internal: e.is_internal ?? !1,
39
+ created_at: e.created_at
40
+ };
41
+ }
42
+ class Y {
43
+ constructor(t) {
44
+ this.endUserId = null, this.theme = {}, this.projectName = "", this.enabledKinds = [], this.kindVisibility = {}, this.kindInteractions = {}, this.apiUrl = t.apiUrl || oe, this.projectKey = t.projectKey;
45
+ }
46
+ async init(t = {}) {
47
+ const s = {
48
+ external_id: t.externalId ?? de() ?? void 0,
49
+ email: t.email,
50
+ name: t.name,
51
+ avatar_url: t.avatarUrl,
52
+ platform: t.platform || "web"
53
+ }, n = await this.request("/sdk/init", "POST", s);
54
+ this.endUserId = n.end_user_id;
55
+ const o = n.project ?? n;
56
+ return this.theme = o.theme || {}, this.projectName = o.name ?? o.project_name ?? "", this.enabledKinds = o.enabled_kinds || [], this.kindVisibility = o.kind_visibility || {}, this.kindInteractions = o.kind_interactions || {}, n;
57
+ }
58
+ getTheme() {
59
+ return this.theme;
60
+ }
61
+ getEnabledKinds() {
62
+ return this.enabledKinds;
63
+ }
64
+ getKindVisibility() {
65
+ return this.kindVisibility;
66
+ }
67
+ getKindInteractions() {
68
+ return this.kindInteractions;
69
+ }
70
+ getProjectName() {
71
+ return this.projectName;
72
+ }
73
+ getEndUserId() {
74
+ return this.endUserId;
75
+ }
76
+ /// Convenience: which interactions are enabled for a given kind, in stable order.
77
+ getInteractionsFor(t) {
78
+ const i = this.kindInteractions[t] || {};
79
+ return ["upvote", "downvote", "plus_one", "like"].filter(
80
+ (s) => i[s]
81
+ );
82
+ }
83
+ async list(t = {}) {
84
+ this.ensureInit();
85
+ const i = new URLSearchParams({ end_user_id: this.endUserId });
86
+ t.status && i.set("status", t.status), t.kind && i.set("kind", t.kind), t.sort && i.set("sort", t.sort);
87
+ const s = await this.request(`/sdk/features?${i}`, "GET");
88
+ return (Array.isArray(s) ? s : s.features ?? []).map((o) => $(o));
89
+ }
90
+ async submit(t) {
91
+ this.ensureInit();
92
+ const i = await this.request("/sdk/features", "POST", {
93
+ end_user_id: this.endUserId,
94
+ title: t.title,
95
+ description: t.description || "",
96
+ tag: t.tag || null,
97
+ kind: t.kind || "feature_request"
98
+ });
99
+ return $(i);
100
+ }
101
+ async vote(t) {
102
+ return this.ensureInit(), this.request(`/sdk/features/${t}/vote`, "POST", {
103
+ end_user_id: this.endUserId
104
+ });
105
+ }
106
+ async listComments(t) {
107
+ const i = await this.request(`/sdk/features/${t}/comments`, "GET");
108
+ return (Array.isArray(i) ? i : i.comments ?? []).map((n) => V(n));
109
+ }
110
+ async comment(t, i) {
111
+ this.ensureInit();
112
+ const s = await this.request(`/sdk/features/${t}/comments`, "POST", {
113
+ end_user_id: this.endUserId,
114
+ body: i
115
+ });
116
+ return V(s);
117
+ }
118
+ ensureInit() {
119
+ if (!this.endUserId) throw new Error("HeedKit not initialized — call init() first");
120
+ }
121
+ async request(t, i, s) {
122
+ const n = await fetch(`${this.apiUrl}${t}`, {
123
+ method: i,
124
+ headers: {
125
+ "Content-Type": "application/json",
126
+ "X-Project-Key": this.projectKey
127
+ },
128
+ body: s ? JSON.stringify(s) : void 0
129
+ });
130
+ if (!n.ok) {
131
+ let o = `HTTP ${n.status}`;
132
+ try {
133
+ const h = await n.json();
134
+ o = h.error || h.detail || o;
135
+ } catch {
136
+ }
137
+ throw new Error(o);
138
+ }
139
+ return n.json();
140
+ }
141
+ }
142
+ const G = Symbol("heedkit");
143
+ function me(e) {
144
+ return {
145
+ install(t) {
146
+ const i = new Y(e), s = q(!1), n = q({});
147
+ i.init(e.user || {}).then(() => {
148
+ n.value = i.getTheme(), s.value = !0;
149
+ }), t.provide(G, {
150
+ client: i,
151
+ ready: s,
152
+ theme: n,
153
+ projectKey: e.projectKey,
154
+ apiUrl: e.apiUrl,
155
+ user: e.user
156
+ });
157
+ }
158
+ };
159
+ }
160
+ const U = {
161
+ feature_request: { label: "Features", placeholder: "What should we build?", tabIcon: "💡" },
162
+ bug_report: { label: "Bugs", placeholder: "What's broken?", tabIcon: "🐞" },
163
+ improvement: { label: "Improvements", placeholder: "What could be better?", tabIcon: "✨" },
164
+ appreciation: { label: "Appreciation", placeholder: "What did you love?", tabIcon: "❤️" },
165
+ other: { label: "Other", placeholder: "Tell us anything", tabIcon: "💬" }
166
+ }, le = {
167
+ upvote: { icon: "▲", label: "Upvote" },
168
+ downvote: { icon: "▼", label: "Downvote" },
169
+ plus_one: { icon: "+1", label: "+1" },
170
+ like: { icon: "♥", label: "Like" }
171
+ }, W = { sm: "13px", md: "14px", lg: "16px" }, R = "heedkit-styles", ce = `
172
+ .fk-launcher {
173
+ position: fixed; bottom: 24px; right: 24px; z-index: 2147483645;
174
+ background: var(--fh-primary); color: #fff; border: 0; border-radius: 999px;
175
+ padding: 12px 18px; font-weight: 600; font-size: var(--fh-fs); cursor: pointer;
176
+ box-shadow: 0 10px 24px rgba(0,0,0,.18); font-family: var(--fh-font);
177
+ transition: transform .15s ease;
178
+ }
179
+ .fk-launcher:hover { transform: translateY(-1px); }
180
+ .fk-overlay {
181
+ position: fixed; inset: 0; z-index: 2147483646; display: flex;
182
+ align-items: center; justify-content: center; padding: 16px;
183
+ background: rgba(0,0,0,.45); backdrop-filter: blur(2px);
184
+ font-family: var(--fh-font); font-size: var(--fh-fs);
185
+ }
186
+ .fk-panel {
187
+ width: 100%; max-width: 520px; max-height: 85vh; display: flex; flex-direction: column;
188
+ background: var(--fh-bg); color: var(--fh-fg);
189
+ border-radius: calc(var(--fh-radius) * 1.5); overflow: hidden;
190
+ box-shadow: 0 20px 60px rgba(0,0,0,.3);
191
+ }
192
+ .fk-head { padding: 18px 20px 12px; border-bottom: 1px solid var(--fh-border); }
193
+ .fk-titlerow { display:flex; justify-content:space-between; align-items:center; }
194
+ .fk-title { font-size: calc(var(--fh-fs) + 6px); font-weight: 700; }
195
+ .fk-close {
196
+ background: transparent; border: 0; color: var(--fh-muted);
197
+ font-size: 22px; cursor: pointer; line-height: 1; padding: 0 4px;
198
+ }
199
+ .fk-modes { display:flex; gap: 6px; margin-top: 12px; }
200
+ .fk-mode {
201
+ border: 0; background: transparent; padding: 6px 12px; border-radius: 999px;
202
+ font-size: calc(var(--fh-fs) - 1px); font-weight: 600; cursor: pointer;
203
+ color: var(--fh-muted); font-family: inherit;
204
+ }
205
+ .fk-mode[data-active="true"] { background: var(--fh-primary); color: #fff; }
206
+ .fk-tabs {
207
+ display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 20px; border-bottom: 1px solid var(--fh-border);
208
+ }
209
+ .fk-tab {
210
+ border: 0; background: var(--fh-row); color: var(--fh-fg);
211
+ padding: 6px 12px; border-radius: 999px;
212
+ font-size: calc(var(--fh-fs) - 1px); font-weight: 500; cursor: pointer; font-family: inherit;
213
+ display: inline-flex; align-items: center; gap: 6px;
214
+ }
215
+ .fk-tab[data-active="true"] { background: var(--fh-primary); color: #fff; }
216
+ .fk-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
217
+ .fk-empty { text-align: center; padding: 32px; opacity: .6; }
218
+ .fk-loading { text-align: center; padding: 32px; opacity: .6; }
219
+ .fk-row {
220
+ display: flex; gap: 12px; padding: 12px; margin-bottom: 8px;
221
+ background: var(--fh-row); border-radius: var(--fh-radius);
222
+ }
223
+ .fk-actions { display:flex; flex-direction: column; gap: 4px; }
224
+ .fk-act {
225
+ border: 1px solid var(--fh-border); background: transparent;
226
+ color: var(--fh-fg); border-radius: calc(var(--fh-radius) - 4px);
227
+ min-width: 44px; padding: 6px 8px; cursor: pointer;
228
+ display: flex; flex-direction: column; align-items: center; gap: 2px;
229
+ font-weight: 600; font-size: calc(var(--fh-fs) - 1px); font-family: inherit;
230
+ }
231
+ .fk-act[data-voted="true"] {
232
+ border: 2px solid var(--fh-primary);
233
+ background: color-mix(in srgb, var(--fh-primary) 14%, transparent);
234
+ color: var(--fh-primary);
235
+ }
236
+ .fk-act[disabled] { cursor: default; opacity: .85; }
237
+ .fk-act .fk-glyph { font-size: calc(var(--fh-fs) + 1px); line-height: 1; }
238
+ .fk-meta { flex:1; min-width:0; cursor: pointer; }
239
+ .fk-item-title { font-weight: 600; }
240
+ .fk-item-desc { opacity: .7; font-size: calc(var(--fh-fs) - 1px); margin-top: 4px; }
241
+ .fk-item-badges { display:flex; gap:6px; margin-top:6px; flex-wrap: wrap; }
242
+ .fk-badge {
243
+ font-size: calc(var(--fh-fs) - 3px); padding: 2px 8px; border-radius: 999px;
244
+ background: var(--fh-border); color: var(--fh-muted); text-transform: uppercase; letter-spacing: .04em;
245
+ }
246
+ .fk-badge[data-status="planned"] { background: rgba(59,130,246,.15); color: rgb(37,99,235); }
247
+ .fk-badge[data-status="in_progress"] { background: rgba(234,179,8,.18); color: rgb(161,98,7); }
248
+ .fk-badge[data-status="shipped"] { background: rgba(34,197,94,.18); color: rgb(22,101,52); }
249
+ .fk-comments { margin-top: 10px; border-top: 1px solid var(--fh-border); padding-top: 10px; }
250
+ .fk-comment { padding: 6px 0; border-top: 1px dashed var(--fh-border); font-size: calc(var(--fh-fs) - 1px); }
251
+ .fk-comment:first-child { border-top: 0; }
252
+ .fk-comment-author { font-weight: 600; }
253
+ .fk-form { display: flex; flex-direction: column; gap: 12px; }
254
+ .fk-label { font-size: calc(var(--fh-fs) - 1px); font-weight: 500; }
255
+ .fk-input, .fk-textarea {
256
+ width: 100%; padding: 10px 12px; margin-top: 4px;
257
+ border-radius: calc(var(--fh-radius) - 2px);
258
+ border: 1px solid var(--fh-input-border); background: var(--fh-input-bg);
259
+ color: var(--fh-fg); font-size: var(--fh-fs); font-family: inherit;
260
+ box-sizing: border-box;
261
+ }
262
+ .fk-textarea { resize: vertical; min-height: 96px; }
263
+ .fk-input:focus, .fk-textarea:focus { outline: 2px solid var(--fh-primary); outline-offset: 1px; }
264
+ .fk-submit {
265
+ background: var(--fh-primary); color: #fff; border: 0;
266
+ padding: 12px 14px; border-radius: var(--fh-radius);
267
+ font-weight: 600; font-size: var(--fh-fs); cursor: pointer; font-family: inherit;
268
+ }
269
+ .fk-submit[disabled] { opacity: .6; cursor: not-allowed; }
270
+ .fk-segmented {
271
+ display: inline-flex; gap: 4px; padding: 4px; margin-top: 6px;
272
+ background: var(--fh-row); border-radius: 999px;
273
+ }
274
+ .fk-seg {
275
+ border: 0; background: transparent; cursor: pointer;
276
+ padding: 6px 12px; border-radius: 999px; font-size: calc(var(--fh-fs) - 2px);
277
+ font-weight: 500; color: var(--fh-muted); font-family: inherit;
278
+ }
279
+ .fk-seg[data-active="true"] {
280
+ background: var(--fh-bg); color: var(--fh-fg);
281
+ box-shadow: 0 1px 2px rgba(0,0,0,.06), 0 2px 8px rgba(0,0,0,.04);
282
+ }
283
+ .fk-error { color: rgb(220,38,38); font-size: calc(var(--fh-fs) - 1px); }
284
+ `;
285
+ function pe() {
286
+ if (document.getElementById(R)) return;
287
+ const e = document.createElement("style");
288
+ e.id = R, e.textContent = ce, document.head.appendChild(e);
289
+ }
290
+ function fe(e) {
291
+ const t = e.mode || "light";
292
+ return t === "system" ? typeof window < "u" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : t === "dark" ? "dark" : "light";
293
+ }
294
+ function J(e, t) {
295
+ const i = t.primary || "#0D9488", s = `${t.radius ?? 12}px`, n = fe(t) === "dark", o = t.font_family || t.fontFamily || "system-ui, -apple-system, sans-serif", h = W[t.font_size ?? "md"] ?? W.md, m = {
296
+ "--fh-primary": i,
297
+ "--fh-radius": s,
298
+ "--fh-font": o,
299
+ "--fh-fs": h,
300
+ "--fh-bg": n ? "#0F172A" : "#FFFFFF",
301
+ "--fh-fg": n ? "#F1F5F9" : "#0F172A",
302
+ "--fh-muted": n ? "#94A3B8" : "#64748B",
303
+ "--fh-row": n ? "#1E293B" : "#F8FAFC",
304
+ "--fh-border": n ? "#1E293B" : "#E2E8F0",
305
+ "--fh-input-bg": n ? "#0F172A" : "#FFFFFF",
306
+ "--fh-input-border": n ? "#334155" : "#CBD5E1"
307
+ };
308
+ for (const [b, _] of Object.entries(m)) e.style.setProperty(b, _);
309
+ }
310
+ function a(e, t, i) {
311
+ const s = document.createElement(e);
312
+ if (t) for (const [n, o] of Object.entries(t))
313
+ n === "class" ? s.className = o : s.setAttribute(n, o);
314
+ if (i)
315
+ for (const n of i)
316
+ n != null && s.appendChild(typeof n == "string" ? document.createTextNode(n) : n);
317
+ return s;
318
+ }
319
+ function ue(e) {
320
+ pe();
321
+ const t = e.container || document.body, i = new Y(e), s = i.init(e.user || {}).catch((b) => (console.warn("[HeedKit] widget init failed; launcher disabled.", b), null));
322
+ let n = null, o = null;
323
+ function h() {
324
+ n && (n.remove(), n = null);
325
+ }
326
+ async function m() {
327
+ await s && (n || (n = he(i, i.getTheme(), h), t.appendChild(n)));
328
+ }
329
+ return e.hideLauncher || s.then((b) => {
330
+ b && (o = a("button", { class: "fk-launcher", type: "button" }, [
331
+ e.label || "Feedback"
332
+ ]), J(o, i.getTheme()), o.addEventListener("click", m), t.appendChild(o));
333
+ }), {
334
+ client: i,
335
+ open: m,
336
+ close: h,
337
+ destroy() {
338
+ h(), o == null || o.remove();
339
+ }
340
+ };
341
+ }
342
+ function he(e, t, i) {
343
+ const s = a("div", { class: "fk-overlay", role: "dialog" });
344
+ J(s, t), s.addEventListener("click", (r) => {
345
+ r.target === s && i();
346
+ });
347
+ const n = a("div", { class: "fk-panel" });
348
+ s.appendChild(n);
349
+ const o = e.getEnabledKinds(), h = t.group_mode || "tabs";
350
+ let m = "browse", b = h === "tabs" && o.length > 0 ? o[0] : "all", _ = [];
351
+ const F = a("div", { class: "fk-head" }), S = a("div", { class: "fk-titlerow" });
352
+ S.appendChild(a("div", { class: "fk-title" }, [e.getProjectName() || "Feedback"]));
353
+ const P = a("button", { class: "fk-close", type: "button", "aria-label": "Close" }, ["×"]);
354
+ P.addEventListener("click", i), S.appendChild(P), F.appendChild(S);
355
+ const B = a("div", { class: "fk-modes" }), A = a("button", { class: "fk-mode", type: "button" }, ["Browse"]), T = a("button", { class: "fk-mode", type: "button" }, ["Suggest"]);
356
+ B.append(A, T), F.appendChild(B), n.appendChild(F);
357
+ let k = null;
358
+ if (h === "tabs" && o.length > 0) {
359
+ k = a("div", { class: "fk-tabs" });
360
+ const r = a("button", { class: "fk-tab", type: "button" }, ["All"]);
361
+ r.dataset.kind = "all", k.appendChild(r);
362
+ for (const p of o) {
363
+ const d = U[p], l = a("button", { class: "fk-tab", type: "button" }, [
364
+ a("span", {}, [d.tabIcon]),
365
+ a("span", {}, [d.label])
366
+ ]);
367
+ l.dataset.kind = p, k.appendChild(l);
368
+ }
369
+ n.appendChild(k), k.addEventListener("click", (p) => {
370
+ const d = p.target.closest("[data-kind]");
371
+ d && (b = d.dataset.kind, K(), L());
372
+ });
373
+ }
374
+ const v = a("div", { class: "fk-body" });
375
+ n.appendChild(v);
376
+ function N() {
377
+ A.setAttribute("data-active", String(m === "browse")), T.setAttribute("data-active", String(m === "suggest")), k && (k.style.display = m === "browse" ? "" : "none");
378
+ }
379
+ function K() {
380
+ if (k)
381
+ for (const r of Array.from(k.children))
382
+ r.setAttribute(
383
+ "data-active",
384
+ String(r.dataset.kind === String(b))
385
+ );
386
+ }
387
+ async function L() {
388
+ v.replaceChildren(a("div", { class: "fk-loading" }, ["Loading…"]));
389
+ try {
390
+ const r = { sort: "top" };
391
+ b !== "all" && (r.kind = b), _ = await e.list(r);
392
+ } catch (r) {
393
+ v.replaceChildren(
394
+ a("div", { class: "fk-empty" }, [`Failed to load: ${r.message}`])
395
+ );
396
+ return;
397
+ }
398
+ O();
399
+ }
400
+ async function X(r, p) {
401
+ const d = await e.vote(r.id);
402
+ r.voted = d.voted, r.vote_count = d.vote_count, O();
403
+ }
404
+ function Z(r) {
405
+ const p = e.getInteractionsFor(r.kind), d = (e.getTheme().show_counts || {})[r.kind] !== !1, l = a("div", { class: "fk-actions" });
406
+ if (p.length === 0) {
407
+ if (d) {
408
+ const c = a("button", { class: "fk-act", type: "button", disabled: "true" }, [
409
+ a("span", {}, [String(r.vote_count)])
410
+ ]);
411
+ l.appendChild(c);
412
+ }
413
+ return l;
414
+ }
415
+ for (const c of p) {
416
+ const f = le[c], u = a("button", {
417
+ class: "fk-act",
418
+ type: "button",
419
+ "aria-label": f.label
420
+ }, [
421
+ a("span", { class: "fk-glyph" }, [f.icon]),
422
+ ...d ? [a("span", {}, [String(r.vote_count)])] : []
423
+ ]);
424
+ (c === "upvote" || c === "like" || c === "plus_one") && u.setAttribute("data-voted", String(r.voted)), u.addEventListener("click", (g) => {
425
+ g.stopPropagation(), X(r);
426
+ }), l.appendChild(u);
427
+ }
428
+ return l;
429
+ }
430
+ function O() {
431
+ if (v.innerHTML = "", _.length === 0) {
432
+ v.appendChild(a("div", { class: "fk-empty" }, ["No items yet — be the first!"]));
433
+ return;
434
+ }
435
+ for (const r of _)
436
+ v.appendChild(Q(r));
437
+ }
438
+ function Q(r) {
439
+ const p = a("div", { class: "fk-row" });
440
+ p.appendChild(Z(r));
441
+ const d = a("div", { class: "fk-meta" });
442
+ d.appendChild(a("div", { class: "fk-item-title" }, [r.title])), r.description && d.appendChild(a("div", { class: "fk-item-desc" }, [r.description]));
443
+ const l = a("div", { class: "fk-item-badges" });
444
+ if (r.status && r.status !== "open") {
445
+ const u = a("span", { class: "fk-badge" }, [r.status.replace("_", " ")]);
446
+ u.setAttribute("data-status", r.status), l.appendChild(u);
447
+ }
448
+ r.tag && l.appendChild(a("span", { class: "fk-badge" }, [r.tag])), l.children.length && d.appendChild(l);
449
+ let c = !1;
450
+ const f = a("div", { class: "fk-comments" });
451
+ return f.style.display = "none", d.appendChild(f), d.addEventListener("click", async () => {
452
+ const u = f.style.display === "none";
453
+ if (f.style.display = u ? "" : "none", u && !c) {
454
+ c = !0, f.replaceChildren(a("div", { class: "fk-loading" }, ["Loading…"]));
455
+ try {
456
+ const g = await e.listComments(r.id);
457
+ f.replaceChildren(...D(r, g));
458
+ } catch (g) {
459
+ f.replaceChildren(
460
+ a("div", { class: "fk-error" }, [g.message])
461
+ );
462
+ }
463
+ }
464
+ }), p.appendChild(d), p;
465
+ }
466
+ function D(r, p) {
467
+ const d = [];
468
+ if (p.length === 0)
469
+ d.push(a("div", { class: "fk-empty" }, ["No replies yet."]));
470
+ else
471
+ for (const f of p)
472
+ d.push(a("div", { class: "fk-comment" }, [
473
+ a("span", { class: "fk-comment-author" }, [f.author_name || "Anonymous"]),
474
+ " — ",
475
+ f.body
476
+ ]));
477
+ const l = a("textarea", {
478
+ class: "fk-textarea",
479
+ placeholder: "Add a reply…",
480
+ rows: "2"
481
+ }), c = a("button", { class: "fk-submit", type: "button" }, ["Reply"]);
482
+ return c.addEventListener("click", async (f) => {
483
+ var u;
484
+ if (f.stopPropagation(), !!l.value.trim()) {
485
+ c.disabled = !0;
486
+ try {
487
+ const g = await e.comment(r.id, l.value);
488
+ l.value = "", c.disabled = !1;
489
+ const E = await e.listComments(r.id), x = (u = c.parentElement) == null ? void 0 : u.parentElement;
490
+ x && x.replaceChildren(...D(r, E));
491
+ } catch (g) {
492
+ c.disabled = !1, alert(g.message);
493
+ }
494
+ }
495
+ }), d.push(a("div", {}, [l, c])), d;
496
+ }
497
+ function ee() {
498
+ v.innerHTML = "";
499
+ const p = e.getEnabledKinds().map((y) => ({ value: y, ...U[y] })), d = p.length > 0 ? p : [
500
+ { value: "other", ...U.other }
501
+ ];
502
+ let l = d[0].value;
503
+ const c = a("form", { class: "fk-form" }), f = a("label", { class: "fk-label" }, ["What's this about?"]), u = a("div", { class: "fk-segmented" }), g = [];
504
+ for (const y of d) {
505
+ const w = a("button", { class: "fk-seg", type: "button" }, [y.label]);
506
+ w.setAttribute("data-active", String(y.value === l)), w.addEventListener("click", () => {
507
+ l = y.value, g.forEach(
508
+ (z, te) => z.setAttribute("data-active", String(d[te].value === l))
509
+ ), x.placeholder = d.find((z) => z.value === l).placeholder;
510
+ }), g.push(w), u.appendChild(w);
511
+ }
512
+ f.appendChild(u);
513
+ const E = a("label", { class: "fk-label" }, ["Title"]), x = a("input", {
514
+ class: "fk-input",
515
+ type: "text",
516
+ placeholder: d[0].placeholder,
517
+ required: "true"
518
+ });
519
+ E.appendChild(x);
520
+ const M = a("label", { class: "fk-label" }, ["Description"]), j = a("textarea", {
521
+ class: "fk-textarea",
522
+ placeholder: "Any extra context helps.",
523
+ rows: "4"
524
+ });
525
+ M.appendChild(j);
526
+ const C = a("button", { class: "fk-submit", type: "submit" }, ["Submit"]);
527
+ c.append(f, E, M, C), v.appendChild(c), c.addEventListener("submit", async (y) => {
528
+ if (y.preventDefault(), !!x.value.trim()) {
529
+ C.disabled = !0, C.textContent = "Submitting…";
530
+ try {
531
+ await e.submit({
532
+ title: x.value,
533
+ description: j.value,
534
+ kind: l
535
+ }), x.value = "", j.value = "", I("browse"), k && (b = l, K()), await L();
536
+ } catch (w) {
537
+ C.disabled = !1, C.textContent = "Submit", alert(w.message);
538
+ }
539
+ }
540
+ });
541
+ }
542
+ function I(r) {
543
+ m = r, N(), m === "browse" ? L() : ee();
544
+ }
545
+ return A.addEventListener("click", () => I("browse")), T.addEventListener("click", () => I("suggest")), N(), K(), I("browse"), s;
546
+ }
547
+ const ge = /* @__PURE__ */ ne({
548
+ __name: "FeedbackButton",
549
+ props: {
550
+ label: {},
551
+ hideLauncher: { type: Boolean }
552
+ },
553
+ setup(e, { expose: t }) {
554
+ const i = e, s = ae(G);
555
+ if (!s) throw new Error("Install the HeedKit plugin first: app.use(createHeedKit(...))");
556
+ let n = null;
557
+ function o() {
558
+ n || (n = ue({
559
+ projectKey: s.projectKey,
560
+ apiUrl: s.apiUrl,
561
+ user: s.user,
562
+ label: i.label,
563
+ hideLauncher: i.hideLauncher
564
+ }));
565
+ }
566
+ return re(() => s.ready.value, (h) => {
567
+ h && o();
568
+ }, { immediate: !0 }), ie(() => {
569
+ s.ready.value && o();
570
+ }), se(() => {
571
+ n == null || n.destroy(), n = null;
572
+ }), t({
573
+ open: () => n == null ? void 0 : n.open(),
574
+ close: () => n == null ? void 0 : n.close()
575
+ }), (h, m) => null;
576
+ }
577
+ });
578
+ export {
579
+ ge as FeedbackButton,
580
+ G as HEEDKIT_KEY,
581
+ Y as HeedKitClient,
582
+ me as createHeedKit,
583
+ ue as mount
584
+ };
@@ -0,0 +1,14 @@
1
+ import { type App, type InjectionKey, type Ref } from "vue";
2
+ import { HeedKitClient, type EndUser, type HeedKitConfig, type Theme } from "./client";
3
+ export type HeedKitInjection = {
4
+ client: HeedKitClient;
5
+ ready: Ref<boolean>;
6
+ theme: Ref<Theme>;
7
+ projectKey: string;
8
+ apiUrl?: string;
9
+ user?: EndUser;
10
+ };
11
+ export declare const HEEDKIT_KEY: InjectionKey<HeedKitInjection>;
12
+ export declare function createHeedKit(config: HeedKitConfig): {
13
+ install(app: App): void;
14
+ };
@@ -0,0 +1,13 @@
1
+ import { HeedKitClient, type HeedKitConfig } from "./client";
2
+ export type MountOptions = HeedKitConfig & {
3
+ label?: string;
4
+ hideLauncher?: boolean;
5
+ container?: HTMLElement;
6
+ };
7
+ export type Widget = {
8
+ client: HeedKitClient;
9
+ open: () => void;
10
+ close: () => void;
11
+ destroy: () => void;
12
+ };
13
+ export declare function mount(options: MountOptions): Widget;
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@heedkit/sdk-vue",
3
+ "version": "0.1.0",
4
+ "description": "HeedKit SDK for Vue 3.",
5
+ "license": "MIT",
6
+ "author": "HeedKit",
7
+ "homepage": "https://github.com/heedkit/heedkit-sdk-vue#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/heedkit/heedkit-sdk-vue.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/heedkit/heedkit-sdk-vue/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.js",
22
+ "require": "./dist/index.cjs",
23
+ "types": "./dist/index.d.ts"
24
+ }
25
+ },
26
+ "sideEffects": false,
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "build": "vite build && vue-tsc --emitDeclarationOnly --declaration --outDir dist",
38
+ "test": "vitest run src/client.test.ts",
39
+ "prepublishOnly": "npm run build && npm test"
40
+ },
41
+ "peerDependencies": {
42
+ "vue": "^3.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@vitejs/plugin-vue": "^5.1.4",
46
+ "typescript": "^5.6.3",
47
+ "vite": "^5.4.10",
48
+ "vitest": "^3.2.4",
49
+ "vue": "^3.5.12",
50
+ "vue-tsc": "^2.1.10"
51
+ }
52
+ }