@vouchfor/embeds 0.0.0-experiment.bb1a5cf → 0.0.0-experiment.eb79200

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vouchfor/embeds",
3
- "version": "0.0.0-experiment.bb1a5cf",
3
+ "version": "0.0.0-experiment.eb79200",
4
4
  "license": "MIT",
5
5
  "author": "Aaron Williams",
6
6
  "main": "dist/es/embeds.js",
@@ -35,10 +35,12 @@
35
35
  "**/*.{md,json,yml}": "prettier --write"
36
36
  },
37
37
  "dependencies": {
38
- "@vouchfor/media-player": "0.0.0-experiment.bb1a5cf"
38
+ "@lit/task": "1.0.0",
39
+ "@vouchfor/media-player": "0.0.0-experiment.eb79200",
40
+ "uuid": "^9.0.1"
39
41
  },
40
42
  "peerDependencies": {
41
- "lit": "^2.7.5"
43
+ "lit": "^3.0.2"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@esm-bundle/chai": "^4.3.4-fix.0",
@@ -0,0 +1,82 @@
1
+ import { html } from 'lit';
2
+ import { ifDefined } from 'lit/directives/if-defined.js';
3
+
4
+ import type { EmbedProps } from './';
5
+ import type { Meta, StoryObj } from '@storybook/web-components';
6
+
7
+ import './';
8
+
9
+ type EmbedArgs = EmbedProps & {
10
+ showVouch?: boolean;
11
+ };
12
+
13
+ const _Embed = ({
14
+ vouchHashId,
15
+ preload,
16
+ autoplay,
17
+ env,
18
+ apiKey,
19
+ controls,
20
+ enableTracking,
21
+ trackingSource,
22
+
23
+ resolution,
24
+ aspectRatio
25
+ }: EmbedArgs) => {
26
+ return html`
27
+ <div style="height: 100vh">
28
+ <vouch-embed
29
+ env=${ifDefined(env)}
30
+ apiKey=${ifDefined(apiKey)}
31
+ ?autoplay=${autoplay}
32
+ ?enableTracking=${enableTracking}
33
+ vouchHashId=${ifDefined(vouchHashId)}
34
+ resolution=${ifDefined(resolution)}
35
+ aspectRatio=${ifDefined(aspectRatio)}
36
+ preload=${ifDefined(preload)}
37
+ .controls=${controls}
38
+ trackingSource=${ifDefined(trackingSource)}
39
+ ></vouch-embed>
40
+ </div>
41
+ `;
42
+ };
43
+
44
+ // More on how to set up stories at: https://storybook.js.org/docs/web-components/writing-stories/introduction
45
+ const meta = {
46
+ title: 'Embed',
47
+ tags: ['autodocs'],
48
+ render: (args) => _Embed(args),
49
+ component: 'vouch-embed'
50
+ } satisfies Meta<EmbedProps>;
51
+
52
+ type Story = StoryObj<EmbedArgs>;
53
+
54
+ const Embed: Story = {
55
+ args: {
56
+ env: 'dev',
57
+ apiKey: 'TVik9uTMgE-PD25UTHIS6gyl0hMBWC7AT4dkpdlLBT4VIfDWZJrQiCk6Ak7m1',
58
+ vouchHashId: '6JQEIPeStt',
59
+ resolution: 1080,
60
+ aspectRatio: 0,
61
+ preload: 'none',
62
+ autoplay: false,
63
+ enableTracking: true,
64
+ trackingSource: 'media_player_storybook'
65
+ },
66
+ argTypes: {
67
+ env: {
68
+ control: 'radio',
69
+ options: ['prod', 'staging', 'dev']
70
+ },
71
+ preload: {
72
+ control: 'radio',
73
+ options: ['auto', 'none']
74
+ }
75
+ },
76
+ parameters: {
77
+ layout: 'fullscreen'
78
+ }
79
+ };
80
+
81
+ export default meta;
82
+ export { Embed };
@@ -0,0 +1,47 @@
1
+ import { createRef, ref } from 'lit/directives/ref.js';
2
+
3
+ import type { Embed } from '..';
4
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
5
+ import type { Ref } from 'lit/directives/ref.js';
6
+
7
+ import { forwardEvent } from '~/utils/events';
8
+
9
+ type EmbedHost = ReactiveControllerHost & Embed;
10
+
11
+ class EventForwardController implements ReactiveController {
12
+ host: EmbedHost;
13
+
14
+ private _events: string[] = [];
15
+ private _cleanup: (() => void)[] = [];
16
+ private _forwardElementRef: Ref<HTMLElement> = createRef();
17
+
18
+ constructor(host: EmbedHost, events: string[]) {
19
+ this.host = host;
20
+ this._events = events;
21
+ host.addController(this);
22
+ }
23
+
24
+ register() {
25
+ return ref(this._forwardElementRef);
26
+ }
27
+
28
+ hostConnected() {
29
+ // Guaranteed to have the element ref in the RAF callback because of how the lit lifecycle works
30
+ requestAnimationFrame(() => {
31
+ this._events.forEach((event) => {
32
+ if (this._forwardElementRef.value) {
33
+ this._cleanup.push(forwardEvent(event, this._forwardElementRef.value, this.host));
34
+ }
35
+ });
36
+ });
37
+ }
38
+
39
+ hostDisconnected() {
40
+ this._cleanup.forEach((fn) => {
41
+ fn();
42
+ });
43
+ this._cleanup = [];
44
+ }
45
+ }
46
+
47
+ export { EventForwardController };
@@ -0,0 +1,43 @@
1
+ import { Task } from '@lit/task';
2
+
3
+ import type { Embed, EmbedProps } from '..';
4
+ import type { ReactiveControllerHost } from 'lit';
5
+
6
+ import { getEnvUrls } from '~/utils/env';
7
+
8
+ type EmbedHost = ReactiveControllerHost & Embed;
9
+
10
+ type TaskDeps = [EmbedProps['env'], EmbedProps['apiKey'], EmbedProps['vouchHashId']];
11
+
12
+ class FetcherController {
13
+ host: EmbedHost;
14
+
15
+ constructor(host: EmbedHost) {
16
+ this.host = host;
17
+ new Task<TaskDeps, void>(
18
+ this.host,
19
+ async ([env, apiKey, vouchHashId]: TaskDeps) => {
20
+ try {
21
+ if (vouchHashId) {
22
+ host.fetching = true;
23
+
24
+ const { embedApiUrl } = getEnvUrls(env);
25
+
26
+ const data = await fetch(`${embedApiUrl}/vouches/${vouchHashId}`, {
27
+ method: 'GET',
28
+ headers: [['X-Api-Key', apiKey]]
29
+ }).then((response) => response.json());
30
+
31
+ host.data = data;
32
+ host.instance = data?.settings?.template?.instance;
33
+ }
34
+ } finally {
35
+ host.fetching = false;
36
+ }
37
+ },
38
+ () => [host.env, host.apiKey, host.vouchHashId]
39
+ );
40
+ }
41
+ }
42
+
43
+ export { FetcherController };
@@ -0,0 +1,268 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+
3
+ import type { Embed } from '..';
4
+ import type { VideoEventDetail } from '@vouchfor/media-player';
5
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
6
+
7
+ import { getEnvUrls } from '~/utils/env';
8
+
9
+ const STREAMED_THROTTLE = 2000;
10
+
11
+ type EmbedHost = ReactiveControllerHost & Embed;
12
+
13
+ type TrackingEvent = 'VOUCH_LOADED' | 'VOUCH_RESPONSE_VIEWED' | 'VIDEO_PLAYED' | 'VIDEO_STREAMED';
14
+ type TrackingPayload = {
15
+ vouchId: string;
16
+ answerId?: string;
17
+ streamStart?: number;
18
+ streamEnd?: number;
19
+ };
20
+
21
+ type TimeMap = {
22
+ [key: string]: number;
23
+ };
24
+
25
+ type BooleanMap = {
26
+ [key: string]: boolean;
27
+ };
28
+
29
+ class TrackingController implements ReactiveController {
30
+ host: EmbedHost;
31
+
32
+ private _tabId: string | undefined = undefined;
33
+ private _clientId: string | undefined = undefined;
34
+ private _visitorId: string | undefined = undefined;
35
+
36
+ private _hasPlayed = false;
37
+ private _hasLoaded: BooleanMap = {};
38
+ private _answersViewed: BooleanMap = {};
39
+ private _streamedTime: TimeMap = {};
40
+ private _streamedPrevTimestamp: TimeMap = {};
41
+
42
+ constructor(host: EmbedHost) {
43
+ this.host = host;
44
+ host.addController(this);
45
+ }
46
+
47
+ private _findVouchId() {
48
+ if (this.host.data) {
49
+ if ('uuid' in this.host.data) {
50
+ return this.host.data.uuid;
51
+ }
52
+ return this.host.data.id;
53
+ }
54
+ }
55
+
56
+ private _createVisitor = (visitorId: string) => {
57
+ const { publicApiUrl } = getEnvUrls(this.host.env);
58
+ window.localStorage?.setItem?.('vouch-uid-visitor', visitorId);
59
+ navigator.sendBeacon(`${publicApiUrl}/api/visitor`, JSON.stringify({ visitorId }));
60
+ return visitorId;
61
+ };
62
+
63
+ private _getUids() {
64
+ if (typeof window === 'undefined') {
65
+ return {
66
+ client: null,
67
+ tab: null,
68
+ request: uuidv4()
69
+ };
70
+ }
71
+
72
+ // Persisted for a user for the same device + browser, so we can e.g. search for all logs related to that browser
73
+ const visitorId =
74
+ this._visitorId || window.localStorage?.getItem?.('vouch-uid-visitor') || this._createVisitor(uuidv4());
75
+ // Persisted for a user for the same device + browser, so we can e.g. search for all logs related to that browser
76
+ const clientId = this._clientId || window.localStorage?.getItem?.('vouch-uid-client') || uuidv4();
77
+ // Persisted in session storage, so we can search for everything the user has done in a specific tab
78
+ const tabId = this._tabId || window.sessionStorage?.getItem?.('vouch-uid-tab') || uuidv4();
79
+ // Not persisted, allows us to search for any logs related to a single FE request
80
+ // E.g. BE should pass this request ID through all other services to be able to group logs
81
+ const requestId = uuidv4();
82
+
83
+ // Cache and persist uids
84
+ if (visitorId !== this._visitorId) {
85
+ this._visitorId = visitorId;
86
+ window.localStorage?.setItem?.('vouch-uid-visitor', visitorId);
87
+ }
88
+
89
+ if (clientId !== this._clientId) {
90
+ this._clientId = clientId;
91
+ window.localStorage?.setItem?.('vouch-uid-client', clientId);
92
+ }
93
+
94
+ if (tabId !== this._tabId) {
95
+ this._tabId = tabId;
96
+ window.sessionStorage?.setItem?.('vouch-uid-tab', tabId);
97
+ }
98
+
99
+ return {
100
+ client: clientId,
101
+ tab: tabId,
102
+ request: requestId,
103
+ visitor: visitorId
104
+ };
105
+ }
106
+
107
+ private _getReportingMetadata = () => {
108
+ const [country, region] = Intl.DateTimeFormat().resolvedOptions().timeZone?.split?.('/') ?? [];
109
+
110
+ const utmParams: any = {};
111
+ [...new URLSearchParams(location.search).entries()].forEach(([key, value]) => {
112
+ if (/utm/.test(key)) {
113
+ const param = key.toLowerCase().replace(/[-_][a-z0-9]/g, (group) => group.slice(-1).toUpperCase());
114
+ utmParams[param] = value;
115
+ }
116
+ });
117
+
118
+ return {
119
+ // Source might be embeds, could be playlink etc.
120
+ source: this.host.trackingSource,
121
+ time: new Date(),
122
+ region,
123
+ country,
124
+ screenHeight: window.screen.height,
125
+ screenWidth: window.screen.width,
126
+ referrer: document.referrer,
127
+ currentUrl: location.href,
128
+ ...utmParams
129
+ };
130
+ };
131
+
132
+ private _sendTrackingEvent = (event: TrackingEvent, payload: TrackingPayload) => {
133
+ const { publicApiUrl } = getEnvUrls(this.host.env);
134
+ const { client, tab, request, visitor } = this._getUids();
135
+
136
+ // Don't send tracking if we don't have a source
137
+ if (this.host.enableTracking && this.host.trackingSource) {
138
+ navigator.sendBeacon(
139
+ `${publicApiUrl}/api/events`,
140
+ JSON.stringify({
141
+ event,
142
+ payload,
143
+ context: {
144
+ 'x-uid-client': client,
145
+ 'x-uid-tab': tab,
146
+ 'x-uid-request': request,
147
+ 'x-uid-visitor': visitor,
148
+ 'x-reporting-metadata': this._getReportingMetadata()
149
+ }
150
+ })
151
+ );
152
+ }
153
+ };
154
+
155
+ private _handleVouchLoaded = ({ detail: vouchId }: CustomEvent<string>) => {
156
+ if (!vouchId) {
157
+ return;
158
+ }
159
+
160
+ // Only send loaded event once per session
161
+ if (!this._hasLoaded[vouchId]) {
162
+ this._sendTrackingEvent('VOUCH_LOADED', {
163
+ vouchId
164
+ });
165
+ this._hasLoaded[vouchId] = true;
166
+ }
167
+ };
168
+
169
+ private _handlePlay = () => {
170
+ const vouchId = this._findVouchId();
171
+
172
+ if (!vouchId) {
173
+ return;
174
+ }
175
+
176
+ // Only send the video played event once per session
177
+ if (!this._hasPlayed) {
178
+ this._sendTrackingEvent('VIDEO_PLAYED', {
179
+ vouchId,
180
+ streamStart: this.host.currentTime
181
+ });
182
+ this._hasPlayed = true;
183
+ }
184
+ };
185
+
186
+ private _handleVideoPlay = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
187
+ const vouchId = this._findVouchId();
188
+ if (!vouchId) {
189
+ return;
190
+ }
191
+ // Only increment play count once per session
192
+ if (!this._answersViewed[id]) {
193
+ this._sendTrackingEvent('VOUCH_RESPONSE_VIEWED', {
194
+ vouchId,
195
+ answerId: id
196
+ });
197
+ this._answersViewed[id] = true;
198
+ }
199
+ this._streamedTime[id] = node.currentTime;
200
+ this._streamedPrevTimestamp[id] = Date.now();
201
+ };
202
+
203
+ private _handleVideoTimeUpdate = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
204
+ const vouchId = this._findVouchId();
205
+ if (!vouchId) {
206
+ return;
207
+ }
208
+ const currentTimestamp = Date.now();
209
+ if (
210
+ !this.host.paused &&
211
+ // Only fire the video seeked event when this video is the active one
212
+ id === this.host.scene?.video?.id &&
213
+ // Throttle the frequency that we send streamed events while playing
214
+ currentTimestamp - this._streamedPrevTimestamp[id] > STREAMED_THROTTLE
215
+ ) {
216
+ this._sendTrackingEvent('VIDEO_STREAMED', {
217
+ vouchId,
218
+ answerId: id,
219
+ streamStart: this._streamedTime[id],
220
+ streamEnd: node.currentTime
221
+ });
222
+ this._streamedTime[id] = node.currentTime;
223
+ this._streamedPrevTimestamp[id] = currentTimestamp;
224
+ }
225
+ };
226
+
227
+ private _handleVideoPause = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
228
+ const vouchId = this._findVouchId();
229
+ if (!vouchId) {
230
+ return;
231
+ }
232
+ // Send a video streamed event any time the video pauses then reset the streamed state
233
+ // We do this to capture the last bit of time that the video was played between the previous
234
+ // stream event and the video being paused manually or stopping because it ended
235
+ this._sendTrackingEvent('VIDEO_STREAMED', {
236
+ vouchId,
237
+ answerId: id,
238
+ streamStart: this._streamedTime[id],
239
+ streamEnd: node.currentTime
240
+ });
241
+ delete this._streamedTime[id];
242
+ delete this._streamedPrevTimestamp[id];
243
+ };
244
+
245
+ hostConnected() {
246
+ if (this.host.enableTracking && this.host.trackingSource) {
247
+ requestAnimationFrame(() => {
248
+ this.host.addEventListener('vouch:loaded', this._handleVouchLoaded);
249
+ this.host.mediaPlayer?.addEventListener('play', this._handlePlay);
250
+ this.host.mediaPlayer?.addEventListener('video:play', this._handleVideoPlay);
251
+ this.host.mediaPlayer?.addEventListener('video:pause', this._handleVideoPause);
252
+ this.host.mediaPlayer?.addEventListener('video:timeupdate', this._handleVideoTimeUpdate);
253
+ });
254
+ }
255
+ }
256
+
257
+ hostDisconnected() {
258
+ if (this.host.enableTracking && this.host.trackingSource) {
259
+ this.host.removeEventListener('vouch:loaded', this._handleVouchLoaded);
260
+ this.host.mediaPlayer?.removeEventListener('play', this._handlePlay);
261
+ this.host.mediaPlayer?.removeEventListener('video:play', this._handleVideoPlay);
262
+ this.host.mediaPlayer?.removeEventListener('video:pause', this._handleVideoPause);
263
+ this.host.mediaPlayer?.removeEventListener('video:timeupdate', this._handleVideoTimeUpdate);
264
+ }
265
+ }
266
+ }
267
+
268
+ export { TrackingController };
@@ -0,0 +1,204 @@
1
+ import { html, LitElement } from 'lit';
2
+ import { customElement, state, property } from 'lit/decorators.js';
3
+ import { ifDefined } from 'lit/directives/if-defined.js';
4
+ import { createRef, ref } from 'lit/directives/ref.js';
5
+
6
+ import type { Scene } from '@vouchfor/canvas-video';
7
+ import type { MediaPlayer, MediaPlayerProps } from '@vouchfor/media-player';
8
+ import type { Ref } from 'lit/directives/ref.js';
9
+ import type { Environment } from '~/utils/env';
10
+
11
+ import { EventForwardController } from './controllers/event-forwarder';
12
+ import { FetcherController } from './controllers/fetcher';
13
+ import { TrackingController } from './controllers/tracking';
14
+
15
+ import '@vouchfor/media-player';
16
+
17
+ type EmbedProps = Pick<
18
+ MediaPlayerProps,
19
+ 'data' | 'resolution' | 'aspectRatio' | 'preload' | 'autoplay' | 'controls'
20
+ > & {
21
+ env: Environment;
22
+ apiKey: string;
23
+ vouchHashId?: string;
24
+ enableTracking?: boolean;
25
+ trackingSource?: string;
26
+ };
27
+
28
+ @customElement('vouch-embed')
29
+ class Embed extends LitElement {
30
+ private _mediaPlayerRef: Ref<MediaPlayer> = createRef();
31
+
32
+ @property({ type: Object, attribute: 'data' }) propData: EmbedProps['data'];
33
+ @property({ type: String }) vouchHashId: EmbedProps['vouchHashId'];
34
+
35
+ @property({ type: String }) env: EmbedProps['env'] = 'prod';
36
+ @property({ type: String }) apiKey: EmbedProps['apiKey'] = '';
37
+
38
+ @property({ type: String }) preload: EmbedProps['preload'] = 'auto';
39
+ @property({ type: Boolean }) autoplay: EmbedProps['autoplay'] = false;
40
+ @property({ type: Boolean }) enableTracking: EmbedProps['enableTracking'] = false;
41
+ @property({ type: String }) trackingSource: EmbedProps['trackingSource'] = 'media_player';
42
+
43
+ @property({ type: Number }) resolution: EmbedProps['resolution'] = 1080;
44
+ @property({ type: Number }) aspectRatio: EmbedProps['aspectRatio'] = 0;
45
+ @property({ type: Array }) controls: EmbedProps['controls'];
46
+
47
+ @state() data: MediaPlayerProps['data'];
48
+ @state() instance: MediaPlayerProps['instance'];
49
+ @state() fetching = false;
50
+
51
+ private eventController = new EventForwardController(this, [
52
+ 'durationchange',
53
+ 'ended',
54
+ 'error',
55
+ 'loadeddata',
56
+ 'pause',
57
+ 'stalled',
58
+ 'play',
59
+ 'playing',
60
+ 'ratechange',
61
+ 'scenechange',
62
+ 'seeking',
63
+ 'seeked',
64
+ 'timeupdate',
65
+ 'volumechange',
66
+ 'waiting',
67
+
68
+ 'video:loadeddata',
69
+ 'video:seeked',
70
+ 'video:play',
71
+ 'video:playing',
72
+ 'video:pause',
73
+ 'video:stalled',
74
+ 'video:timeupdate',
75
+ 'video:ended',
76
+ 'video:error'
77
+ ]);
78
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
79
+ // @ts-ignore
80
+ private trackingController = new TrackingController(this);
81
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
82
+ // @ts-ignore
83
+ private fetcherController = new FetcherController(this);
84
+
85
+ get waiting() {
86
+ return this._mediaPlayerRef.value?.waiting;
87
+ }
88
+
89
+ get seeking() {
90
+ return this._mediaPlayerRef.value?.seeking;
91
+ }
92
+
93
+ get paused() {
94
+ return this._mediaPlayerRef.value?.paused;
95
+ }
96
+
97
+ get captions() {
98
+ return this._mediaPlayerRef.value?.captions;
99
+ }
100
+
101
+ get fullscreen() {
102
+ return this._mediaPlayerRef.value?.fullscreen;
103
+ }
104
+
105
+ get duration() {
106
+ return this._mediaPlayerRef.value?.duration;
107
+ }
108
+
109
+ set currentTime(value: number) {
110
+ if (this._mediaPlayerRef.value) {
111
+ this._mediaPlayerRef.value.currentTime = value;
112
+ }
113
+ }
114
+ get currentTime() {
115
+ return this._mediaPlayerRef.value?.currentTime ?? 0;
116
+ }
117
+
118
+ set playbackRate(value: number) {
119
+ if (this._mediaPlayerRef.value) {
120
+ this._mediaPlayerRef.value.playbackRate = value;
121
+ }
122
+ }
123
+ get playbackRate() {
124
+ return this._mediaPlayerRef.value?.playbackRate ?? 1;
125
+ }
126
+
127
+ set volume(value: number) {
128
+ if (this._mediaPlayerRef.value) {
129
+ this._mediaPlayerRef.value.volume = value;
130
+ }
131
+ }
132
+ get volume() {
133
+ return this._mediaPlayerRef.value?.volume ?? 1;
134
+ }
135
+
136
+ set muted(value: boolean) {
137
+ if (this._mediaPlayerRef.value) {
138
+ this._mediaPlayerRef.value.muted = value;
139
+ }
140
+ }
141
+ get muted() {
142
+ return this._mediaPlayerRef.value?.muted ?? false;
143
+ }
144
+
145
+ get scene(): Scene | null {
146
+ return this._mediaPlayerRef.value?.scene ?? null;
147
+ }
148
+
149
+ get scenes(): Scene[] {
150
+ return this._mediaPlayerRef.value?.scenes ?? [];
151
+ }
152
+
153
+ get videoState() {
154
+ return this._mediaPlayerRef.value?.videoState;
155
+ }
156
+
157
+ get mediaPlayer() {
158
+ return this._mediaPlayerRef.value;
159
+ }
160
+
161
+ play() {
162
+ this._mediaPlayerRef.value?.play();
163
+ }
164
+
165
+ pause() {
166
+ this._mediaPlayerRef.value?.pause();
167
+ }
168
+
169
+ setScene(index: number) {
170
+ this._mediaPlayerRef.value?.setScene(index);
171
+ }
172
+
173
+ render() {
174
+ return html`
175
+ <vmp-media-player
176
+ ${ref(this._mediaPlayerRef)}
177
+ ${this.eventController.register()}
178
+ ?autoplay=${this.autoplay}
179
+ ?loading=${this.fetching}
180
+ .data=${this.data}
181
+ .instance=${this.instance}
182
+ resolution=${ifDefined(this.resolution)}
183
+ aspectRatio=${ifDefined(this.aspectRatio)}
184
+ preload=${ifDefined(this.preload)}
185
+ .controls=${this.controls}
186
+ ></vmp-media-player>
187
+ `;
188
+ }
189
+ }
190
+
191
+ declare global {
192
+ interface HTMLElementTagNameMap {
193
+ 'vouch-embed': Embed;
194
+ }
195
+
196
+ namespace JSX {
197
+ interface IntrinsicElements {
198
+ 'vouch-embed': Embed;
199
+ }
200
+ }
201
+ }
202
+
203
+ export { Embed };
204
+ export type { EmbedProps };
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export { InlineEmbed } from '~/components/InlineEmbed';
1
+ export { Embed } from '~/components/Embed';