@vouchfor/embeds 0.0.0-experiment.17b3fc9 → 0.0.0-experiment.18c17da
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/dist/es/components/Embed/controllers/fetcher.d.ts +0 -7
- package/dist/es/components/Embed/index.d.ts +7 -3
- package/dist/es/embeds.js +942 -659
- package/dist/es/embeds.js.map +1 -1
- package/dist/es/utils/env.d.ts +5 -11
- package/dist/iife/embeds.iife.js +225 -289
- package/dist/iife/embeds.iife.js.map +1 -1
- package/package.json +6 -6
- package/src/components/Embed/Embed.stories.ts +3 -3
- package/src/components/Embed/controllers/fetcher.ts +63 -45
- package/src/components/Embed/controllers/tracking.ts +40 -30
- package/src/components/Embed/index.ts +7 -8
- package/src/utils/env.ts +18 -32
- package/dist/es/components/Embed/controllers/event-forwarder.d.ts +0 -14
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vouchfor/embeds",
|
3
|
-
"version": "0.0.0-experiment.
|
3
|
+
"version": "0.0.0-experiment.18c17da",
|
4
4
|
"license": "MIT",
|
5
5
|
"author": "Aaron Williams",
|
6
6
|
"main": "dist/es/embeds.js",
|
@@ -26,7 +26,7 @@
|
|
26
26
|
"lint:staged": "lint-staged",
|
27
27
|
"prepublishOnly": "yarn build",
|
28
28
|
"size": "size-limit",
|
29
|
-
"storybook": "yarn prebuild && storybook dev -p
|
29
|
+
"storybook": "yarn prebuild && storybook dev -p 6007",
|
30
30
|
"prebuild": "yarn build:deps && yarn generate:manifest",
|
31
31
|
"test": "true"
|
32
32
|
},
|
@@ -35,12 +35,12 @@
|
|
35
35
|
"**/*.{md,json,yml}": "prettier --write"
|
36
36
|
},
|
37
37
|
"dependencies": {
|
38
|
-
"@lit/task": "1.0.0",
|
39
|
-
"@vouchfor/media-player": "0.0.0-experiment.
|
38
|
+
"@lit/task": "^1.0.0",
|
39
|
+
"@vouchfor/media-player": "0.0.0-experiment.18c17da",
|
40
40
|
"uuid": "^9.0.1"
|
41
41
|
},
|
42
42
|
"peerDependencies": {
|
43
|
-
"lit": "^3.0
|
43
|
+
"lit": "^3.1.0"
|
44
44
|
},
|
45
45
|
"devDependencies": {
|
46
46
|
"@esm-bundle/chai": "^4.3.4-fix.0",
|
@@ -62,7 +62,7 @@
|
|
62
62
|
"eslint": "^8.50.0",
|
63
63
|
"eslint-plugin-import": "^2.28.1",
|
64
64
|
"lint-staged": "^14.0.1",
|
65
|
-
"lit": "^
|
65
|
+
"lit": "^3.1.0",
|
66
66
|
"prettier": "^3.0.3",
|
67
67
|
"react": "^18.2.0",
|
68
68
|
"react-dom": "^18.2.0",
|
@@ -39,10 +39,10 @@ type Story = StoryObj<EmbedArgs>;
|
|
39
39
|
|
40
40
|
const Embed: Story = {
|
41
41
|
args: {
|
42
|
-
env: '
|
42
|
+
env: 'local',
|
43
43
|
apiKey: 'TVik9uTMgE-PD25UTHIS6gyl0hMBWC7AT4dkpdlLBT4VIfDWZJrQiCk6Ak7m1',
|
44
44
|
vouchId: '6JQEIPeStt',
|
45
|
-
templateId: '
|
45
|
+
templateId: '357fc118-e179-4171-9446-ff2b8e9d1b29',
|
46
46
|
aspectRatio: 0,
|
47
47
|
preload: 'none',
|
48
48
|
autoplay: false
|
@@ -50,7 +50,7 @@ const Embed: Story = {
|
|
50
50
|
argTypes: {
|
51
51
|
env: {
|
52
52
|
control: 'radio',
|
53
|
-
options: ['
|
53
|
+
options: ['local', 'dev', 'staging', 'prod']
|
54
54
|
},
|
55
55
|
preload: {
|
56
56
|
control: 'radio',
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { Task } from '@lit/task';
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
2
3
|
|
3
4
|
import type { Embed, EmbedProps } from '..';
|
4
|
-
import type { TemplateInstance } from '@vouchfor/canvas-video';
|
5
5
|
import type { ReactiveControllerHost } from 'lit';
|
6
6
|
import type { Environment } from '~/utils/env';
|
7
7
|
|
@@ -21,8 +21,6 @@ class FetcherController {
|
|
21
21
|
host: EmbedHost;
|
22
22
|
|
23
23
|
private _fetching = false;
|
24
|
-
private _vouch?: EmbedProps['data'];
|
25
|
-
private _template?: TemplateInstance;
|
26
24
|
|
27
25
|
set fetching(value) {
|
28
26
|
if (this._fetching !== value) {
|
@@ -34,51 +32,67 @@ class FetcherController {
|
|
34
32
|
return this._fetching;
|
35
33
|
}
|
36
34
|
|
37
|
-
|
38
|
-
if (this._vouch !== value) {
|
39
|
-
this._vouch = value;
|
40
|
-
this.host.requestUpdate();
|
41
|
-
}
|
42
|
-
}
|
43
|
-
get vouch() {
|
44
|
-
return this._vouch;
|
45
|
-
}
|
46
|
-
|
47
|
-
set template(value) {
|
48
|
-
if (this._template !== value) {
|
49
|
-
this._template = value;
|
50
|
-
this.host.requestUpdate();
|
51
|
-
}
|
52
|
-
}
|
53
|
-
get template() {
|
54
|
-
return this._template;
|
55
|
-
}
|
56
|
-
|
57
|
-
private async getVouch(env: Environment, apiKey: string, vouchId?: string) {
|
35
|
+
private getVouch = async (env: Environment, apiKey: string, vouchId: string) => {
|
58
36
|
const { embedApiUrl } = getEnvUrls(env);
|
59
37
|
|
60
|
-
|
61
|
-
|
38
|
+
const cacheCheck = uuidv4();
|
39
|
+
const res = await fetch(`${embedApiUrl}/vouches/${vouchId}`, {
|
40
|
+
method: 'GET',
|
41
|
+
headers: [
|
42
|
+
['X-Api-Key', apiKey],
|
43
|
+
['X-Cache-Check', cacheCheck]
|
44
|
+
]
|
45
|
+
});
|
46
|
+
|
47
|
+
const vouch = await res.json();
|
48
|
+
this.host.dispatchEvent(new CustomEvent('vouch:loaded', { detail: vouchId }));
|
49
|
+
|
50
|
+
// HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
|
51
|
+
// so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
|
52
|
+
// API call with the `Cache-Control` header which will re-fill the cache
|
53
|
+
const resCacheCheck = res?.headers?.get('X-Cache-Check');
|
54
|
+
if (resCacheCheck && resCacheCheck !== cacheCheck) {
|
55
|
+
fetch(`${embedApiUrl}/vouches/${vouchId}`, {
|
62
56
|
method: 'GET',
|
63
|
-
headers: [
|
64
|
-
|
57
|
+
headers: [
|
58
|
+
['X-Api-Key', apiKey],
|
59
|
+
['Cache-Control', 'max-age=0']
|
60
|
+
]
|
61
|
+
});
|
65
62
|
}
|
66
63
|
|
67
|
-
return
|
68
|
-
}
|
64
|
+
return vouch;
|
65
|
+
};
|
69
66
|
|
70
|
-
private async
|
67
|
+
private getTemplate = async (env: Environment, apiKey: string, templateId: string) => {
|
71
68
|
const { embedApiUrl } = getEnvUrls(env);
|
72
69
|
|
73
|
-
|
74
|
-
|
70
|
+
const cacheCheck = uuidv4();
|
71
|
+
const res = await fetch(`${embedApiUrl}/templates/${templateId}`, {
|
72
|
+
method: 'GET',
|
73
|
+
headers: [
|
74
|
+
['X-Api-Key', apiKey],
|
75
|
+
['X-Cache-Check', cacheCheck]
|
76
|
+
]
|
77
|
+
});
|
78
|
+
const template = await res.json();
|
79
|
+
|
80
|
+
// HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
|
81
|
+
// so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
|
82
|
+
// API call with the `Cache-Control` header which will re-fill the cache
|
83
|
+
const resCacheCheck = res?.headers?.get('X-Cache-Check');
|
84
|
+
if (resCacheCheck && resCacheCheck !== cacheCheck) {
|
85
|
+
fetch(`${embedApiUrl}/templates/${templateId}`, {
|
75
86
|
method: 'GET',
|
76
|
-
headers: [
|
77
|
-
|
87
|
+
headers: [
|
88
|
+
['X-Api-Key', apiKey],
|
89
|
+
['Cache-Control', 'max-age=0']
|
90
|
+
]
|
91
|
+
});
|
78
92
|
}
|
79
93
|
|
80
|
-
return
|
81
|
-
}
|
94
|
+
return template;
|
95
|
+
};
|
82
96
|
|
83
97
|
constructor(host: EmbedHost) {
|
84
98
|
this.host = host;
|
@@ -86,22 +100,26 @@ class FetcherController {
|
|
86
100
|
this.host,
|
87
101
|
async ([env, apiKey, data, vouchId, templateId]: TaskDeps) => {
|
88
102
|
try {
|
89
|
-
|
90
|
-
|
103
|
+
host.vouch = undefined;
|
104
|
+
host.template = undefined;
|
91
105
|
|
92
106
|
if (data) {
|
93
|
-
|
94
|
-
|
95
|
-
|
107
|
+
let template;
|
108
|
+
if (templateId) {
|
109
|
+
this.fetching = true;
|
110
|
+
template = await this.getTemplate(env, apiKey, templateId);
|
111
|
+
}
|
112
|
+
host.vouch = data;
|
113
|
+
host.template = template ?? data?.settings?.template?.instance;
|
96
114
|
} else if (vouchId) {
|
97
115
|
this.fetching = true;
|
98
116
|
|
99
117
|
const [vouch, template] = await Promise.all([
|
100
118
|
this.getVouch(env, apiKey, vouchId),
|
101
|
-
this.getTemplate(env, apiKey, templateId)
|
119
|
+
templateId ? this.getTemplate(env, apiKey, templateId) : null
|
102
120
|
]);
|
103
|
-
|
104
|
-
|
121
|
+
host.vouch = vouch;
|
122
|
+
host.template = template ?? vouch?.settings?.template?.instance;
|
105
123
|
}
|
106
124
|
} finally {
|
107
125
|
this.fetching = false;
|
@@ -6,7 +6,7 @@ import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
6
6
|
|
7
7
|
import { getEnvUrls } from '~/utils/env';
|
8
8
|
|
9
|
-
const STREAMED_THROTTLE =
|
9
|
+
const STREAMED_THROTTLE = 10000;
|
10
10
|
|
11
11
|
type EmbedHost = ReactiveControllerHost & Embed;
|
12
12
|
|
@@ -45,11 +45,11 @@ class TrackingController implements ReactiveController {
|
|
45
45
|
}
|
46
46
|
|
47
47
|
private _findVouchId() {
|
48
|
-
if (this.host.
|
49
|
-
if ('uuid' in this.host.
|
50
|
-
return this.host.
|
48
|
+
if (this.host.vouch) {
|
49
|
+
if ('uuid' in this.host.vouch) {
|
50
|
+
return this.host.vouch.uuid;
|
51
51
|
}
|
52
|
-
return this.host.
|
52
|
+
return this.host.vouch.id;
|
53
53
|
}
|
54
54
|
}
|
55
55
|
|
@@ -116,7 +116,7 @@ class TrackingController implements ReactiveController {
|
|
116
116
|
});
|
117
117
|
|
118
118
|
return {
|
119
|
-
source:
|
119
|
+
source: this.host.trackingSource,
|
120
120
|
time: new Date(),
|
121
121
|
region,
|
122
122
|
country,
|
@@ -132,21 +132,22 @@ class TrackingController implements ReactiveController {
|
|
132
132
|
const { publicApiUrl } = getEnvUrls(this.host.env);
|
133
133
|
const { client, tab, request, visitor } = this._getUids();
|
134
134
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
135
|
+
if (this.host.enableTracking) {
|
136
|
+
navigator.sendBeacon(
|
137
|
+
`${publicApiUrl}/api/events`,
|
138
|
+
JSON.stringify({
|
139
|
+
event,
|
140
|
+
payload,
|
141
|
+
context: {
|
142
|
+
'x-uid-client': client,
|
143
|
+
'x-uid-tab': tab,
|
144
|
+
'x-uid-request': request,
|
145
|
+
'x-uid-visitor': visitor,
|
146
|
+
'x-reporting-metadata': this._getReportingMetadata()
|
147
|
+
}
|
148
|
+
})
|
149
|
+
);
|
150
|
+
}
|
150
151
|
};
|
151
152
|
|
152
153
|
private _handleVouchLoaded = ({ detail: vouchId }: CustomEvent<string>) => {
|
@@ -182,6 +183,7 @@ class TrackingController implements ReactiveController {
|
|
182
183
|
|
183
184
|
private _handleVideoPlay = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
|
184
185
|
const vouchId = this._findVouchId();
|
186
|
+
|
185
187
|
if (!vouchId) {
|
186
188
|
return;
|
187
189
|
}
|
@@ -199,11 +201,14 @@ class TrackingController implements ReactiveController {
|
|
199
201
|
|
200
202
|
private _handleVideoTimeUpdate = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
|
201
203
|
const vouchId = this._findVouchId();
|
204
|
+
|
202
205
|
if (!vouchId) {
|
203
206
|
return;
|
204
207
|
}
|
205
208
|
const currentTimestamp = Date.now();
|
206
209
|
if (
|
210
|
+
node.currentTime &&
|
211
|
+
!node.paused &&
|
207
212
|
!this.host.paused &&
|
208
213
|
// Only fire the video seeked event when this video is the active one
|
209
214
|
id === this.host.scene?.video?.id &&
|
@@ -223,18 +228,23 @@ class TrackingController implements ReactiveController {
|
|
223
228
|
|
224
229
|
private _handleVideoPause = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
|
225
230
|
const vouchId = this._findVouchId();
|
231
|
+
|
226
232
|
if (!vouchId) {
|
227
233
|
return;
|
228
234
|
}
|
229
|
-
|
230
|
-
//
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
235
|
+
|
236
|
+
// Don't send a tracking event if the video pauses when seeking backwards
|
237
|
+
if (node.currentTime > this._streamedTime[id]) {
|
238
|
+
// Send a video streamed event any time the video pauses then reset the streamed state
|
239
|
+
// We do this to capture the last bit of time that the video was played between the previous
|
240
|
+
// stream event and the video being paused manually or stopping because it ended
|
241
|
+
this._sendTrackingEvent('VIDEO_STREAMED', {
|
242
|
+
vouchId,
|
243
|
+
answerId: id,
|
244
|
+
streamStart: this._streamedTime[id],
|
245
|
+
streamEnd: node.currentTime
|
246
|
+
});
|
247
|
+
}
|
238
248
|
delete this._streamedTime[id];
|
239
249
|
delete this._streamedPrevTimestamp[id];
|
240
250
|
};
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { html, LitElement } from 'lit';
|
2
|
-
import { customElement, property } from 'lit/decorators.js';
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
3
3
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
4
4
|
import { createRef, ref } from 'lit/directives/ref.js';
|
5
5
|
|
@@ -17,6 +17,8 @@ import '@vouchfor/media-player';
|
|
17
17
|
type EmbedProps = Pick<MediaPlayerProps, 'data' | 'aspectRatio' | 'preload' | 'autoplay' | 'controls'> & {
|
18
18
|
env: Environment;
|
19
19
|
apiKey: string;
|
20
|
+
enableTracking?: boolean;
|
21
|
+
trackingSource?: string;
|
20
22
|
vouchId?: string;
|
21
23
|
templateId?: string;
|
22
24
|
};
|
@@ -31,6 +33,8 @@ class Embed extends LitElement {
|
|
31
33
|
|
32
34
|
@property({ type: String }) env: EmbedProps['env'] = 'prod';
|
33
35
|
@property({ type: String }) apiKey: EmbedProps['apiKey'] = '';
|
36
|
+
@property({ type: Boolean }) enableTracking: EmbedProps['enableTracking'] = true;
|
37
|
+
@property({ type: String }) trackingSource: EmbedProps['trackingSource'] = 'embed';
|
34
38
|
|
35
39
|
@property({ type: Array }) controls: EmbedProps['controls'];
|
36
40
|
@property({ type: String }) preload: EmbedProps['preload'] = 'auto';
|
@@ -69,13 +73,8 @@ class Embed extends LitElement {
|
|
69
73
|
// @ts-ignore
|
70
74
|
private _trackingController = new TrackingController(this);
|
71
75
|
|
72
|
-
|
73
|
-
|
74
|
-
}
|
75
|
-
|
76
|
-
get template(): TemplateInstance | undefined {
|
77
|
-
return this._fetcherController.template;
|
78
|
-
}
|
76
|
+
@state() vouch: EmbedProps['data'];
|
77
|
+
@state() template: TemplateInstance | undefined;
|
79
78
|
|
80
79
|
get fetching() {
|
81
80
|
return this._fetcherController.fetching;
|
package/src/utils/env.ts
CHANGED
@@ -1,15 +1,11 @@
|
|
1
|
-
type Environment = 'dev' | 'staging' | 'prod';
|
1
|
+
type Environment = 'local' | 'dev' | 'staging' | 'prod';
|
2
2
|
|
3
3
|
type GetEnvUrlsReturn = {
|
4
|
-
marketingUrl: string;
|
5
4
|
videoUrl: string;
|
6
5
|
publicApiUrl: string;
|
7
6
|
embedApiUrl: string;
|
8
|
-
publicRecorderUrl: string;
|
9
7
|
};
|
10
8
|
|
11
|
-
const marketingUrl = 'https://vouchfor.com';
|
12
|
-
|
13
9
|
const devVideoUrl = 'https://d2rxhdlm2q91uk.cloudfront.net';
|
14
10
|
const stagingVideoUrl = 'https://d1ix11aj5kfygl.cloudfront.net';
|
15
11
|
const prodVideoUrl = 'https://d157jlwnudd93d.cloudfront.net';
|
@@ -18,61 +14,51 @@ const devPublicApiUrl = 'https://bshyfw4h5a.execute-api.ap-southeast-2.amazonaws
|
|
18
14
|
const stagingPublicApiUrl = 'https://gyzw7rpbq3.execute-api.ap-southeast-2.amazonaws.com/staging';
|
19
15
|
const prodPublicApiUrl = 'https://vfcjuim1l3.execute-api.ap-southeast-2.amazonaws.com/prod';
|
20
16
|
|
21
|
-
const
|
22
|
-
const
|
23
|
-
const
|
24
|
-
|
25
|
-
const devPublicRecorderUrl = 'https://dev.vouchfor.com';
|
26
|
-
const stagingPublicRecorderUrl = 'https://staging.vouchfor.com';
|
27
|
-
const prodPublicRecorderUrl = 'https://app.vouchfor.com';
|
17
|
+
const localEmbedApiUrl = 'http://localhost:6060/v2';
|
18
|
+
const devEmbedApiUrl = 'https://embed-dev.vouchfor.com/v2';
|
19
|
+
const stagingEmbedApiUrl = 'https://embed-staging.vouchfor.com/v2';
|
20
|
+
const prodEmbedApiUrl = 'https://embed.vouchfor.com/v2';
|
28
21
|
|
29
22
|
// We are handling the case where env is an unknown string so the ts error is a lie
|
30
23
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
31
24
|
// @ts-ignore
|
32
25
|
function getEnvUrls(env: Environment): GetEnvUrlsReturn {
|
33
|
-
if (!['dev', 'staging', 'prod'].includes(env)) {
|
26
|
+
if (!['local', 'dev', 'staging', 'prod'].includes(env)) {
|
34
27
|
throw new Error(`Unknown environment: ${env}`);
|
35
28
|
}
|
36
29
|
|
30
|
+
if (env === 'local') {
|
31
|
+
return {
|
32
|
+
videoUrl: devVideoUrl,
|
33
|
+
publicApiUrl: devPublicApiUrl,
|
34
|
+
embedApiUrl: localEmbedApiUrl
|
35
|
+
};
|
36
|
+
}
|
37
|
+
|
37
38
|
if (env === 'dev') {
|
38
39
|
return {
|
39
|
-
marketingUrl,
|
40
40
|
videoUrl: devVideoUrl,
|
41
41
|
publicApiUrl: devPublicApiUrl,
|
42
|
-
embedApiUrl: devEmbedApiUrl
|
43
|
-
publicRecorderUrl: devPublicRecorderUrl
|
42
|
+
embedApiUrl: devEmbedApiUrl
|
44
43
|
};
|
45
44
|
}
|
46
45
|
|
47
46
|
if (env === 'staging') {
|
48
47
|
return {
|
49
|
-
marketingUrl,
|
50
48
|
videoUrl: stagingVideoUrl,
|
51
49
|
publicApiUrl: stagingPublicApiUrl,
|
52
|
-
embedApiUrl: stagingEmbedApiUrl
|
53
|
-
publicRecorderUrl: stagingPublicRecorderUrl
|
50
|
+
embedApiUrl: stagingEmbedApiUrl
|
54
51
|
};
|
55
52
|
}
|
56
53
|
|
57
54
|
if (env === 'prod') {
|
58
55
|
return {
|
59
|
-
marketingUrl,
|
60
56
|
videoUrl: prodVideoUrl,
|
61
57
|
publicApiUrl: prodPublicApiUrl,
|
62
|
-
embedApiUrl: prodEmbedApiUrl
|
63
|
-
publicRecorderUrl: prodPublicRecorderUrl
|
58
|
+
embedApiUrl: prodEmbedApiUrl
|
64
59
|
};
|
65
60
|
}
|
66
61
|
}
|
67
62
|
|
68
|
-
export {
|
69
|
-
marketingUrl,
|
70
|
-
devEmbedApiUrl,
|
71
|
-
stagingEmbedApiUrl,
|
72
|
-
prodEmbedApiUrl,
|
73
|
-
devPublicRecorderUrl,
|
74
|
-
stagingPublicRecorderUrl,
|
75
|
-
prodPublicRecorderUrl,
|
76
|
-
getEnvUrls
|
77
|
-
};
|
63
|
+
export { devEmbedApiUrl, stagingEmbedApiUrl, prodEmbedApiUrl, getEnvUrls };
|
78
64
|
export type { Environment };
|
@@ -1,14 +0,0 @@
|
|
1
|
-
import type { Embed } from '..';
|
2
|
-
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
3
|
-
type EmbedHost = ReactiveControllerHost & Embed;
|
4
|
-
declare class EventForwardController implements ReactiveController {
|
5
|
-
host: EmbedHost;
|
6
|
-
private _events;
|
7
|
-
private _cleanup;
|
8
|
-
private _forwardElementRef;
|
9
|
-
constructor(host: EmbedHost, events: string[]);
|
10
|
-
register(): import("lit-html/directive.js").DirectiveResult<typeof import("lit-html/directives/ref.js").RefDirective>;
|
11
|
-
hostConnected(): void;
|
12
|
-
hostDisconnected(): void;
|
13
|
-
}
|
14
|
-
export { EventForwardController };
|