@nuxt/scripts 0.3.2 → 0.3.4
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/client/200.html +5 -5
- package/dist/client/404.html +5 -5
- package/dist/client/_nuxt/{5Hk8ESNW.js → B4lN06_-.js} +1 -1
- package/dist/client/_nuxt/{TwOttoDL.js → Bcyt2NVQ.js} +1 -1
- package/dist/client/_nuxt/{DeEC_XNp.js → DaX43REz.js} +1 -1
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/d2381002-a578-4bfe-a659-00ae20f09aa2.json +1 -0
- package/dist/client/_nuxt/oEMCrPza.js +31 -0
- package/dist/client/index.html +5 -5
- package/dist/module.d.mts +2 -2
- package/dist/module.d.ts +2 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +60 -32
- package/dist/registry.d.mts +2 -2
- package/dist/registry.d.ts +2 -2
- package/dist/registry.mjs +16 -16
- package/dist/runtime/components/ScriptGoogleMaps.vue +264 -0
- package/dist/runtime/components/ScriptLemonSqueezyButton.vue +42 -0
- package/dist/runtime/components/ScriptLoadingIndicator.vue +15 -0
- package/dist/runtime/components/ScriptStripePricingTable.vue +47 -0
- package/dist/runtime/components/ScriptVimeoPlayer.vue +243 -0
- package/dist/runtime/components/ScriptYouTubePlayer.vue +160 -0
- package/dist/runtime/composables/useElementScriptTrigger.d.ts +3 -3
- package/dist/runtime/composables/useElementScriptTrigger.mjs +41 -5
- package/dist/runtime/composables/useScript.mjs +6 -4
- package/dist/runtime/registry/cloudflare-web-analytics.mjs +2 -2
- package/dist/runtime/registry/fathom-analytics.d.ts +2 -2
- package/dist/runtime/registry/fathom-analytics.mjs +2 -2
- package/dist/runtime/registry/google-analytics.d.ts +4 -2
- package/dist/runtime/registry/google-analytics.mjs +13 -8
- package/dist/runtime/registry/google-maps.mjs +2 -2
- package/dist/runtime/registry/google-tag-manager.mjs +2 -2
- package/dist/runtime/registry/hotjar.mjs +4 -4
- package/dist/runtime/registry/intercom.mjs +3 -8
- package/dist/runtime/registry/lemon-squeezy.d.ts +1 -2
- package/dist/runtime/registry/lemon-squeezy.mjs +3 -6
- package/dist/runtime/registry/matomo-analytics.mjs +11 -10
- package/dist/runtime/registry/{facebook-pixel.d.ts → meta-pixel.d.ts} +7 -7
- package/dist/runtime/registry/{facebook-pixel.mjs → meta-pixel.mjs} +9 -7
- package/dist/runtime/registry/npm.mjs +2 -2
- package/dist/runtime/registry/plausible-analytics.mjs +2 -2
- package/dist/runtime/registry/segment.d.ts +22 -9
- package/dist/runtime/registry/segment.mjs +43 -21
- package/dist/runtime/registry/stripe.mjs +4 -4
- package/dist/runtime/registry/vimeo-player.d.ts +6 -6
- package/dist/runtime/registry/vimeo-player.mjs +30 -34
- package/dist/runtime/registry/x-pixel.mjs +17 -13
- package/dist/runtime/registry/youtube-player.d.ts +14 -0
- package/dist/runtime/registry/{youtube-iframe.mjs → youtube-player.mjs} +22 -20
- package/dist/runtime/types.d.ts +33 -16
- package/dist/runtime/types.mjs +2 -0
- package/dist/runtime/utils.d.ts +3 -2
- package/dist/runtime/utils.mjs +15 -13
- package/package.json +6 -6
- package/dist/client/_nuxt/BxcSmj5a.js +0 -31
- package/dist/client/_nuxt/builds/meta/d293267d-837e-425a-bc4b-81773905567b.json +0 -1
- package/dist/runtime/components/GoogleMaps.vue +0 -130
- package/dist/runtime/components/LemonSqueezyButton.vue +0 -28
- package/dist/runtime/components/StripePricingTableEmbed.vue +0 -33
- package/dist/runtime/components/VimeoEmbed.vue +0 -161
- package/dist/runtime/components/YouTubeEmbed.vue +0 -79
- package/dist/runtime/registry/youtube-iframe.d.ts +0 -15
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
/// <reference types="google.maps" />
|
|
3
|
+
import {
|
|
4
|
+
type HTMLAttributes,
|
|
5
|
+
type ImgHTMLAttributes,
|
|
6
|
+
type Ref,
|
|
7
|
+
type ReservedProps,
|
|
8
|
+
computed,
|
|
9
|
+
onBeforeUnmount,
|
|
10
|
+
onMounted,
|
|
11
|
+
ref,
|
|
12
|
+
watch,
|
|
13
|
+
} from 'vue'
|
|
14
|
+
import { type QueryObject, withQuery } from 'ufo'
|
|
15
|
+
import { defu } from 'defu'
|
|
16
|
+
import type { ElementScriptTrigger } from '../composables/useElementScriptTrigger'
|
|
17
|
+
import { scriptRuntimeConfig } from '../utils'
|
|
18
|
+
import { resolveComponent, useElementScriptTrigger, useHead, useScriptGoogleMaps } from '#imports'
|
|
19
|
+
|
|
20
|
+
interface PlaceholderOptions {
|
|
21
|
+
width?: string | number
|
|
22
|
+
height?: string | number
|
|
23
|
+
center?: string
|
|
24
|
+
zoom?: number
|
|
25
|
+
size?: string
|
|
26
|
+
scale?: number
|
|
27
|
+
format?: 'png' | 'jpg' | 'gif' | 'png8' | 'png32' | 'jpg-baseline'
|
|
28
|
+
maptype?: 'roadmap' | 'satellite' | 'terrain' | 'hybrid'
|
|
29
|
+
language?: string
|
|
30
|
+
region?: string
|
|
31
|
+
markers?: string
|
|
32
|
+
path?: string
|
|
33
|
+
visible?: string
|
|
34
|
+
style?: string
|
|
35
|
+
map_id?: string
|
|
36
|
+
key?: string
|
|
37
|
+
signature?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const props = withDefaults(defineProps<{
|
|
41
|
+
/**
|
|
42
|
+
* Defines the trigger event to load the script.
|
|
43
|
+
*/
|
|
44
|
+
trigger?: ElementScriptTrigger
|
|
45
|
+
/**
|
|
46
|
+
* Is Google Maps being rendered above the fold?
|
|
47
|
+
* This will load the placeholder image with higher priority.
|
|
48
|
+
*/
|
|
49
|
+
aboveTheFold?: boolean
|
|
50
|
+
/**
|
|
51
|
+
* Defines the Google Maps API key. Must have access to the Static Maps API as well.
|
|
52
|
+
*/
|
|
53
|
+
apiKey: string
|
|
54
|
+
/**
|
|
55
|
+
* Defines map marker location.
|
|
56
|
+
*/
|
|
57
|
+
query?: string
|
|
58
|
+
/**
|
|
59
|
+
* Options for the map.
|
|
60
|
+
*/
|
|
61
|
+
options?: google.maps.MapOptions
|
|
62
|
+
/**
|
|
63
|
+
* Defines the width of the map.
|
|
64
|
+
*/
|
|
65
|
+
width?: number | string
|
|
66
|
+
/**
|
|
67
|
+
* Defines the height of the map
|
|
68
|
+
*/
|
|
69
|
+
height?: number | string
|
|
70
|
+
/**
|
|
71
|
+
* Customize the placeholder image attributes.
|
|
72
|
+
*
|
|
73
|
+
* @see https://developers.google.com/maps/documentation/maps-static/start.
|
|
74
|
+
*/
|
|
75
|
+
placeholderOptions?: PlaceholderOptions
|
|
76
|
+
/**
|
|
77
|
+
* Customize the placeholder image attributes.
|
|
78
|
+
*/
|
|
79
|
+
placeholderAttrs?: ImgHTMLAttributes & ReservedProps & Record<string, unknown>
|
|
80
|
+
/**
|
|
81
|
+
* Customize the root element attributes.
|
|
82
|
+
*/
|
|
83
|
+
rootAttrs?: HTMLAttributes & ReservedProps & Record<string, unknown>
|
|
84
|
+
}>(), {
|
|
85
|
+
// @ts-expect-error untyped
|
|
86
|
+
trigger: ['mouseenter', 'mouseover', 'mousedown'],
|
|
87
|
+
width: 600,
|
|
88
|
+
height: 400,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const emits = defineEmits<{
|
|
92
|
+
// our emit
|
|
93
|
+
ready: [e: Ref<google.maps.Map | undefined>]
|
|
94
|
+
}>()
|
|
95
|
+
|
|
96
|
+
const apiKey = props.apiKey || scriptRuntimeConfig('googleMaps')?.apiKey
|
|
97
|
+
|
|
98
|
+
if (!apiKey)
|
|
99
|
+
throw new Error('GoogleMaps requires an API key. Please provide `apiKey` on the <ScriptGoogleMaps> or globally via `runtimeConfig.public.scripts.googleMaps.apiKey`.')
|
|
100
|
+
if (!props.query && !props.options?.center)
|
|
101
|
+
throw new Error('GoogleMaps requires either a query or center prop to be set.')
|
|
102
|
+
|
|
103
|
+
const rootEl = ref<HTMLElement>()
|
|
104
|
+
const mapEl = ref<HTMLElement>()
|
|
105
|
+
|
|
106
|
+
const { $script } = useScriptGoogleMaps({
|
|
107
|
+
apiKey: props.apiKey,
|
|
108
|
+
scriptOptions: {
|
|
109
|
+
trigger: useElementScriptTrigger({ trigger: props.trigger, el: rootEl }),
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const options = computed(() => {
|
|
114
|
+
return defu(props.options, {
|
|
115
|
+
zoom: 15,
|
|
116
|
+
mapId: 'map',
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
const ready = ref(false)
|
|
120
|
+
|
|
121
|
+
function queryToLocation(service: google.maps.places.PlacesService, query: string) {
|
|
122
|
+
return new Promise<google.maps.LatLng>((resolve, reject) => {
|
|
123
|
+
service.findPlaceFromQuery({
|
|
124
|
+
query,
|
|
125
|
+
fields: ['name', 'geometry'],
|
|
126
|
+
}, (results, status) => {
|
|
127
|
+
if (status === 'OK' && results?.[0]?.geometry?.location)
|
|
128
|
+
return resolve(results[0].geometry.location)
|
|
129
|
+
return reject(new Error(`No location found for ${query}`))
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const map: Ref<google.maps.Map | undefined> = ref()
|
|
135
|
+
const markers: Ref<google.maps.marker.AdvancedMarkerElement[]> = ref([])
|
|
136
|
+
defineExpose({
|
|
137
|
+
map,
|
|
138
|
+
markers,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
onMounted(() => {
|
|
142
|
+
watch(ready, (v) => {
|
|
143
|
+
v && emits('ready', map)
|
|
144
|
+
})
|
|
145
|
+
// create the map
|
|
146
|
+
$script.then(async (instance) => {
|
|
147
|
+
const maps = await instance.maps as typeof google.maps
|
|
148
|
+
const _map = new maps.Map(mapEl.value!, options.value)
|
|
149
|
+
const placesService = new maps.places.PlacesService(_map)
|
|
150
|
+
|
|
151
|
+
watch(options, () => _map.setOptions(options.value))
|
|
152
|
+
watch(() => props.query, async (query) => {
|
|
153
|
+
// always clear old markers
|
|
154
|
+
markers.value.forEach(marker => marker.remove())
|
|
155
|
+
markers.value = []
|
|
156
|
+
if (query) {
|
|
157
|
+
const marker = await maps.importLibrary('marker') as google.maps.MarkerLibrary
|
|
158
|
+
const location = await queryToLocation(placesService, query).catch((err) => {
|
|
159
|
+
console.warn(err)
|
|
160
|
+
return {
|
|
161
|
+
lat: 0,
|
|
162
|
+
lng: 0,
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
_map.setCenter(location)
|
|
166
|
+
markers.value.push(new marker.AdvancedMarkerElement({
|
|
167
|
+
map: _map,
|
|
168
|
+
position: location,
|
|
169
|
+
}))
|
|
170
|
+
ready.value = true
|
|
171
|
+
}
|
|
172
|
+
}, {
|
|
173
|
+
immediate: !!props.query,
|
|
174
|
+
})
|
|
175
|
+
if (!props.query)
|
|
176
|
+
ready.value = true
|
|
177
|
+
map.value = _map
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
if (import.meta.server) {
|
|
182
|
+
useHead({
|
|
183
|
+
link: [
|
|
184
|
+
{
|
|
185
|
+
rel: props.aboveTheFold ? 'preconnect' : 'dns-prefetch',
|
|
186
|
+
href: 'https://maps.googleapis.com',
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const placeholder = computed(() => {
|
|
193
|
+
const placeholderOptions: PlaceholderOptions = defu(props.placeholderOptions, {
|
|
194
|
+
// only map option values
|
|
195
|
+
zoom: options.value.zoom,
|
|
196
|
+
center: options.value.center?.toString() || '',
|
|
197
|
+
}, {
|
|
198
|
+
size: `${props.width}x${props.height}`,
|
|
199
|
+
key: apiKey,
|
|
200
|
+
scale: 2, // we assume a high DPI to avoid hydration issues
|
|
201
|
+
markers: `color:red|${props.query}`,
|
|
202
|
+
})
|
|
203
|
+
return withQuery('https://maps.googleapis.com/maps/api/staticmap', placeholderOptions as QueryObject)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const placeholderAttrs = computed(() => {
|
|
207
|
+
return defu(props.placeholderAttrs, {
|
|
208
|
+
src: placeholder.value,
|
|
209
|
+
alt: props.query || '',
|
|
210
|
+
loading: props.aboveTheFold ? 'eager' : 'lazy',
|
|
211
|
+
style: {
|
|
212
|
+
cursor: 'pointer',
|
|
213
|
+
width: '100%',
|
|
214
|
+
objectFit: 'cover',
|
|
215
|
+
height: '100%',
|
|
216
|
+
},
|
|
217
|
+
} satisfies ImgHTMLAttributes)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
const rootAttrs = computed(() => {
|
|
221
|
+
return defu(props.rootAttrs, {
|
|
222
|
+
'aria-busy': $script.status.value === 'loading',
|
|
223
|
+
'aria-label': $script.status.value === 'awaitingLoad'
|
|
224
|
+
? 'Google Maps Static Map'
|
|
225
|
+
: $script.status.value === 'loading'
|
|
226
|
+
? 'Google Maps Map Embed Loading'
|
|
227
|
+
: 'Google Maps Embed',
|
|
228
|
+
'aria-live': 'polite',
|
|
229
|
+
'role': 'application',
|
|
230
|
+
'style': {
|
|
231
|
+
cursor: 'pointer',
|
|
232
|
+
position: 'relative',
|
|
233
|
+
maxWidth: '100%',
|
|
234
|
+
width: `${props.width}px`,
|
|
235
|
+
height: `'auto'`,
|
|
236
|
+
aspectRatio: `${props.width}/${props.height}`,
|
|
237
|
+
},
|
|
238
|
+
}) as HTMLAttributes
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
const ScriptLoadingIndicator = resolveComponent('ScriptLoadingIndicator')
|
|
242
|
+
|
|
243
|
+
onBeforeUnmount(() => {
|
|
244
|
+
markers.value.forEach(marker => marker.remove())
|
|
245
|
+
markers.value = []
|
|
246
|
+
map.value?.unbindAll()
|
|
247
|
+
map.value = undefined
|
|
248
|
+
mapEl.value?.firstChild?.remove()
|
|
249
|
+
})
|
|
250
|
+
</script>
|
|
251
|
+
|
|
252
|
+
<template>
|
|
253
|
+
<div ref="rootEl" v-bind="rootAttrs">
|
|
254
|
+
<div v-show="ready" ref="mapEl" class="script-google-maps__map" :style="{ width: '100%', height: '100%', maxWidth: '100%' }" />
|
|
255
|
+
<slot v-if="!ready" :placeholder="placeholder" name="placeholder">
|
|
256
|
+
<img v-bind="placeholderAttrs">
|
|
257
|
+
</slot>
|
|
258
|
+
<slot v-if="$script.status.value === 'loading'" name="loading">
|
|
259
|
+
<ScriptLoadingIndicator color="black" />
|
|
260
|
+
</slot>
|
|
261
|
+
<slot v-if="$script.status.value === 'awaitingLoad'" name="awaitingLoad" />
|
|
262
|
+
<slot />
|
|
263
|
+
</div>
|
|
264
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { type ElementScriptTrigger, onMounted, ref, useElementScriptTrigger, useScriptLemonSqueezy } from '#imports'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<{
|
|
5
|
+
trigger?: ElementScriptTrigger
|
|
6
|
+
href: string
|
|
7
|
+
}>(), {
|
|
8
|
+
trigger: 'visible',
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const emits = defineEmits<{
|
|
12
|
+
event: [{ event: string, data?: Record<string, any> }]
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const rootEl = ref<HTMLElement | null>(null)
|
|
16
|
+
const { $script } = useScriptLemonSqueezy({
|
|
17
|
+
scriptOptions: {
|
|
18
|
+
trigger: useElementScriptTrigger({ trigger: props.trigger, el: rootEl }),
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
onMounted(() => {
|
|
22
|
+
// @ts-expect-error untyped
|
|
23
|
+
$script.then(({ Setup }) => {
|
|
24
|
+
Setup({
|
|
25
|
+
// @ts-expect-error untyped
|
|
26
|
+
eventHandler(event) {
|
|
27
|
+
emits('event', event)
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div ref="rootEl">
|
|
36
|
+
<slot v-bind="{ class: 'lemonsqueezy-button', href }">
|
|
37
|
+
<a :href="href" class="lemonsqueezy-button">
|
|
38
|
+
Buy me
|
|
39
|
+
</a>
|
|
40
|
+
</slot>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
withDefaults(defineProps<{
|
|
3
|
+
color?: string
|
|
4
|
+
}>(), {
|
|
5
|
+
color: 'currentColor',
|
|
6
|
+
})
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<div class="loader" aria-label="Loading..." role="status" />
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<style scoped>
|
|
14
|
+
.loader{animation:rotation 1s linear infinite;border:5px solid v-bind(color);border-bottom-color:transparent;border-radius:50%;bottom:12px;box-sizing:border-box;display:inline-block;height:30px;left:12px;opacity:.5;position:absolute;width:30px}@keyframes rotation{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}
|
|
15
|
+
</style>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import type { ElementScriptTrigger } from '../composables/useElementScriptTrigger'
|
|
4
|
+
import { useElementScriptTrigger, useScript } from '#imports'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<{
|
|
7
|
+
trigger?: ElementScriptTrigger
|
|
8
|
+
publishableKey: string
|
|
9
|
+
pricingTableId: string
|
|
10
|
+
clientReferenceId?: string
|
|
11
|
+
customerEmail?: string
|
|
12
|
+
customerSessionClientSecret?: string
|
|
13
|
+
}>(), {
|
|
14
|
+
trigger: 'visible',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const emit = defineEmits<{
|
|
18
|
+
ready: []
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const rootEl = ref()
|
|
22
|
+
const { $script } = useScript(`https://js.stripe.com/v3/pricing-table.js`, {
|
|
23
|
+
trigger: useElementScriptTrigger({ trigger: props.trigger, el: rootEl }),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
$script.then(() => {
|
|
27
|
+
emit('ready')
|
|
28
|
+
})
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<div ref="rootEl">
|
|
33
|
+
<ClientOnly>
|
|
34
|
+
<stripe-pricing-table
|
|
35
|
+
v-bind="$attrs"
|
|
36
|
+
:publishable-key="publishableKey"
|
|
37
|
+
:pricing-table-id="pricingTableId"
|
|
38
|
+
:client-reference-id="clientReferenceId"
|
|
39
|
+
:customer-email="customerEmail"
|
|
40
|
+
:customer-session-client-secret="customerSessionClientSecret"
|
|
41
|
+
/>
|
|
42
|
+
</ClientOnly>
|
|
43
|
+
<slot v-if="$script.status.value === 'loading'" name="loading" />
|
|
44
|
+
<slot v-if="$script.status.value === 'awaitingLoad'" name="awaitingLoad" />
|
|
45
|
+
<slot />
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type HTMLAttributes, type ImgHTMLAttributes, computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
3
|
+
import type Player from 'vimeo__player'
|
|
4
|
+
import type { EventMap, VimeoVideoQuality } from 'vimeo__player'
|
|
5
|
+
import { defu } from 'defu'
|
|
6
|
+
import type { ElementScriptTrigger } from '../composables/useElementScriptTrigger'
|
|
7
|
+
import { useAsyncData, useElementScriptTrigger, useHead, useScriptVimeoPlayer } from '#imports'
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(defineProps<{
|
|
10
|
+
// custom
|
|
11
|
+
trigger?: ElementScriptTrigger
|
|
12
|
+
placeholderAttrs?: ImgHTMLAttributes
|
|
13
|
+
rootAttrs?: HTMLAttributes
|
|
14
|
+
aboveTheFold?: boolean
|
|
15
|
+
// copied from @types/vimeo__player
|
|
16
|
+
id: string | number | undefined
|
|
17
|
+
url?: string | undefined
|
|
18
|
+
autopause?: boolean | undefined
|
|
19
|
+
autoplay?: boolean | undefined
|
|
20
|
+
background?: boolean | undefined
|
|
21
|
+
byline?: boolean | undefined
|
|
22
|
+
color?: string | undefined
|
|
23
|
+
controls?: boolean | undefined
|
|
24
|
+
dnt?: boolean | undefined
|
|
25
|
+
height?: number | undefined
|
|
26
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
27
|
+
interactive_params?: string | undefined
|
|
28
|
+
keyboard?: boolean | undefined
|
|
29
|
+
loop?: boolean | undefined
|
|
30
|
+
maxheight?: number | undefined
|
|
31
|
+
maxwidth?: number | undefined
|
|
32
|
+
muted?: boolean | undefined
|
|
33
|
+
pip?: boolean | undefined
|
|
34
|
+
playsinline?: boolean | undefined
|
|
35
|
+
portrait?: boolean | undefined
|
|
36
|
+
responsive?: boolean | undefined
|
|
37
|
+
speed?: boolean | undefined
|
|
38
|
+
quality?: VimeoVideoQuality | undefined
|
|
39
|
+
texttrack?: string | undefined
|
|
40
|
+
title?: boolean | undefined
|
|
41
|
+
transparent?: boolean | undefined
|
|
42
|
+
width?: number | undefined
|
|
43
|
+
}>(), {
|
|
44
|
+
trigger: 'mousedown',
|
|
45
|
+
width: 640,
|
|
46
|
+
height: 480,
|
|
47
|
+
loop: false,
|
|
48
|
+
controls: true,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const emits = defineEmits<{
|
|
52
|
+
play: [e: EventMap['play'], player: Player]
|
|
53
|
+
playing: [e: EventMap['playing'], player: Player]
|
|
54
|
+
pause: [e: EventMap['pause'], player: Player]
|
|
55
|
+
ended: [e: EventMap['ended'], player: Player]
|
|
56
|
+
timeupdate: [e: EventMap['timeupdate'], player: Player]
|
|
57
|
+
progress: [e: EventMap['progress'], player: Player]
|
|
58
|
+
seeking: [e: EventMap['seeking'], player: Player]
|
|
59
|
+
seeked: [e: EventMap['seeked'], player: Player]
|
|
60
|
+
texttrackchange: [e: EventMap['texttrackchange'], player: Player]
|
|
61
|
+
chapterchange: [e: EventMap['chapterchange'], player: Player]
|
|
62
|
+
cuechange: [e: EventMap['cuechange'], player: Player]
|
|
63
|
+
cuepoint: [e: EventMap['cuepoint'], player: Player]
|
|
64
|
+
volumechange: [e: EventMap['volumechange'], player: Player]
|
|
65
|
+
playbackratechange: [e: EventMap['playbackratechange'], player: Player]
|
|
66
|
+
bufferstart: [e: EventMap['bufferstart'], player: Player]
|
|
67
|
+
bufferend: [e: EventMap['bufferend'], player: Player]
|
|
68
|
+
error: [e: EventMap['error'], player: Player]
|
|
69
|
+
loaded: [e: EventMap['loaded'], player: Player]
|
|
70
|
+
durationchange: [e: EventMap['durationchange'], player: Player]
|
|
71
|
+
fullscreenchange: [e: EventMap['fullscreenchange'], player: Player]
|
|
72
|
+
qualitychange: [e: EventMap['qualitychange'], player: Player]
|
|
73
|
+
camerachange: [e: EventMap['camerachange'], player: Player]
|
|
74
|
+
resize: [e: EventMap['resize'], player: Player]
|
|
75
|
+
enterpictureinpicture: [e: EventMap['enterpictureinpicture'], player: Player]
|
|
76
|
+
leavepictureinpicture: [e: EventMap['leavepictureinpicture'], player: Player]
|
|
77
|
+
}>()
|
|
78
|
+
|
|
79
|
+
const events = [
|
|
80
|
+
'play',
|
|
81
|
+
'playing',
|
|
82
|
+
'pause',
|
|
83
|
+
'ended',
|
|
84
|
+
'timeupdate',
|
|
85
|
+
'progress',
|
|
86
|
+
'seeking',
|
|
87
|
+
'seeked',
|
|
88
|
+
'texttrackchange',
|
|
89
|
+
'chapterchange',
|
|
90
|
+
'cuechange',
|
|
91
|
+
'cuepoint',
|
|
92
|
+
'volumechange',
|
|
93
|
+
'playbackratechange',
|
|
94
|
+
'bufferstart',
|
|
95
|
+
'bufferend',
|
|
96
|
+
'error',
|
|
97
|
+
'loaded',
|
|
98
|
+
'durationchange',
|
|
99
|
+
'fullscreenchange',
|
|
100
|
+
'qualitychange',
|
|
101
|
+
'camerachange',
|
|
102
|
+
'resize',
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
const elVimeo = ref()
|
|
106
|
+
const rootEl = ref()
|
|
107
|
+
|
|
108
|
+
const trigger = useElementScriptTrigger({ trigger: props.trigger, el: rootEl })
|
|
109
|
+
let clickTriggered = false
|
|
110
|
+
if (props.trigger === 'mousedown') {
|
|
111
|
+
trigger.then(() => {
|
|
112
|
+
clickTriggered = true
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
const ready = ref(false)
|
|
116
|
+
const { $script } = useScriptVimeoPlayer({
|
|
117
|
+
scriptOptions: {
|
|
118
|
+
trigger,
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
if (import.meta.server) {
|
|
123
|
+
// dns-prefetch https://i.vimeocdn.com
|
|
124
|
+
useHead({
|
|
125
|
+
link: [
|
|
126
|
+
{
|
|
127
|
+
rel: props.aboveTheFold ? 'preconnect' : 'dns-prefetch',
|
|
128
|
+
href: 'https://i.vimeocdn.com',
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const { data: payload } = useAsyncData(
|
|
135
|
+
`vimeo-embed:${props.id}`,
|
|
136
|
+
// TODO ideally we cache this
|
|
137
|
+
() => $fetch(`https://vimeo.com/api/v2/video/${props.id}.json`).then(res => res[0]),
|
|
138
|
+
{
|
|
139
|
+
watch: [() => props.id],
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
const placeholder = computed(() => payload.value?.thumbnail_large)
|
|
144
|
+
|
|
145
|
+
let player: Player | undefined
|
|
146
|
+
// we can't directly expose the player as vue will break the proxy
|
|
147
|
+
defineExpose({
|
|
148
|
+
play: () => player?.play(),
|
|
149
|
+
pause: () => player?.pause(),
|
|
150
|
+
getDuration: () => player?.getDuration(),
|
|
151
|
+
getCurrentTime: () => player?.getCurrentTime(),
|
|
152
|
+
setCurrentTime: (time: number) => player?.setCurrentTime(time),
|
|
153
|
+
getVolume: () => player?.getVolume(),
|
|
154
|
+
setVolume: (volume: number) => player?.setVolume(volume),
|
|
155
|
+
getPaused: () => player?.getPaused(),
|
|
156
|
+
getEnded: () => player?.getEnded(),
|
|
157
|
+
getLoop: () => player?.getLoop(),
|
|
158
|
+
setLoop: (loop: boolean) => player?.setLoop(loop),
|
|
159
|
+
getPlaybackRate: () => player?.getPlaybackRate(),
|
|
160
|
+
setPlaybackRate: (rate: number) => player?.setPlaybackRate(rate),
|
|
161
|
+
})
|
|
162
|
+
onMounted(() => {
|
|
163
|
+
$script.then(async ({ Vimeo }) => {
|
|
164
|
+
// filter props for false values
|
|
165
|
+
player = new Vimeo.Player(elVimeo.value, {
|
|
166
|
+
...props,
|
|
167
|
+
url: encodeURI(`https://vimeo.com/${props.id}`),
|
|
168
|
+
})
|
|
169
|
+
if (clickTriggered) {
|
|
170
|
+
player!.play()
|
|
171
|
+
clickTriggered = false
|
|
172
|
+
}
|
|
173
|
+
for (const event of events) {
|
|
174
|
+
player!.on(event, (e) => {
|
|
175
|
+
emits(event, e, player)
|
|
176
|
+
if (event === 'loaded')
|
|
177
|
+
ready.value = true
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
watch(() => props.id, (v) => {
|
|
183
|
+
v && player?.loadVideo(Number(v))
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const rootAttrs = computed(() => {
|
|
188
|
+
return defu(props.rootAttrs, {
|
|
189
|
+
'aria-busy': $script.status.value === 'loading',
|
|
190
|
+
'aria-label': $script.status.value === 'awaitingLoad'
|
|
191
|
+
? 'Vimeo Player - Placeholder'
|
|
192
|
+
: $script.status.value === 'loading'
|
|
193
|
+
? 'Vimeo Player - Loading'
|
|
194
|
+
: 'Vimeo Player - Loaded',
|
|
195
|
+
'aria-live': 'polite',
|
|
196
|
+
'role': 'application',
|
|
197
|
+
'style': {
|
|
198
|
+
maxWidth: '100%',
|
|
199
|
+
width: `${props.width}px`,
|
|
200
|
+
height: `'auto'`,
|
|
201
|
+
aspectRatio: `${props.width}/${props.height}`,
|
|
202
|
+
position: 'relative',
|
|
203
|
+
backgroundColor: 'black',
|
|
204
|
+
},
|
|
205
|
+
}) as HTMLAttributes
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const placeholderAttrs = computed(() => {
|
|
209
|
+
return defu(props.placeholderAttrs, {
|
|
210
|
+
src: placeholder.value,
|
|
211
|
+
alt: '',
|
|
212
|
+
loading: props.aboveTheFold ? 'eager' : 'lazy',
|
|
213
|
+
// @ts-expect-error untyped
|
|
214
|
+
fetchpriority: props.aboveTheFold ? 'high' : undefined,
|
|
215
|
+
style: {
|
|
216
|
+
cursor: 'pointer',
|
|
217
|
+
width: '100%',
|
|
218
|
+
objectFit: 'contain',
|
|
219
|
+
height: '100%',
|
|
220
|
+
},
|
|
221
|
+
} satisfies ImgHTMLAttributes)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
onBeforeUnmount(() => player?.unload())
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
<template>
|
|
228
|
+
<div ref="rootEl" v-bind="rootAttrs">
|
|
229
|
+
<div v-show="ready" ref="elVimeo" class="vimeo-player" style="width: 100%; height: 100%; max-width: 100%;" />
|
|
230
|
+
<slot v-if="!ready" v-bind="payload" :placeholder="placeholder" name="placeholder">
|
|
231
|
+
<img v-if="placeholder" v-bind="placeholderAttrs">
|
|
232
|
+
</slot>
|
|
233
|
+
<slot v-if="$script.status.value === 'loading'" name="loading">
|
|
234
|
+
<ScriptLoadingIndicator color="white" />
|
|
235
|
+
</slot>
|
|
236
|
+
<slot v-if="$script.status.value === 'awaitingLoad'" name="awaitingLoad" />
|
|
237
|
+
<slot />
|
|
238
|
+
</div>
|
|
239
|
+
</template>
|
|
240
|
+
|
|
241
|
+
<style>
|
|
242
|
+
.vimeo-player iframe{max-width:100%!important}
|
|
243
|
+
</style>
|