@framer-framer/vue 3.2.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,142 @@
1
+ # @framer-framer/vue
2
+
3
+ Vue 3 embed component for [framer-framer](https://github.com/piroz/framer-framer). Resolve any URL to embed HTML with a simple component or composable.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @framer-framer/vue framer-framer vue
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### `<Embed>` Component
14
+
15
+ ```vue
16
+ <script setup>
17
+ import { Embed } from '@framer-framer/vue';
18
+ </script>
19
+
20
+ <template>
21
+ <Embed
22
+ url="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
23
+ :max-width="640"
24
+ @load="(result) => console.log('Loaded:', result.provider)"
25
+ @error="(err) => console.error(err)"
26
+ />
27
+ </template>
28
+ ```
29
+
30
+ ### Custom Slots
31
+
32
+ ```vue
33
+ <template>
34
+ <Embed url="https://www.youtube.com/watch?v=dQw4w9WgXcQ">
35
+ <!-- Custom loading skeleton -->
36
+ <template #loading>
37
+ <div class="skeleton" />
38
+ </template>
39
+
40
+ <!-- Custom error display -->
41
+ <template #error="{ error }">
42
+ <div class="error">Failed: {{ error.message }}</div>
43
+ </template>
44
+
45
+ <!-- Custom embed rendering -->
46
+ <template #default="{ result }">
47
+ <div v-html="result.html" />
48
+ <p>{{ result.title }} — {{ result.provider }}</p>
49
+ </template>
50
+ </Embed>
51
+ </template>
52
+ ```
53
+
54
+ ### `useEmbed()` Composable
55
+
56
+ ```vue
57
+ <script setup>
58
+ import { useEmbed } from '@framer-framer/vue';
59
+
60
+ const { result, loading, error } = useEmbed(
61
+ 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
62
+ { maxWidth: 640 }
63
+ );
64
+ </script>
65
+
66
+ <template>
67
+ <div v-if="loading">Loading...</div>
68
+ <div v-else-if="error">Error: {{ error.message }}</div>
69
+ <div v-else-if="result" v-html="result.html" />
70
+ </template>
71
+ ```
72
+
73
+ The composable also accepts a reactive ref or getter as the URL:
74
+
75
+ ```vue
76
+ <script setup>
77
+ import { ref } from 'vue';
78
+ import { useEmbed } from '@framer-framer/vue';
79
+
80
+ const url = ref('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
81
+ const { result, loading, error } = useEmbed(url);
82
+ </script>
83
+ ```
84
+
85
+ ## Props
86
+
87
+ | Prop | Type | Required | Description |
88
+ |------|------|----------|-------------|
89
+ | `url` | `string` | Yes | URL to resolve |
90
+ | `maxWidth` | `number` | No | Max embed width |
91
+ | `maxHeight` | `number` | No | Max embed height |
92
+ | `options` | `EmbedOptions` | No | Options passed to framer-framer's `embed()` |
93
+
94
+ ## Events
95
+
96
+ | Event | Payload | Description |
97
+ |-------|---------|-------------|
98
+ | `@load` | `EmbedResult` | Emitted when the embed is resolved |
99
+ | `@error` | `Error` | Emitted when resolution fails |
100
+
101
+ ## Slots
102
+
103
+ | Slot | Props | Description |
104
+ |------|-------|-------------|
105
+ | `default` | `{ result: EmbedResult }` | Custom embed rendering |
106
+ | `loading` | — | Custom loading skeleton |
107
+ | `error` | `{ error: Error }` | Custom error display |
108
+
109
+ ## Nuxt Integration
110
+
111
+ `@framer-framer/vue` works with Nuxt 3 out of the box. Since `embed()` calls fetch external oEmbed APIs, use the component in client-only mode to avoid SSR issues:
112
+
113
+ ```vue
114
+ <template>
115
+ <ClientOnly>
116
+ <Embed url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" />
117
+ <template #fallback>
118
+ <div>Loading embed...</div>
119
+ </template>
120
+ </ClientOnly>
121
+ </template>
122
+ ```
123
+
124
+ For server-side resolution, use the `useEmbed()` composable inside `onMounted`:
125
+
126
+ ```vue
127
+ <script setup>
128
+ import { ref, onMounted } from 'vue';
129
+ import { embed } from 'framer-framer';
130
+
131
+ const html = ref('');
132
+
133
+ onMounted(async () => {
134
+ const result = await embed('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
135
+ html.value = result.html;
136
+ });
137
+ </script>
138
+ ```
139
+
140
+ ## License
141
+
142
+ MIT
@@ -0,0 +1,110 @@
1
+ import * as vue from 'vue';
2
+ import { PropType, SlotsType, Ref, MaybeRefOrGetter } from 'vue';
3
+ import { EmbedOptions, EmbedResult } from 'framer-framer';
4
+
5
+ /** Theme mode for Embed components */
6
+ type Theme = "light" | "dark" | "auto";
7
+
8
+ declare const Embed: vue.DefineComponent<vue.ExtractPropTypes<{
9
+ url: {
10
+ type: StringConstructor;
11
+ required: true;
12
+ };
13
+ maxWidth: {
14
+ type: NumberConstructor;
15
+ default: undefined;
16
+ };
17
+ maxHeight: {
18
+ type: NumberConstructor;
19
+ default: undefined;
20
+ };
21
+ options: {
22
+ type: PropType<EmbedOptions>;
23
+ default: undefined;
24
+ };
25
+ theme: {
26
+ type: PropType<Theme>;
27
+ default: string;
28
+ };
29
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
30
+ [key: string]: any;
31
+ }> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
32
+ load: (_result: EmbedResult) => true;
33
+ error: (_error: Error) => true;
34
+ }, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
35
+ url: {
36
+ type: StringConstructor;
37
+ required: true;
38
+ };
39
+ maxWidth: {
40
+ type: NumberConstructor;
41
+ default: undefined;
42
+ };
43
+ maxHeight: {
44
+ type: NumberConstructor;
45
+ default: undefined;
46
+ };
47
+ options: {
48
+ type: PropType<EmbedOptions>;
49
+ default: undefined;
50
+ };
51
+ theme: {
52
+ type: PropType<Theme>;
53
+ default: string;
54
+ };
55
+ }>> & Readonly<{
56
+ onLoad?: ((_result: EmbedResult) => any) | undefined;
57
+ onError?: ((_error: Error) => any) | undefined;
58
+ }>, {
59
+ maxWidth: number;
60
+ maxHeight: number;
61
+ options: EmbedOptions;
62
+ theme: Theme;
63
+ }, SlotsType<{
64
+ default?: {
65
+ result: EmbedResult;
66
+ };
67
+ loading?: Record<string, never>;
68
+ error?: {
69
+ error: Error;
70
+ };
71
+ }>, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
72
+
73
+ /** Props for the Embed component */
74
+ interface EmbedProps {
75
+ /** URL to resolve */
76
+ url: string;
77
+ /** Max embed width */
78
+ maxWidth?: number;
79
+ /** Max embed height */
80
+ maxHeight?: number;
81
+ /** Options passed to framer-framer's embed() */
82
+ options?: EmbedOptions;
83
+ /** Theme mode: 'light', 'dark', or 'auto' (default: 'auto') */
84
+ theme?: Theme;
85
+ }
86
+
87
+ interface UseEmbedOptions {
88
+ /** Max embed width */
89
+ maxWidth?: number;
90
+ /** Max embed height */
91
+ maxHeight?: number;
92
+ /** Additional options passed to framer-framer's embed() */
93
+ embedOptions?: EmbedOptions;
94
+ }
95
+ interface UseEmbedReturn {
96
+ result: Ref<EmbedResult | null>;
97
+ loading: Ref<boolean>;
98
+ error: Ref<Error | null>;
99
+ }
100
+ /**
101
+ * Composable that resolves a URL to embed data using framer-framer.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * const { result, loading, error } = useEmbed('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
106
+ * ```
107
+ */
108
+ declare function useEmbed(url: MaybeRefOrGetter<string>, options?: UseEmbedOptions): UseEmbedReturn;
109
+
110
+ export { Embed, type EmbedProps, type Theme, type UseEmbedOptions, type UseEmbedReturn, useEmbed };
@@ -0,0 +1,110 @@
1
+ import * as vue from 'vue';
2
+ import { PropType, SlotsType, Ref, MaybeRefOrGetter } from 'vue';
3
+ import { EmbedOptions, EmbedResult } from 'framer-framer';
4
+
5
+ /** Theme mode for Embed components */
6
+ type Theme = "light" | "dark" | "auto";
7
+
8
+ declare const Embed: vue.DefineComponent<vue.ExtractPropTypes<{
9
+ url: {
10
+ type: StringConstructor;
11
+ required: true;
12
+ };
13
+ maxWidth: {
14
+ type: NumberConstructor;
15
+ default: undefined;
16
+ };
17
+ maxHeight: {
18
+ type: NumberConstructor;
19
+ default: undefined;
20
+ };
21
+ options: {
22
+ type: PropType<EmbedOptions>;
23
+ default: undefined;
24
+ };
25
+ theme: {
26
+ type: PropType<Theme>;
27
+ default: string;
28
+ };
29
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
30
+ [key: string]: any;
31
+ }> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
32
+ load: (_result: EmbedResult) => true;
33
+ error: (_error: Error) => true;
34
+ }, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
35
+ url: {
36
+ type: StringConstructor;
37
+ required: true;
38
+ };
39
+ maxWidth: {
40
+ type: NumberConstructor;
41
+ default: undefined;
42
+ };
43
+ maxHeight: {
44
+ type: NumberConstructor;
45
+ default: undefined;
46
+ };
47
+ options: {
48
+ type: PropType<EmbedOptions>;
49
+ default: undefined;
50
+ };
51
+ theme: {
52
+ type: PropType<Theme>;
53
+ default: string;
54
+ };
55
+ }>> & Readonly<{
56
+ onLoad?: ((_result: EmbedResult) => any) | undefined;
57
+ onError?: ((_error: Error) => any) | undefined;
58
+ }>, {
59
+ maxWidth: number;
60
+ maxHeight: number;
61
+ options: EmbedOptions;
62
+ theme: Theme;
63
+ }, SlotsType<{
64
+ default?: {
65
+ result: EmbedResult;
66
+ };
67
+ loading?: Record<string, never>;
68
+ error?: {
69
+ error: Error;
70
+ };
71
+ }>, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
72
+
73
+ /** Props for the Embed component */
74
+ interface EmbedProps {
75
+ /** URL to resolve */
76
+ url: string;
77
+ /** Max embed width */
78
+ maxWidth?: number;
79
+ /** Max embed height */
80
+ maxHeight?: number;
81
+ /** Options passed to framer-framer's embed() */
82
+ options?: EmbedOptions;
83
+ /** Theme mode: 'light', 'dark', or 'auto' (default: 'auto') */
84
+ theme?: Theme;
85
+ }
86
+
87
+ interface UseEmbedOptions {
88
+ /** Max embed width */
89
+ maxWidth?: number;
90
+ /** Max embed height */
91
+ maxHeight?: number;
92
+ /** Additional options passed to framer-framer's embed() */
93
+ embedOptions?: EmbedOptions;
94
+ }
95
+ interface UseEmbedReturn {
96
+ result: Ref<EmbedResult | null>;
97
+ loading: Ref<boolean>;
98
+ error: Ref<Error | null>;
99
+ }
100
+ /**
101
+ * Composable that resolves a URL to embed data using framer-framer.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * const { result, loading, error } = useEmbed('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
106
+ * ```
107
+ */
108
+ declare function useEmbed(url: MaybeRefOrGetter<string>, options?: UseEmbedOptions): UseEmbedReturn;
109
+
110
+ export { Embed, type EmbedProps, type Theme, type UseEmbedOptions, type UseEmbedReturn, useEmbed };
package/dist/index.js ADDED
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Embed: () => Embed,
24
+ useEmbed: () => useEmbed
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/Embed.ts
29
+ var import_vue2 = require("vue");
30
+
31
+ // src/theme.ts
32
+ var themeStyleId = "framer-framer-theme";
33
+ var themeCSS = `
34
+ [data-framer-theme="light"] {
35
+ --framer-bg: #ffffff;
36
+ --framer-border: #e5e7eb;
37
+ --framer-skeleton-bg: #e5e7eb;
38
+ --framer-error-bg: #fef2f2;
39
+ --framer-error-border: #fca5a5;
40
+ --framer-error-text: #991b1b;
41
+ }
42
+
43
+ [data-framer-theme="dark"] {
44
+ --framer-bg: #1f2937;
45
+ --framer-border: #374151;
46
+ --framer-skeleton-bg: #374151;
47
+ --framer-error-bg: #451a1a;
48
+ --framer-error-border: #991b1b;
49
+ --framer-error-text: #fca5a5;
50
+ }
51
+
52
+ [data-framer-theme="auto"] {
53
+ --framer-bg: #ffffff;
54
+ --framer-border: #e5e7eb;
55
+ --framer-skeleton-bg: #e5e7eb;
56
+ --framer-error-bg: #fef2f2;
57
+ --framer-error-border: #fca5a5;
58
+ --framer-error-text: #991b1b;
59
+ }
60
+
61
+ @media (prefers-color-scheme: dark) {
62
+ [data-framer-theme="auto"] {
63
+ --framer-bg: #1f2937;
64
+ --framer-border: #374151;
65
+ --framer-skeleton-bg: #374151;
66
+ --framer-error-bg: #451a1a;
67
+ --framer-error-border: #991b1b;
68
+ --framer-error-text: #fca5a5;
69
+ }
70
+ }`;
71
+
72
+ // src/useEmbed.ts
73
+ var import_framer_framer = require("framer-framer");
74
+ var import_vue = require("vue");
75
+ function useEmbed(url, options) {
76
+ const result = (0, import_vue.ref)(null);
77
+ const loading = (0, import_vue.ref)(true);
78
+ const error = (0, import_vue.ref)(null);
79
+ async function resolve(targetUrl) {
80
+ if (!targetUrl) {
81
+ result.value = null;
82
+ loading.value = false;
83
+ error.value = null;
84
+ return;
85
+ }
86
+ loading.value = true;
87
+ error.value = null;
88
+ try {
89
+ const embedResult = await (0, import_framer_framer.embed)(targetUrl, {
90
+ ...options?.embedOptions,
91
+ maxWidth: options?.maxWidth,
92
+ maxHeight: options?.maxHeight
93
+ });
94
+ result.value = embedResult;
95
+ } catch (e) {
96
+ error.value = e instanceof Error ? e : new Error(String(e));
97
+ result.value = null;
98
+ } finally {
99
+ loading.value = false;
100
+ }
101
+ }
102
+ (0, import_vue.watch)(
103
+ () => (0, import_vue.toValue)(url),
104
+ (newUrl) => {
105
+ resolve(newUrl);
106
+ },
107
+ { immediate: true }
108
+ );
109
+ return { result, loading, error };
110
+ }
111
+
112
+ // src/Embed.ts
113
+ var Embed = (0, import_vue2.defineComponent)({
114
+ name: "Embed",
115
+ props: {
116
+ url: {
117
+ type: String,
118
+ required: true
119
+ },
120
+ maxWidth: {
121
+ type: Number,
122
+ default: void 0
123
+ },
124
+ maxHeight: {
125
+ type: Number,
126
+ default: void 0
127
+ },
128
+ options: {
129
+ type: Object,
130
+ default: void 0
131
+ },
132
+ theme: {
133
+ type: String,
134
+ default: "auto"
135
+ }
136
+ },
137
+ emits: {
138
+ load: (_result) => true,
139
+ error: (_error) => true
140
+ },
141
+ slots: Object,
142
+ setup(props, { emit, slots }) {
143
+ const { result, loading, error } = useEmbed(() => props.url, {
144
+ maxWidth: props.maxWidth,
145
+ maxHeight: props.maxHeight,
146
+ embedOptions: props.options
147
+ });
148
+ let lastEmittedUrl = null;
149
+ let lastEmittedError = null;
150
+ return () => {
151
+ const themeStyle = (0, import_vue2.h)("style", { id: themeStyleId }, themeCSS);
152
+ const themeAttr = props.theme ?? "auto";
153
+ if (loading.value) {
154
+ const loadingContent = slots.loading ? slots.loading() : (0, import_vue2.h)("div", { class: "framer-framer-loading" });
155
+ return (0, import_vue2.h)("div", { "data-framer-theme": themeAttr }, [themeStyle, loadingContent]);
156
+ }
157
+ if (error.value) {
158
+ if (lastEmittedError !== error.value) {
159
+ lastEmittedError = error.value;
160
+ emit("error", error.value);
161
+ }
162
+ const errorContent = slots.error ? slots.error({ error: error.value }) : (0, import_vue2.h)("div", { class: "framer-framer-error" }, error.value.message);
163
+ return (0, import_vue2.h)("div", { "data-framer-theme": themeAttr }, [themeStyle, errorContent]);
164
+ }
165
+ if (result.value) {
166
+ if (lastEmittedUrl !== result.value.url) {
167
+ lastEmittedUrl = result.value.url;
168
+ emit("load", result.value);
169
+ }
170
+ if (slots.default) {
171
+ return (0, import_vue2.h)("div", { "data-framer-theme": themeAttr }, [
172
+ themeStyle,
173
+ slots.default({ result: result.value })
174
+ ]);
175
+ }
176
+ return (0, import_vue2.h)("div", { "data-framer-theme": themeAttr }, [
177
+ themeStyle,
178
+ (0, import_vue2.h)("div", {
179
+ class: "framer-framer-embed",
180
+ innerHTML: result.value.html
181
+ })
182
+ ]);
183
+ }
184
+ return null;
185
+ };
186
+ }
187
+ });
188
+ // Annotate the CommonJS export names for ESM import in node:
189
+ 0 && (module.exports = {
190
+ Embed,
191
+ useEmbed
192
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,164 @@
1
+ // src/Embed.ts
2
+ import { defineComponent, h } from "vue";
3
+
4
+ // src/theme.ts
5
+ var themeStyleId = "framer-framer-theme";
6
+ var themeCSS = `
7
+ [data-framer-theme="light"] {
8
+ --framer-bg: #ffffff;
9
+ --framer-border: #e5e7eb;
10
+ --framer-skeleton-bg: #e5e7eb;
11
+ --framer-error-bg: #fef2f2;
12
+ --framer-error-border: #fca5a5;
13
+ --framer-error-text: #991b1b;
14
+ }
15
+
16
+ [data-framer-theme="dark"] {
17
+ --framer-bg: #1f2937;
18
+ --framer-border: #374151;
19
+ --framer-skeleton-bg: #374151;
20
+ --framer-error-bg: #451a1a;
21
+ --framer-error-border: #991b1b;
22
+ --framer-error-text: #fca5a5;
23
+ }
24
+
25
+ [data-framer-theme="auto"] {
26
+ --framer-bg: #ffffff;
27
+ --framer-border: #e5e7eb;
28
+ --framer-skeleton-bg: #e5e7eb;
29
+ --framer-error-bg: #fef2f2;
30
+ --framer-error-border: #fca5a5;
31
+ --framer-error-text: #991b1b;
32
+ }
33
+
34
+ @media (prefers-color-scheme: dark) {
35
+ [data-framer-theme="auto"] {
36
+ --framer-bg: #1f2937;
37
+ --framer-border: #374151;
38
+ --framer-skeleton-bg: #374151;
39
+ --framer-error-bg: #451a1a;
40
+ --framer-error-border: #991b1b;
41
+ --framer-error-text: #fca5a5;
42
+ }
43
+ }`;
44
+
45
+ // src/useEmbed.ts
46
+ import { embed } from "framer-framer";
47
+ import { ref, toValue, watch } from "vue";
48
+ function useEmbed(url, options) {
49
+ const result = ref(null);
50
+ const loading = ref(true);
51
+ const error = ref(null);
52
+ async function resolve(targetUrl) {
53
+ if (!targetUrl) {
54
+ result.value = null;
55
+ loading.value = false;
56
+ error.value = null;
57
+ return;
58
+ }
59
+ loading.value = true;
60
+ error.value = null;
61
+ try {
62
+ const embedResult = await embed(targetUrl, {
63
+ ...options?.embedOptions,
64
+ maxWidth: options?.maxWidth,
65
+ maxHeight: options?.maxHeight
66
+ });
67
+ result.value = embedResult;
68
+ } catch (e) {
69
+ error.value = e instanceof Error ? e : new Error(String(e));
70
+ result.value = null;
71
+ } finally {
72
+ loading.value = false;
73
+ }
74
+ }
75
+ watch(
76
+ () => toValue(url),
77
+ (newUrl) => {
78
+ resolve(newUrl);
79
+ },
80
+ { immediate: true }
81
+ );
82
+ return { result, loading, error };
83
+ }
84
+
85
+ // src/Embed.ts
86
+ var Embed = defineComponent({
87
+ name: "Embed",
88
+ props: {
89
+ url: {
90
+ type: String,
91
+ required: true
92
+ },
93
+ maxWidth: {
94
+ type: Number,
95
+ default: void 0
96
+ },
97
+ maxHeight: {
98
+ type: Number,
99
+ default: void 0
100
+ },
101
+ options: {
102
+ type: Object,
103
+ default: void 0
104
+ },
105
+ theme: {
106
+ type: String,
107
+ default: "auto"
108
+ }
109
+ },
110
+ emits: {
111
+ load: (_result) => true,
112
+ error: (_error) => true
113
+ },
114
+ slots: Object,
115
+ setup(props, { emit, slots }) {
116
+ const { result, loading, error } = useEmbed(() => props.url, {
117
+ maxWidth: props.maxWidth,
118
+ maxHeight: props.maxHeight,
119
+ embedOptions: props.options
120
+ });
121
+ let lastEmittedUrl = null;
122
+ let lastEmittedError = null;
123
+ return () => {
124
+ const themeStyle = h("style", { id: themeStyleId }, themeCSS);
125
+ const themeAttr = props.theme ?? "auto";
126
+ if (loading.value) {
127
+ const loadingContent = slots.loading ? slots.loading() : h("div", { class: "framer-framer-loading" });
128
+ return h("div", { "data-framer-theme": themeAttr }, [themeStyle, loadingContent]);
129
+ }
130
+ if (error.value) {
131
+ if (lastEmittedError !== error.value) {
132
+ lastEmittedError = error.value;
133
+ emit("error", error.value);
134
+ }
135
+ const errorContent = slots.error ? slots.error({ error: error.value }) : h("div", { class: "framer-framer-error" }, error.value.message);
136
+ return h("div", { "data-framer-theme": themeAttr }, [themeStyle, errorContent]);
137
+ }
138
+ if (result.value) {
139
+ if (lastEmittedUrl !== result.value.url) {
140
+ lastEmittedUrl = result.value.url;
141
+ emit("load", result.value);
142
+ }
143
+ if (slots.default) {
144
+ return h("div", { "data-framer-theme": themeAttr }, [
145
+ themeStyle,
146
+ slots.default({ result: result.value })
147
+ ]);
148
+ }
149
+ return h("div", { "data-framer-theme": themeAttr }, [
150
+ themeStyle,
151
+ h("div", {
152
+ class: "framer-framer-embed",
153
+ innerHTML: result.value.html
154
+ })
155
+ ]);
156
+ }
157
+ return null;
158
+ };
159
+ }
160
+ });
161
+ export {
162
+ Embed,
163
+ useEmbed
164
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@framer-framer/vue",
3
+ "version": "3.2.0",
4
+ "description": "Vue 3 embed component for framer-framer",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "engines": {
19
+ "node": ">=22.0.0"
20
+ },
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "typecheck": "tsc --noEmit",
26
+ "lint": "biome check src test",
27
+ "lint:fix": "biome check --write src test",
28
+ "format": "biome format src test",
29
+ "format:fix": "biome format --write src test",
30
+ "ci": "npm run lint && npm run typecheck && npm run test && npm run build",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "peerDependencies": {
34
+ "framer-framer": "^3.0.0",
35
+ "vue": "^3.3.0"
36
+ },
37
+ "devDependencies": {
38
+ "@biomejs/biome": "^2.4.4",
39
+ "@vue/test-utils": "^2.4.0",
40
+ "framer-framer": "^3.2.0",
41
+ "happy-dom": "^20.8.8",
42
+ "tsup": "^8.0.0",
43
+ "typescript": "^5.4.0",
44
+ "vitest": "^4.0.18",
45
+ "vue": "^3.5.0"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git@github.com:piroz/framer-framer.git",
50
+ "directory": "packages/vue"
51
+ },
52
+ "author": "piroz",
53
+ "license": "MIT",
54
+ "bugs": "https://github.com/piroz/framer-framer/issues",
55
+ "keywords": [
56
+ "vue",
57
+ "vue3",
58
+ "embed",
59
+ "oembed",
60
+ "component",
61
+ "framer-framer"
62
+ ]
63
+ }