@feedvalue/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/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # @feedvalue/vue
2
+
3
+ Vue SDK for FeedValue feedback widget. Provides Plugin and Composables for Vue 3+.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @feedvalue/vue
9
+ # or
10
+ pnpm add @feedvalue/vue
11
+ # or
12
+ yarn add @feedvalue/vue
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Setup with Plugin
18
+
19
+ ```typescript
20
+ // main.ts
21
+ import { createApp } from 'vue';
22
+ import { createFeedValue } from '@feedvalue/vue';
23
+ import App from './App.vue';
24
+
25
+ const app = createApp(App);
26
+
27
+ app.use(createFeedValue({
28
+ widgetId: 'your-widget-id',
29
+ config: {
30
+ theme: 'auto',
31
+ },
32
+ }));
33
+
34
+ app.mount('#app');
35
+ ```
36
+
37
+ ### Using the Composable
38
+
39
+ ```vue
40
+ <script setup>
41
+ import { useFeedValue } from '@feedvalue/vue';
42
+
43
+ const { open, isReady, isOpen } = useFeedValue();
44
+ </script>
45
+
46
+ <template>
47
+ <button @click="open" :disabled="!isReady">
48
+ {{ isOpen ? 'Close' : 'Give Feedback' }}
49
+ </button>
50
+ </template>
51
+ ```
52
+
53
+ ### Standalone Usage (No Plugin)
54
+
55
+ ```vue
56
+ <script setup>
57
+ import { useFeedValue } from '@feedvalue/vue';
58
+
59
+ // Pass widgetId directly when not using plugin
60
+ const { open, isReady } = useFeedValue('your-widget-id');
61
+ </script>
62
+
63
+ <template>
64
+ <button @click="open" :disabled="!isReady">
65
+ Feedback
66
+ </button>
67
+ </template>
68
+ ```
69
+
70
+ ### Programmatic Submission
71
+
72
+ ```vue
73
+ <script setup>
74
+ import { ref } from 'vue';
75
+ import { useFeedValue } from '@feedvalue/vue';
76
+
77
+ const { submit, isSubmitting, error } = useFeedValue();
78
+ const message = ref('');
79
+
80
+ async function handleSubmit() {
81
+ try {
82
+ await submit({ message: message.value });
83
+ message.value = '';
84
+ console.log('Feedback submitted!');
85
+ } catch (err) {
86
+ console.error('Failed:', err);
87
+ }
88
+ }
89
+ </script>
90
+
91
+ <template>
92
+ <form @submit.prevent="handleSubmit">
93
+ <textarea v-model="message" placeholder="Your feedback..." />
94
+ <button type="submit" :disabled="isSubmitting">
95
+ {{ isSubmitting ? 'Submitting...' : 'Submit' }}
96
+ </button>
97
+ <p v-if="error" class="error">{{ error.message }}</p>
98
+ </form>
99
+ </template>
100
+ ```
101
+
102
+ ### User Identification
103
+
104
+ ```vue
105
+ <script setup>
106
+ import { watch } from 'vue';
107
+ import { useFeedValue } from '@feedvalue/vue';
108
+
109
+ const props = defineProps<{ user: User | null }>();
110
+ const { identify, setData, reset } = useFeedValue();
111
+
112
+ watch(() => props.user, (user) => {
113
+ if (user) {
114
+ identify(user.id, {
115
+ name: user.name,
116
+ email: user.email,
117
+ plan: user.plan,
118
+ });
119
+ setData({ company: user.company });
120
+ } else {
121
+ reset();
122
+ }
123
+ }, { immediate: true });
124
+ </script>
125
+ ```
126
+
127
+ ### Options API Support
128
+
129
+ ```vue
130
+ <script>
131
+ export default {
132
+ methods: {
133
+ openFeedback() {
134
+ // Access via global property
135
+ this.$feedvalue?.open();
136
+ },
137
+ },
138
+ };
139
+ </script>
140
+
141
+ <template>
142
+ <button @click="openFeedback">Feedback</button>
143
+ </template>
144
+ ```
145
+
146
+ ## API Reference
147
+
148
+ ### `createFeedValue(options)`
149
+
150
+ Creates a Vue plugin for FeedValue.
151
+
152
+ | Option | Type | Required | Description |
153
+ |--------|------|----------|-------------|
154
+ | `widgetId` | `string` | Yes | Widget ID from FeedValue dashboard |
155
+ | `apiBaseUrl` | `string` | No | Custom API URL (for self-hosted) |
156
+ | `config` | `Partial<FeedValueConfig>` | No | Configuration overrides |
157
+
158
+ ### `useFeedValue(widgetId?, config?)`
159
+
160
+ Composable to access FeedValue functionality.
161
+
162
+ **Parameters:**
163
+
164
+ | Param | Type | Description |
165
+ |-------|------|-------------|
166
+ | `widgetId` | `string` | Optional widget ID (not needed if plugin is installed) |
167
+ | `config` | `Partial<FeedValueConfig>` | Optional config overrides |
168
+
169
+ **Returns:**
170
+
171
+ | Property | Type | Description |
172
+ |----------|------|-------------|
173
+ | `instance` | `Readonly<ShallowRef<FeedValueInstance \| null>>` | Raw instance |
174
+ | `isReady` | `Readonly<Ref<boolean>>` | Widget initialized |
175
+ | `isOpen` | `Readonly<Ref<boolean>>` | Modal is open |
176
+ | `isVisible` | `Readonly<Ref<boolean>>` | Trigger is visible |
177
+ | `error` | `Readonly<Ref<Error \| null>>` | Current error |
178
+ | `isSubmitting` | `Readonly<Ref<boolean>>` | Submission in progress |
179
+ | `open` | `() => void` | Open modal |
180
+ | `close` | `() => void` | Close modal |
181
+ | `toggle` | `() => void` | Toggle modal |
182
+ | `show` | `() => void` | Show trigger |
183
+ | `hide` | `() => void` | Hide trigger |
184
+ | `submit` | `(feedback) => Promise<void>` | Submit feedback |
185
+ | `identify` | `(userId, traits?) => void` | Identify user |
186
+ | `setData` | `(data) => void` | Set user data |
187
+ | `reset` | `() => void` | Reset user data |
188
+
189
+ ### Injection Keys
190
+
191
+ For advanced usage with `inject()`:
192
+
193
+ ```typescript
194
+ import { inject } from 'vue';
195
+ import { FEEDVALUE_KEY, FEEDVALUE_OPTIONS_KEY } from '@feedvalue/vue';
196
+
197
+ // Get raw FeedValue instance
198
+ const instance = inject(FEEDVALUE_KEY);
199
+
200
+ // Get plugin options
201
+ const options = inject(FEEDVALUE_OPTIONS_KEY);
202
+ ```
203
+
204
+ ## Nuxt 3 Integration
205
+
206
+ Create a plugin file:
207
+
208
+ ```typescript
209
+ // plugins/feedvalue.client.ts
210
+ import { createFeedValue } from '@feedvalue/vue';
211
+
212
+ export default defineNuxtPlugin((nuxtApp) => {
213
+ nuxtApp.vueApp.use(createFeedValue({
214
+ widgetId: 'your-widget-id',
215
+ }));
216
+ });
217
+ ```
218
+
219
+ The `.client.ts` suffix ensures the plugin only runs on the client side.
220
+
221
+ ## SSR Support
222
+
223
+ The SDK handles SSR automatically:
224
+ - Plugin only initializes on client side
225
+ - Composable returns safe defaults during SSR
226
+ - No hydration mismatches
227
+
228
+ ```vue
229
+ <script setup>
230
+ import { useFeedValue } from '@feedvalue/vue';
231
+
232
+ const { isReady } = useFeedValue();
233
+ // isReady.value is false during SSR
234
+ </script>
235
+
236
+ <template>
237
+ <!-- Safe to use in SSR context -->
238
+ <button :disabled="!isReady">Feedback</button>
239
+ </template>
240
+ ```
241
+
242
+ ## Requirements
243
+
244
+ - Vue 3.3.0 or higher
245
+
246
+ ## License
247
+
248
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ var core = require('@feedvalue/core');
4
+ var vue = require('vue');
5
+
6
+ // src/plugin.ts
7
+ var FEEDVALUE_KEY = /* @__PURE__ */ Symbol("feedvalue");
8
+ var FEEDVALUE_OPTIONS_KEY = /* @__PURE__ */ Symbol("feedvalue-options");
9
+ function createFeedValue(options) {
10
+ let instance = null;
11
+ return {
12
+ install(app) {
13
+ if (typeof window !== "undefined") {
14
+ instance = core.FeedValue.init({
15
+ widgetId: options.widgetId,
16
+ apiBaseUrl: options.apiBaseUrl,
17
+ config: options.config,
18
+ headless: options.headless
19
+ });
20
+ app.provide(FEEDVALUE_KEY, instance);
21
+ app.config.globalProperties.$feedvalue = instance;
22
+ }
23
+ app.provide(FEEDVALUE_OPTIONS_KEY, options);
24
+ }
25
+ };
26
+ }
27
+ function useFeedValue(widgetId, config) {
28
+ const injectedInstance = vue.inject(FEEDVALUE_KEY, null);
29
+ const injectedOptions = vue.inject(FEEDVALUE_OPTIONS_KEY, null);
30
+ const instance = vue.shallowRef(null);
31
+ const isReady = vue.ref(false);
32
+ const isOpen = vue.ref(false);
33
+ const isVisible = vue.ref(false);
34
+ const error = vue.ref(null);
35
+ const isSubmitting = vue.ref(false);
36
+ const isHeadless = vue.ref(false);
37
+ let ownsInstance = false;
38
+ let unsubscribe = null;
39
+ const syncState = () => {
40
+ const state = instance.value?.getSnapshot();
41
+ if (state) {
42
+ isReady.value = state.isReady;
43
+ isOpen.value = state.isOpen;
44
+ isVisible.value = state.isVisible;
45
+ error.value = state.error;
46
+ isSubmitting.value = state.isSubmitting;
47
+ }
48
+ };
49
+ vue.onMounted(() => {
50
+ if (injectedInstance && !widgetId) {
51
+ instance.value = injectedInstance;
52
+ isHeadless.value = injectedInstance.isHeadless();
53
+ } else {
54
+ const effectiveWidgetId = widgetId ?? injectedOptions?.widgetId;
55
+ if (!effectiveWidgetId) {
56
+ console.error(
57
+ "[FeedValue] No widgetId provided. Either install the plugin with createFeedValue() or pass widgetId to useFeedValue()."
58
+ );
59
+ return;
60
+ }
61
+ instance.value = core.FeedValue.init({
62
+ widgetId: effectiveWidgetId,
63
+ apiBaseUrl: injectedOptions?.apiBaseUrl,
64
+ config: config ?? injectedOptions?.config,
65
+ headless: injectedOptions?.headless
66
+ });
67
+ ownsInstance = true;
68
+ }
69
+ if (instance.value) {
70
+ unsubscribe = instance.value.subscribe(syncState);
71
+ isHeadless.value = instance.value.isHeadless();
72
+ syncState();
73
+ }
74
+ });
75
+ vue.onUnmounted(() => {
76
+ unsubscribe?.();
77
+ if (ownsInstance && instance.value) {
78
+ instance.value.destroy();
79
+ }
80
+ instance.value = null;
81
+ });
82
+ const open = () => instance.value?.open();
83
+ const close = () => instance.value?.close();
84
+ const toggle = () => instance.value?.toggle();
85
+ const show = () => instance.value?.show();
86
+ const hide = () => instance.value?.hide();
87
+ const submit = (feedback) => instance.value?.submit(feedback) ?? Promise.reject(new Error("Not initialized"));
88
+ const identify = (userId, traits) => instance.value?.identify(userId, traits);
89
+ const setData = (data) => instance.value?.setData(data);
90
+ const reset = () => instance.value?.reset();
91
+ return {
92
+ instance: vue.readonly(instance),
93
+ isReady: vue.readonly(isReady),
94
+ isOpen: vue.readonly(isOpen),
95
+ isVisible: vue.readonly(isVisible),
96
+ error: vue.readonly(error),
97
+ isSubmitting: vue.readonly(isSubmitting),
98
+ isHeadless: vue.readonly(isHeadless),
99
+ open,
100
+ close,
101
+ toggle,
102
+ show,
103
+ hide,
104
+ submit,
105
+ identify,
106
+ setData,
107
+ reset
108
+ };
109
+ }
110
+
111
+ exports.FEEDVALUE_KEY = FEEDVALUE_KEY;
112
+ exports.FEEDVALUE_OPTIONS_KEY = FEEDVALUE_OPTIONS_KEY;
113
+ exports.createFeedValue = createFeedValue;
114
+ exports.useFeedValue = useFeedValue;
115
+ //# sourceMappingURL=index.cjs.map
116
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts","../src/composables.ts"],"names":["FeedValue","inject","shallowRef","ref","onMounted","onUnmounted","readonly"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @feedvalue/vue - Plugin\n *\n * Vue plugin for FeedValue. Provides app-level configuration\n * and automatic initialization.\n */\n\nimport type { App, InjectionKey } from 'vue';\nimport { FeedValue, type FeedValueConfig, type FeedValueInstance } from '@feedvalue/core';\n\n/**\n * Plugin options\n */\nexport interface FeedValuePluginOptions {\n /** Widget ID from FeedValue dashboard */\n widgetId: string;\n /** API base URL (for self-hosted) */\n apiBaseUrl?: string;\n /** Configuration overrides */\n config?: Partial<FeedValueConfig>;\n /**\n * Headless mode - disables all DOM rendering.\n * Use this when you want full control over the UI.\n * The SDK will still fetch config and provide all API methods\n * but won't render any trigger button or modal.\n *\n * @default false\n */\n headless?: boolean;\n}\n\n/**\n * Injection key for FeedValue instance\n */\nexport const FEEDVALUE_KEY: InjectionKey<FeedValueInstance> = Symbol('feedvalue');\n\n/**\n * Injection key for widget ID (used by useFeedValue when no instance is injected)\n */\nexport const FEEDVALUE_OPTIONS_KEY: InjectionKey<FeedValuePluginOptions> = Symbol('feedvalue-options');\n\n/**\n * Create FeedValue Vue plugin\n *\n * @example\n * ```ts\n * // main.ts\n * import { createApp } from 'vue';\n * import { createFeedValue } from '@feedvalue/vue';\n * import App from './App.vue';\n *\n * const app = createApp(App);\n *\n * app.use(createFeedValue({\n * widgetId: 'your-widget-id',\n * config: { theme: 'dark' }\n * }));\n *\n * app.mount('#app');\n * ```\n */\nexport function createFeedValue(options: FeedValuePluginOptions) {\n let instance: FeedValueInstance | null = null;\n\n return {\n install(app: App) {\n // Only initialize on client side\n if (typeof window !== 'undefined') {\n instance = FeedValue.init({\n widgetId: options.widgetId,\n apiBaseUrl: options.apiBaseUrl,\n config: options.config,\n headless: options.headless,\n });\n\n // Provide instance to all components\n app.provide(FEEDVALUE_KEY, instance);\n\n // Also provide to global properties for Options API access\n app.config.globalProperties.$feedvalue = instance;\n }\n\n // Always provide options (for SSR where we don't initialize)\n app.provide(FEEDVALUE_OPTIONS_KEY, options);\n },\n };\n}\n\n/**\n * Type augmentation for global properties\n */\ndeclare module 'vue' {\n interface ComponentCustomProperties {\n $feedvalue: FeedValueInstance | undefined;\n }\n}\n","/**\n * @feedvalue/vue - Composables\n *\n * Vue composables for FeedValue. Provides reactive state and methods.\n */\n\nimport {\n ref,\n shallowRef,\n readonly,\n onMounted,\n onUnmounted,\n inject,\n type Ref,\n type ShallowRef,\n} from 'vue';\nimport {\n FeedValue,\n type FeedValueConfig,\n type FeedValueInstance,\n type FeedbackData,\n type UserTraits,\n} from '@feedvalue/core';\nimport { FEEDVALUE_KEY, FEEDVALUE_OPTIONS_KEY } from './plugin';\n\n/**\n * Return type for useFeedValue composable\n */\nexport interface UseFeedValueReturn {\n /** FeedValue instance (for advanced usage) */\n instance: Readonly<ShallowRef<FeedValueInstance | null>>;\n /** Widget is ready */\n isReady: Readonly<Ref<boolean>>;\n /** Modal is open */\n isOpen: Readonly<Ref<boolean>>;\n /** Widget is visible */\n isVisible: Readonly<Ref<boolean>>;\n /** Current error */\n error: Readonly<Ref<Error | null>>;\n /** Submission in progress */\n isSubmitting: Readonly<Ref<boolean>>;\n /** Running in headless mode (no default UI rendered) */\n isHeadless: Readonly<Ref<boolean>>;\n /** Open the feedback modal */\n open: () => void;\n /** Close the feedback modal */\n close: () => void;\n /** Toggle the feedback modal */\n toggle: () => void;\n /** Show the trigger button */\n show: () => void;\n /** Hide the trigger button */\n hide: () => void;\n /** Submit feedback programmatically */\n submit: (feedback: Partial<FeedbackData>) => Promise<void>;\n /** Identify user */\n identify: (userId: string, traits?: UserTraits) => void;\n /** Set user data */\n setData: (data: Record<string, string>) => void;\n /** Reset user data */\n reset: () => void;\n}\n\n/**\n * FeedValue composable\n *\n * Can be used with or without plugin. If plugin is installed, uses the\n * provided instance. Otherwise, creates a new instance.\n *\n * @example\n * ```vue\n * <script setup>\n * import { useFeedValue } from '@feedvalue/vue';\n *\n * // With plugin installed\n * const { open, isReady } = useFeedValue();\n *\n * // Or standalone with widgetId\n * const { open, isReady } = useFeedValue('your-widget-id');\n * </script>\n *\n * <template>\n * <button @click=\"open\" :disabled=\"!isReady\">\n * Give Feedback\n * </button>\n * </template>\n * ```\n */\nexport function useFeedValue(\n widgetId?: string,\n config?: Partial<FeedValueConfig>\n): UseFeedValueReturn {\n // Try to inject instance from plugin\n const injectedInstance = inject(FEEDVALUE_KEY, null);\n const injectedOptions = inject(FEEDVALUE_OPTIONS_KEY, null);\n\n // Refs for reactive state\n const instance = shallowRef<FeedValueInstance | null>(null);\n const isReady = ref(false);\n const isOpen = ref(false);\n const isVisible = ref(false);\n const error = ref<Error | null>(null);\n const isSubmitting = ref(false);\n const isHeadless = ref(false);\n\n // Track if we own the instance (need to destroy on unmount)\n let ownsInstance = false;\n let unsubscribe: (() => void) | null = null;\n\n /**\n * Sync reactive state from instance\n */\n const syncState = () => {\n const state = instance.value?.getSnapshot();\n if (state) {\n isReady.value = state.isReady;\n isOpen.value = state.isOpen;\n isVisible.value = state.isVisible;\n error.value = state.error;\n isSubmitting.value = state.isSubmitting;\n }\n };\n\n onMounted(() => {\n // Use injected instance if available and no widgetId override\n if (injectedInstance && !widgetId) {\n instance.value = injectedInstance;\n isHeadless.value = injectedInstance.isHeadless();\n } else {\n // Create new instance\n const effectiveWidgetId = widgetId ?? injectedOptions?.widgetId;\n\n if (!effectiveWidgetId) {\n console.error(\n '[FeedValue] No widgetId provided. Either install the plugin with createFeedValue() ' +\n 'or pass widgetId to useFeedValue().'\n );\n return;\n }\n\n instance.value = FeedValue.init({\n widgetId: effectiveWidgetId,\n apiBaseUrl: injectedOptions?.apiBaseUrl,\n config: config ?? injectedOptions?.config,\n headless: injectedOptions?.headless,\n });\n ownsInstance = true;\n }\n\n // Subscribe to state changes\n if (instance.value) {\n unsubscribe = instance.value.subscribe(syncState);\n isHeadless.value = instance.value.isHeadless();\n syncState(); // Initial sync\n }\n });\n\n onUnmounted(() => {\n unsubscribe?.();\n if (ownsInstance && instance.value) {\n instance.value.destroy();\n }\n instance.value = null;\n });\n\n // Methods that delegate to instance\n const open = () => instance.value?.open();\n const close = () => instance.value?.close();\n const toggle = () => instance.value?.toggle();\n const show = () => instance.value?.show();\n const hide = () => instance.value?.hide();\n const submit = (feedback: Partial<FeedbackData>) =>\n instance.value?.submit(feedback) ?? Promise.reject(new Error('Not initialized'));\n const identify = (userId: string, traits?: UserTraits) =>\n instance.value?.identify(userId, traits);\n const setData = (data: Record<string, string>) => instance.value?.setData(data);\n const reset = () => instance.value?.reset();\n\n return {\n instance: readonly(instance),\n isReady: readonly(isReady),\n isOpen: readonly(isOpen),\n isVisible: readonly(isVisible),\n error: readonly(error),\n isSubmitting: readonly(isSubmitting),\n isHeadless: readonly(isHeadless),\n open,\n close,\n toggle,\n show,\n hide,\n submit,\n identify,\n setData,\n reset,\n };\n}\n"]}
@@ -0,0 +1,142 @@
1
+ import { InjectionKey, App, ShallowRef, Ref } from 'vue';
2
+ import { FeedValueInstance, FeedValueConfig, FeedbackData, UserTraits } from '@feedvalue/core';
3
+ export { FeedValueConfig, FeedValueEvents, FeedValueState, FeedbackData, FeedbackMetadata, UserData, UserTraits } from '@feedvalue/core';
4
+
5
+ /**
6
+ * @feedvalue/vue - Plugin
7
+ *
8
+ * Vue plugin for FeedValue. Provides app-level configuration
9
+ * and automatic initialization.
10
+ */
11
+
12
+ /**
13
+ * Plugin options
14
+ */
15
+ interface FeedValuePluginOptions {
16
+ /** Widget ID from FeedValue dashboard */
17
+ widgetId: string;
18
+ /** API base URL (for self-hosted) */
19
+ apiBaseUrl?: string;
20
+ /** Configuration overrides */
21
+ config?: Partial<FeedValueConfig>;
22
+ /**
23
+ * Headless mode - disables all DOM rendering.
24
+ * Use this when you want full control over the UI.
25
+ * The SDK will still fetch config and provide all API methods
26
+ * but won't render any trigger button or modal.
27
+ *
28
+ * @default false
29
+ */
30
+ headless?: boolean;
31
+ }
32
+ /**
33
+ * Injection key for FeedValue instance
34
+ */
35
+ declare const FEEDVALUE_KEY: InjectionKey<FeedValueInstance>;
36
+ /**
37
+ * Injection key for widget ID (used by useFeedValue when no instance is injected)
38
+ */
39
+ declare const FEEDVALUE_OPTIONS_KEY: InjectionKey<FeedValuePluginOptions>;
40
+ /**
41
+ * Create FeedValue Vue plugin
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * // main.ts
46
+ * import { createApp } from 'vue';
47
+ * import { createFeedValue } from '@feedvalue/vue';
48
+ * import App from './App.vue';
49
+ *
50
+ * const app = createApp(App);
51
+ *
52
+ * app.use(createFeedValue({
53
+ * widgetId: 'your-widget-id',
54
+ * config: { theme: 'dark' }
55
+ * }));
56
+ *
57
+ * app.mount('#app');
58
+ * ```
59
+ */
60
+ declare function createFeedValue(options: FeedValuePluginOptions): {
61
+ install(app: App): void;
62
+ };
63
+ /**
64
+ * Type augmentation for global properties
65
+ */
66
+ declare module 'vue' {
67
+ interface ComponentCustomProperties {
68
+ $feedvalue: FeedValueInstance | undefined;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * @feedvalue/vue - Composables
74
+ *
75
+ * Vue composables for FeedValue. Provides reactive state and methods.
76
+ */
77
+
78
+ /**
79
+ * Return type for useFeedValue composable
80
+ */
81
+ interface UseFeedValueReturn {
82
+ /** FeedValue instance (for advanced usage) */
83
+ instance: Readonly<ShallowRef<FeedValueInstance | null>>;
84
+ /** Widget is ready */
85
+ isReady: Readonly<Ref<boolean>>;
86
+ /** Modal is open */
87
+ isOpen: Readonly<Ref<boolean>>;
88
+ /** Widget is visible */
89
+ isVisible: Readonly<Ref<boolean>>;
90
+ /** Current error */
91
+ error: Readonly<Ref<Error | null>>;
92
+ /** Submission in progress */
93
+ isSubmitting: Readonly<Ref<boolean>>;
94
+ /** Running in headless mode (no default UI rendered) */
95
+ isHeadless: Readonly<Ref<boolean>>;
96
+ /** Open the feedback modal */
97
+ open: () => void;
98
+ /** Close the feedback modal */
99
+ close: () => void;
100
+ /** Toggle the feedback modal */
101
+ toggle: () => void;
102
+ /** Show the trigger button */
103
+ show: () => void;
104
+ /** Hide the trigger button */
105
+ hide: () => void;
106
+ /** Submit feedback programmatically */
107
+ submit: (feedback: Partial<FeedbackData>) => Promise<void>;
108
+ /** Identify user */
109
+ identify: (userId: string, traits?: UserTraits) => void;
110
+ /** Set user data */
111
+ setData: (data: Record<string, string>) => void;
112
+ /** Reset user data */
113
+ reset: () => void;
114
+ }
115
+ /**
116
+ * FeedValue composable
117
+ *
118
+ * Can be used with or without plugin. If plugin is installed, uses the
119
+ * provided instance. Otherwise, creates a new instance.
120
+ *
121
+ * @example
122
+ * ```vue
123
+ * <script setup>
124
+ * import { useFeedValue } from '@feedvalue/vue';
125
+ *
126
+ * // With plugin installed
127
+ * const { open, isReady } = useFeedValue();
128
+ *
129
+ * // Or standalone with widgetId
130
+ * const { open, isReady } = useFeedValue('your-widget-id');
131
+ * </script>
132
+ *
133
+ * <template>
134
+ * <button @click="open" :disabled="!isReady">
135
+ * Give Feedback
136
+ * </button>
137
+ * </template>
138
+ * ```
139
+ */
140
+ declare function useFeedValue(widgetId?: string, config?: Partial<FeedValueConfig>): UseFeedValueReturn;
141
+
142
+ export { FEEDVALUE_KEY, FEEDVALUE_OPTIONS_KEY, type FeedValuePluginOptions, type UseFeedValueReturn, createFeedValue, useFeedValue };
@@ -0,0 +1,142 @@
1
+ import { InjectionKey, App, ShallowRef, Ref } from 'vue';
2
+ import { FeedValueInstance, FeedValueConfig, FeedbackData, UserTraits } from '@feedvalue/core';
3
+ export { FeedValueConfig, FeedValueEvents, FeedValueState, FeedbackData, FeedbackMetadata, UserData, UserTraits } from '@feedvalue/core';
4
+
5
+ /**
6
+ * @feedvalue/vue - Plugin
7
+ *
8
+ * Vue plugin for FeedValue. Provides app-level configuration
9
+ * and automatic initialization.
10
+ */
11
+
12
+ /**
13
+ * Plugin options
14
+ */
15
+ interface FeedValuePluginOptions {
16
+ /** Widget ID from FeedValue dashboard */
17
+ widgetId: string;
18
+ /** API base URL (for self-hosted) */
19
+ apiBaseUrl?: string;
20
+ /** Configuration overrides */
21
+ config?: Partial<FeedValueConfig>;
22
+ /**
23
+ * Headless mode - disables all DOM rendering.
24
+ * Use this when you want full control over the UI.
25
+ * The SDK will still fetch config and provide all API methods
26
+ * but won't render any trigger button or modal.
27
+ *
28
+ * @default false
29
+ */
30
+ headless?: boolean;
31
+ }
32
+ /**
33
+ * Injection key for FeedValue instance
34
+ */
35
+ declare const FEEDVALUE_KEY: InjectionKey<FeedValueInstance>;
36
+ /**
37
+ * Injection key for widget ID (used by useFeedValue when no instance is injected)
38
+ */
39
+ declare const FEEDVALUE_OPTIONS_KEY: InjectionKey<FeedValuePluginOptions>;
40
+ /**
41
+ * Create FeedValue Vue plugin
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * // main.ts
46
+ * import { createApp } from 'vue';
47
+ * import { createFeedValue } from '@feedvalue/vue';
48
+ * import App from './App.vue';
49
+ *
50
+ * const app = createApp(App);
51
+ *
52
+ * app.use(createFeedValue({
53
+ * widgetId: 'your-widget-id',
54
+ * config: { theme: 'dark' }
55
+ * }));
56
+ *
57
+ * app.mount('#app');
58
+ * ```
59
+ */
60
+ declare function createFeedValue(options: FeedValuePluginOptions): {
61
+ install(app: App): void;
62
+ };
63
+ /**
64
+ * Type augmentation for global properties
65
+ */
66
+ declare module 'vue' {
67
+ interface ComponentCustomProperties {
68
+ $feedvalue: FeedValueInstance | undefined;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * @feedvalue/vue - Composables
74
+ *
75
+ * Vue composables for FeedValue. Provides reactive state and methods.
76
+ */
77
+
78
+ /**
79
+ * Return type for useFeedValue composable
80
+ */
81
+ interface UseFeedValueReturn {
82
+ /** FeedValue instance (for advanced usage) */
83
+ instance: Readonly<ShallowRef<FeedValueInstance | null>>;
84
+ /** Widget is ready */
85
+ isReady: Readonly<Ref<boolean>>;
86
+ /** Modal is open */
87
+ isOpen: Readonly<Ref<boolean>>;
88
+ /** Widget is visible */
89
+ isVisible: Readonly<Ref<boolean>>;
90
+ /** Current error */
91
+ error: Readonly<Ref<Error | null>>;
92
+ /** Submission in progress */
93
+ isSubmitting: Readonly<Ref<boolean>>;
94
+ /** Running in headless mode (no default UI rendered) */
95
+ isHeadless: Readonly<Ref<boolean>>;
96
+ /** Open the feedback modal */
97
+ open: () => void;
98
+ /** Close the feedback modal */
99
+ close: () => void;
100
+ /** Toggle the feedback modal */
101
+ toggle: () => void;
102
+ /** Show the trigger button */
103
+ show: () => void;
104
+ /** Hide the trigger button */
105
+ hide: () => void;
106
+ /** Submit feedback programmatically */
107
+ submit: (feedback: Partial<FeedbackData>) => Promise<void>;
108
+ /** Identify user */
109
+ identify: (userId: string, traits?: UserTraits) => void;
110
+ /** Set user data */
111
+ setData: (data: Record<string, string>) => void;
112
+ /** Reset user data */
113
+ reset: () => void;
114
+ }
115
+ /**
116
+ * FeedValue composable
117
+ *
118
+ * Can be used with or without plugin. If plugin is installed, uses the
119
+ * provided instance. Otherwise, creates a new instance.
120
+ *
121
+ * @example
122
+ * ```vue
123
+ * <script setup>
124
+ * import { useFeedValue } from '@feedvalue/vue';
125
+ *
126
+ * // With plugin installed
127
+ * const { open, isReady } = useFeedValue();
128
+ *
129
+ * // Or standalone with widgetId
130
+ * const { open, isReady } = useFeedValue('your-widget-id');
131
+ * </script>
132
+ *
133
+ * <template>
134
+ * <button @click="open" :disabled="!isReady">
135
+ * Give Feedback
136
+ * </button>
137
+ * </template>
138
+ * ```
139
+ */
140
+ declare function useFeedValue(widgetId?: string, config?: Partial<FeedValueConfig>): UseFeedValueReturn;
141
+
142
+ export { FEEDVALUE_KEY, FEEDVALUE_OPTIONS_KEY, type FeedValuePluginOptions, type UseFeedValueReturn, createFeedValue, useFeedValue };
package/dist/index.js ADDED
@@ -0,0 +1,111 @@
1
+ import { FeedValue } from '@feedvalue/core';
2
+ import { inject, shallowRef, ref, onMounted, onUnmounted, readonly } from 'vue';
3
+
4
+ // src/plugin.ts
5
+ var FEEDVALUE_KEY = /* @__PURE__ */ Symbol("feedvalue");
6
+ var FEEDVALUE_OPTIONS_KEY = /* @__PURE__ */ Symbol("feedvalue-options");
7
+ function createFeedValue(options) {
8
+ let instance = null;
9
+ return {
10
+ install(app) {
11
+ if (typeof window !== "undefined") {
12
+ instance = FeedValue.init({
13
+ widgetId: options.widgetId,
14
+ apiBaseUrl: options.apiBaseUrl,
15
+ config: options.config,
16
+ headless: options.headless
17
+ });
18
+ app.provide(FEEDVALUE_KEY, instance);
19
+ app.config.globalProperties.$feedvalue = instance;
20
+ }
21
+ app.provide(FEEDVALUE_OPTIONS_KEY, options);
22
+ }
23
+ };
24
+ }
25
+ function useFeedValue(widgetId, config) {
26
+ const injectedInstance = inject(FEEDVALUE_KEY, null);
27
+ const injectedOptions = inject(FEEDVALUE_OPTIONS_KEY, null);
28
+ const instance = shallowRef(null);
29
+ const isReady = ref(false);
30
+ const isOpen = ref(false);
31
+ const isVisible = ref(false);
32
+ const error = ref(null);
33
+ const isSubmitting = ref(false);
34
+ const isHeadless = ref(false);
35
+ let ownsInstance = false;
36
+ let unsubscribe = null;
37
+ const syncState = () => {
38
+ const state = instance.value?.getSnapshot();
39
+ if (state) {
40
+ isReady.value = state.isReady;
41
+ isOpen.value = state.isOpen;
42
+ isVisible.value = state.isVisible;
43
+ error.value = state.error;
44
+ isSubmitting.value = state.isSubmitting;
45
+ }
46
+ };
47
+ onMounted(() => {
48
+ if (injectedInstance && !widgetId) {
49
+ instance.value = injectedInstance;
50
+ isHeadless.value = injectedInstance.isHeadless();
51
+ } else {
52
+ const effectiveWidgetId = widgetId ?? injectedOptions?.widgetId;
53
+ if (!effectiveWidgetId) {
54
+ console.error(
55
+ "[FeedValue] No widgetId provided. Either install the plugin with createFeedValue() or pass widgetId to useFeedValue()."
56
+ );
57
+ return;
58
+ }
59
+ instance.value = FeedValue.init({
60
+ widgetId: effectiveWidgetId,
61
+ apiBaseUrl: injectedOptions?.apiBaseUrl,
62
+ config: config ?? injectedOptions?.config,
63
+ headless: injectedOptions?.headless
64
+ });
65
+ ownsInstance = true;
66
+ }
67
+ if (instance.value) {
68
+ unsubscribe = instance.value.subscribe(syncState);
69
+ isHeadless.value = instance.value.isHeadless();
70
+ syncState();
71
+ }
72
+ });
73
+ onUnmounted(() => {
74
+ unsubscribe?.();
75
+ if (ownsInstance && instance.value) {
76
+ instance.value.destroy();
77
+ }
78
+ instance.value = null;
79
+ });
80
+ const open = () => instance.value?.open();
81
+ const close = () => instance.value?.close();
82
+ const toggle = () => instance.value?.toggle();
83
+ const show = () => instance.value?.show();
84
+ const hide = () => instance.value?.hide();
85
+ const submit = (feedback) => instance.value?.submit(feedback) ?? Promise.reject(new Error("Not initialized"));
86
+ const identify = (userId, traits) => instance.value?.identify(userId, traits);
87
+ const setData = (data) => instance.value?.setData(data);
88
+ const reset = () => instance.value?.reset();
89
+ return {
90
+ instance: readonly(instance),
91
+ isReady: readonly(isReady),
92
+ isOpen: readonly(isOpen),
93
+ isVisible: readonly(isVisible),
94
+ error: readonly(error),
95
+ isSubmitting: readonly(isSubmitting),
96
+ isHeadless: readonly(isHeadless),
97
+ open,
98
+ close,
99
+ toggle,
100
+ show,
101
+ hide,
102
+ submit,
103
+ identify,
104
+ setData,
105
+ reset
106
+ };
107
+ }
108
+
109
+ export { FEEDVALUE_KEY, FEEDVALUE_OPTIONS_KEY, createFeedValue, useFeedValue };
110
+ //# sourceMappingURL=index.js.map
111
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts","../src/composables.ts"],"names":["FeedValue"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @feedvalue/vue - Plugin\n *\n * Vue plugin for FeedValue. Provides app-level configuration\n * and automatic initialization.\n */\n\nimport type { App, InjectionKey } from 'vue';\nimport { FeedValue, type FeedValueConfig, type FeedValueInstance } from '@feedvalue/core';\n\n/**\n * Plugin options\n */\nexport interface FeedValuePluginOptions {\n /** Widget ID from FeedValue dashboard */\n widgetId: string;\n /** API base URL (for self-hosted) */\n apiBaseUrl?: string;\n /** Configuration overrides */\n config?: Partial<FeedValueConfig>;\n /**\n * Headless mode - disables all DOM rendering.\n * Use this when you want full control over the UI.\n * The SDK will still fetch config and provide all API methods\n * but won't render any trigger button or modal.\n *\n * @default false\n */\n headless?: boolean;\n}\n\n/**\n * Injection key for FeedValue instance\n */\nexport const FEEDVALUE_KEY: InjectionKey<FeedValueInstance> = Symbol('feedvalue');\n\n/**\n * Injection key for widget ID (used by useFeedValue when no instance is injected)\n */\nexport const FEEDVALUE_OPTIONS_KEY: InjectionKey<FeedValuePluginOptions> = Symbol('feedvalue-options');\n\n/**\n * Create FeedValue Vue plugin\n *\n * @example\n * ```ts\n * // main.ts\n * import { createApp } from 'vue';\n * import { createFeedValue } from '@feedvalue/vue';\n * import App from './App.vue';\n *\n * const app = createApp(App);\n *\n * app.use(createFeedValue({\n * widgetId: 'your-widget-id',\n * config: { theme: 'dark' }\n * }));\n *\n * app.mount('#app');\n * ```\n */\nexport function createFeedValue(options: FeedValuePluginOptions) {\n let instance: FeedValueInstance | null = null;\n\n return {\n install(app: App) {\n // Only initialize on client side\n if (typeof window !== 'undefined') {\n instance = FeedValue.init({\n widgetId: options.widgetId,\n apiBaseUrl: options.apiBaseUrl,\n config: options.config,\n headless: options.headless,\n });\n\n // Provide instance to all components\n app.provide(FEEDVALUE_KEY, instance);\n\n // Also provide to global properties for Options API access\n app.config.globalProperties.$feedvalue = instance;\n }\n\n // Always provide options (for SSR where we don't initialize)\n app.provide(FEEDVALUE_OPTIONS_KEY, options);\n },\n };\n}\n\n/**\n * Type augmentation for global properties\n */\ndeclare module 'vue' {\n interface ComponentCustomProperties {\n $feedvalue: FeedValueInstance | undefined;\n }\n}\n","/**\n * @feedvalue/vue - Composables\n *\n * Vue composables for FeedValue. Provides reactive state and methods.\n */\n\nimport {\n ref,\n shallowRef,\n readonly,\n onMounted,\n onUnmounted,\n inject,\n type Ref,\n type ShallowRef,\n} from 'vue';\nimport {\n FeedValue,\n type FeedValueConfig,\n type FeedValueInstance,\n type FeedbackData,\n type UserTraits,\n} from '@feedvalue/core';\nimport { FEEDVALUE_KEY, FEEDVALUE_OPTIONS_KEY } from './plugin';\n\n/**\n * Return type for useFeedValue composable\n */\nexport interface UseFeedValueReturn {\n /** FeedValue instance (for advanced usage) */\n instance: Readonly<ShallowRef<FeedValueInstance | null>>;\n /** Widget is ready */\n isReady: Readonly<Ref<boolean>>;\n /** Modal is open */\n isOpen: Readonly<Ref<boolean>>;\n /** Widget is visible */\n isVisible: Readonly<Ref<boolean>>;\n /** Current error */\n error: Readonly<Ref<Error | null>>;\n /** Submission in progress */\n isSubmitting: Readonly<Ref<boolean>>;\n /** Running in headless mode (no default UI rendered) */\n isHeadless: Readonly<Ref<boolean>>;\n /** Open the feedback modal */\n open: () => void;\n /** Close the feedback modal */\n close: () => void;\n /** Toggle the feedback modal */\n toggle: () => void;\n /** Show the trigger button */\n show: () => void;\n /** Hide the trigger button */\n hide: () => void;\n /** Submit feedback programmatically */\n submit: (feedback: Partial<FeedbackData>) => Promise<void>;\n /** Identify user */\n identify: (userId: string, traits?: UserTraits) => void;\n /** Set user data */\n setData: (data: Record<string, string>) => void;\n /** Reset user data */\n reset: () => void;\n}\n\n/**\n * FeedValue composable\n *\n * Can be used with or without plugin. If plugin is installed, uses the\n * provided instance. Otherwise, creates a new instance.\n *\n * @example\n * ```vue\n * <script setup>\n * import { useFeedValue } from '@feedvalue/vue';\n *\n * // With plugin installed\n * const { open, isReady } = useFeedValue();\n *\n * // Or standalone with widgetId\n * const { open, isReady } = useFeedValue('your-widget-id');\n * </script>\n *\n * <template>\n * <button @click=\"open\" :disabled=\"!isReady\">\n * Give Feedback\n * </button>\n * </template>\n * ```\n */\nexport function useFeedValue(\n widgetId?: string,\n config?: Partial<FeedValueConfig>\n): UseFeedValueReturn {\n // Try to inject instance from plugin\n const injectedInstance = inject(FEEDVALUE_KEY, null);\n const injectedOptions = inject(FEEDVALUE_OPTIONS_KEY, null);\n\n // Refs for reactive state\n const instance = shallowRef<FeedValueInstance | null>(null);\n const isReady = ref(false);\n const isOpen = ref(false);\n const isVisible = ref(false);\n const error = ref<Error | null>(null);\n const isSubmitting = ref(false);\n const isHeadless = ref(false);\n\n // Track if we own the instance (need to destroy on unmount)\n let ownsInstance = false;\n let unsubscribe: (() => void) | null = null;\n\n /**\n * Sync reactive state from instance\n */\n const syncState = () => {\n const state = instance.value?.getSnapshot();\n if (state) {\n isReady.value = state.isReady;\n isOpen.value = state.isOpen;\n isVisible.value = state.isVisible;\n error.value = state.error;\n isSubmitting.value = state.isSubmitting;\n }\n };\n\n onMounted(() => {\n // Use injected instance if available and no widgetId override\n if (injectedInstance && !widgetId) {\n instance.value = injectedInstance;\n isHeadless.value = injectedInstance.isHeadless();\n } else {\n // Create new instance\n const effectiveWidgetId = widgetId ?? injectedOptions?.widgetId;\n\n if (!effectiveWidgetId) {\n console.error(\n '[FeedValue] No widgetId provided. Either install the plugin with createFeedValue() ' +\n 'or pass widgetId to useFeedValue().'\n );\n return;\n }\n\n instance.value = FeedValue.init({\n widgetId: effectiveWidgetId,\n apiBaseUrl: injectedOptions?.apiBaseUrl,\n config: config ?? injectedOptions?.config,\n headless: injectedOptions?.headless,\n });\n ownsInstance = true;\n }\n\n // Subscribe to state changes\n if (instance.value) {\n unsubscribe = instance.value.subscribe(syncState);\n isHeadless.value = instance.value.isHeadless();\n syncState(); // Initial sync\n }\n });\n\n onUnmounted(() => {\n unsubscribe?.();\n if (ownsInstance && instance.value) {\n instance.value.destroy();\n }\n instance.value = null;\n });\n\n // Methods that delegate to instance\n const open = () => instance.value?.open();\n const close = () => instance.value?.close();\n const toggle = () => instance.value?.toggle();\n const show = () => instance.value?.show();\n const hide = () => instance.value?.hide();\n const submit = (feedback: Partial<FeedbackData>) =>\n instance.value?.submit(feedback) ?? Promise.reject(new Error('Not initialized'));\n const identify = (userId: string, traits?: UserTraits) =>\n instance.value?.identify(userId, traits);\n const setData = (data: Record<string, string>) => instance.value?.setData(data);\n const reset = () => instance.value?.reset();\n\n return {\n instance: readonly(instance),\n isReady: readonly(isReady),\n isOpen: readonly(isOpen),\n isVisible: readonly(isVisible),\n error: readonly(error),\n isSubmitting: readonly(isSubmitting),\n isHeadless: readonly(isHeadless),\n open,\n close,\n toggle,\n show,\n hide,\n submit,\n identify,\n setData,\n reset,\n };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@feedvalue/vue",
3
+ "version": "0.1.0",
4
+ "description": "FeedValue Vue 3 SDK - Plugin and Composables for Vue 3+",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "sideEffects": false,
26
+ "peerDependencies": {
27
+ "vue": ">=3.3.0"
28
+ },
29
+ "dependencies": {
30
+ "@feedvalue/core": "^0.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "@vitejs/plugin-vue": "^5.2.0",
34
+ "@vitest/coverage-v8": "^2.1.0",
35
+ "@vue/test-utils": "^2.4.0",
36
+ "eslint": "^9.17.0",
37
+ "happy-dom": "^15.11.0",
38
+ "tsup": "^8.3.0",
39
+ "typescript": "^5.7.0",
40
+ "vitest": "^2.1.0",
41
+ "vue": "^3.5.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/sarverenterprises/feedvalue-packages.git",
49
+ "directory": "packages/vue"
50
+ },
51
+ "homepage": "https://feedvalue.com",
52
+ "bugs": {
53
+ "url": "https://github.com/sarverenterprises/feedvalue-packages/issues"
54
+ },
55
+ "license": "MIT",
56
+ "author": "Sarver Enterprises",
57
+ "keywords": [
58
+ "feedvalue",
59
+ "feedback",
60
+ "widget",
61
+ "vue",
62
+ "vue3",
63
+ "composable",
64
+ "nuxt"
65
+ ],
66
+ "engines": {
67
+ "node": ">=18"
68
+ },
69
+ "scripts": {
70
+ "build": "tsup",
71
+ "dev": "tsup --watch",
72
+ "lint": "eslint src --ext .ts",
73
+ "test": "vitest run",
74
+ "test:watch": "vitest",
75
+ "test:coverage": "vitest run --coverage",
76
+ "typecheck": "tsc --noEmit",
77
+ "clean": "rm -rf dist"
78
+ }
79
+ }