diff --git a/.babelrc.js b/.babelrc.js
deleted file mode 100644
index 339063f7..00000000
--- a/.babelrc.js
+++ /dev/null
@@ -1,20 +0,0 @@
-module.exports = (api) => {
- api.cache.never();
-
- const envOptions = {
- modules: false,
- loose: true,
- };
-
- if (process.env.NODE_ENV === 'test') {
- envOptions.modules = 'commonjs';
- envOptions.targets = { node: 'current' };
- }
-
- return {
- presets: [
- ['@babel/env', envOptions],
- '@babel/react',
- ],
- };
-};
diff --git a/.browserslistrc b/.browserslistrc
new file mode 100644
index 00000000..e94f8140
--- /dev/null
+++ b/.browserslistrc
@@ -0,0 +1 @@
+defaults
diff --git a/.eslintrc.js b/.eslintrc.js
index 0d954cc6..b614cc4d 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,12 +1,11 @@
module.exports = {
extends: 'airbnb',
+ parserOptions: {
+ ecmaVersion: 2021,
+ },
rules: {
- // I disagree
- 'react/jsx-filename-extension': 'off',
// I disagree
'react/require-default-props': 'off',
- // Our babel config doesn't support class properties
- 'react/state-in-constructor': 'off',
// I disagree
'react/function-component-definition': ['error', {
namedComponents: 'function-declaration',
@@ -21,4 +20,20 @@ module.exports = {
allowChildren: false,
}],
},
+ overrides: [
+ {
+ files: ['**/*.ts', '**/*.tsx'],
+ extends: ['airbnb-typescript'],
+ parserOptions: {
+ project: './tsconfig.json',
+ },
+ },
+ {
+ files: ['example/**/*.ts', 'example/**/*.tsx'],
+ extends: ['airbnb-typescript'],
+ parserOptions: {
+ project: './example/tsconfig.json',
+ },
+ },
+ ],
};
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ce8a96f7..ebed69b6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,21 +3,6 @@ name: CI
on: [push, pull_request]
jobs:
- types:
- name: Types
- runs-on: ubuntu-latest
- steps:
- - name: Checkout sources
- uses: actions/checkout@v4
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version: lts/*
- - name: Install dependencies
- run: npm install
- - name: Check types
- run: npm run tsd
-
lint:
name: Code style
runs-on: ubuntu-latest
@@ -37,11 +22,8 @@ jobs:
name: Tests
strategy:
matrix:
- node-version: [16.x, 18.x, 20.x]
- react-version: [17.x, 18.x]
- include:
- - node-version: 12.x
- react-version: 16.0.0
+ node-version: [18.x, 20.x, 22.x]
+ react-version: [17.x, 18.x, 19.x]
runs-on: ubuntu-latest
steps:
- name: Checkout sources
@@ -55,8 +37,10 @@ jobs:
- name: Install React ${{matrix.react-version}}
if: matrix.react-version != '18.x'
run: |
- npm install --save-dev \
+ npm install --force --save-dev \
react@${{matrix.react-version}} \
react-dom@${{matrix.react-version}}
+ - name: Build module
+ run: npm run build
- name: Run tests
run: npm run tests-only
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 5fc77dda..fccd0e99 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -17,17 +17,16 @@ jobs:
run: npm install
- name: Run tests
run: npm test
+ - name: Build module
+ run: npm run build
- name: Build example
- run: |
- npm run --prefix example build
- mkdir _deploy
- cp example/bundle.js example/index.html _deploy
+ run: npm run --prefix example build
- name: Publish site
if: success()
uses: crazy-max/ghaction-github-pages@v4
with:
target_branch: gh-pages
- build_dir: _deploy
+ build_dir: example/dist
keep_history: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be14f8b0..e3d54a9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
+## 1.0.0-alpha.6 - 2024-12-26
+ * Allow React 19 in peer dependencies range.
+
+## 1.0.0-alpha.5 - 2024-12-26
+ * Use TypeScript for source code.
+ * Support React 19.
+
+## 1.0.0-alpha.4 - 2022-05-03
+ * Add docs for the `useYouTube` hook.
+ * Add props for `origin` / `host` settings.
+ * Pass-through `muted` to the player initially, so `` works as expected.
+
+## 1.0.0-alpha.3 - 2022-05-01
+ * Fix unmount order.
+ * Start player synchronously if the SDK is already loaded.
+
+## 1.0.0-alpha.2 - 2022-05-01
+ * Expose all functionality as a `useYouTube` hook.
+ * Remove props no longer supported by YouTube: `showInfo`, `suggestedQuality`.
+
+## 1.0.0-alpha.1 - 2022-04-20
+ * Improve typings.
+ * Remove duplicate `defaultProps`.
+
+## 1.0.0-alpha.0 - 2021-12-01
+ * Use hooks internally.
+ * Drop support for React 16. This version requires React 17 or 18.
+ * Target evergreen browsers. If you need to support older browsers, you need to transpile this dependency.
+
## 0.7.4 - 2022-04-23
* Fix a warning about workspaces when installing with yarn.
diff --git a/README.md b/README.md
index 20336912..74be86e9 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,14 @@
# @u-wave/react-youtube
-
YouTube player component for React.
-[Install][] - [Usage][] - [Demo][] - [Props][]
+[Install][] - [Usage][] - [Demo][] - [Component API][] - [Hook API][]
## Install
-
```
-npm install --save @u-wave/react-youtube
+npm install @u-wave/react-youtube
```
## Usage
-
[Demo][] - [Demo source code][]
```js
@@ -23,15 +20,23 @@ import YouTube from '@u-wave/react-youtube';
/>
```
-## Props
+
+## ``
+The `` component renders an iframe and attaches the YouTube player to it. It supports all the
+same options as the `useYouTube` hook, plus a few to configure the iframe. If you need to do more with
+the iframe than this component provides, consider using the `useYouTube` hook directly.
+
+### Props
| Name | Type | Default | Description |
|:-----|:-----|:-----|:-----|
-| video | string | | An 11-character string representing a YouTube video ID.. |
| id | string | | DOM ID for the player element. |
| className | string | | CSS className for the player element. |
| style | object | | Inline style for container element. |
+| video | string | | An 11-character string representing a YouTube video ID.. |
| width | number, string | | Width of the player element. |
| height | number, string | | Height of the player element. |
+| host | string | https://www.youtube.com | YouTube host to use: 'https://www.youtube.com' or 'https://www.youtube-nocookie.com'. |
+| origin | string | | The YouTube API will usually default this value correctly. It is exposed for completeness. https://developers.google.com/youtube/player_parameters#origin |
| paused | bool | | Pause the video. |
| autoplay | bool | false | Whether the video should start playing automatically. https://developers.google.com/youtube/player_parameters#autoplay |
| showCaptions | bool | false | Whether to show captions below the video. https://developers.google.com/youtube/player_parameters#cc_load_policy |
@@ -47,31 +52,84 @@ import YouTube from '@u-wave/react-youtube';
| showRelatedVideos | bool | true | Whether to show related videos after the video is over. https://developers.google.com/youtube/player_parameters#rel |
| volume | number | | The playback volume, **as a number between 0 and 1**. |
| muted | bool | | Whether the video's sound should be muted. |
-| suggestedQuality | string | | The suggested playback quality. https://developers.google.com/youtube/iframe_api_reference#Playback_quality |
| playbackRate | number | | Playback speed. https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate |
| onReady | function | | Sent when the YouTube player API has loaded. |
| onError | function | | Sent when the player triggers an error. |
-| onCued | function | () => {} | Sent when the video is cued and ready to play. |
-| onBuffering | function | () => {} | Sent when the video is buffering. |
-| onPlaying | function | () => {} | Sent when playback has been started or resumed. |
-| onPause | function | () => {} | Sent when playback has been paused. |
-| onEnd | function | () => {} | Sent when playback has stopped. |
+| onCued | function | | Sent when the video is cued and ready to play. |
+| onBuffering | function | | Sent when the video is buffering. |
+| onPlaying | function | | Sent when playback has been started or resumed. |
+| onPause | function | | Sent when playback has been paused. |
+| onEnd | function | | Sent when playback has stopped. |
| onStateChange | function | | |
| onPlaybackRateChange | function | | |
| onPlaybackQualityChange | function | | |
-## Related
+
+## `useYouTube(container, options)`
+Create a YouTube player at `container`. `container` must be a ref object.
+
+Returns the `YT.Player` object, or `null` until the player is ready.
+```js
+import { useYouTube } from '@u-wave/react-youtube';
+
+function Player() {
+ const container = useRef(null);
+ const player = useYouTube(container, {
+ video: 'x2to0hs',
+ autoplay: true,
+ });
+ console.log(player?.getVideoUrl());
+ return
;
+}
+```
+
+### Options
+| Name | Type | Default | Description |
+|:-----|:-----|:-----|:-----|
+| video | string | | An 11-character string representing a YouTube video ID.. |
+| width | number, string | | Width of the player element. |
+| height | number, string | | Height of the player element. |
+| host | string | https://www.youtube.com | YouTube host to use: 'https://www.youtube.com' or 'https://www.youtube-nocookie.com'. |
+| origin | string | | The YouTube API will usually default this value correctly. It is exposed for completeness. https://developers.google.com/youtube/player_parameters#origin |
+| paused | bool | | Pause the video. |
+| autoplay | bool | false | Whether the video should start playing automatically. https://developers.google.com/youtube/player_parameters#autoplay |
+| showCaptions | bool | false | Whether to show captions below the video. https://developers.google.com/youtube/player_parameters#cc_load_policy |
+| controls | bool | true | Whether to show video controls. https://developers.google.com/youtube/player_parameters#controls |
+| disableKeyboard | bool | false | Ignore keyboard controls. https://developers.google.com/youtube/player_parameters#disablekb |
+| allowFullscreen | bool | true | Whether to display the fullscreen button. https://developers.google.com/youtube/player_parameters#fs |
+| lang | string | | The player's interface language. The parameter value is an ISO 639-1 two-letter language code or a fully specified locale. https://developers.google.com/youtube/player_parameters#hl |
+| annotations | bool | true | Whether to show annotations on top of the video. https://developers.google.com/youtube/player_parameters#iv_load_policy |
+| startSeconds | number | | Time in seconds at which to start playing the video. https://developers.google.com/youtube/player_parameters#start |
+| endSeconds | number | | Time in seconds at which to stop playing the video. https://developers.google.com/youtube/player_parameters#end |
+| modestBranding | bool | false | Remove most YouTube logos from the player. https://developers.google.com/youtube/player_parameters#modestbranding |
+| playsInline | bool | false | Whether to play the video inline on iOS, instead of fullscreen. https://developers.google.com/youtube/player_parameters#playsinline |
+| showRelatedVideos | bool | true | Whether to show related videos after the video is over. https://developers.google.com/youtube/player_parameters#rel |
+| volume | number | | The playback volume, **as a number between 0 and 1**. |
+| muted | bool | | Whether the video's sound should be muted. |
+| playbackRate | number | | Playback speed. https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate |
+| onReady | function | | Sent when the YouTube player API has loaded. |
+| onError | function | | Sent when the player triggers an error. |
+| onCued | function | | Sent when the video is cued and ready to play. |
+| onBuffering | function | | Sent when the video is buffering. |
+| onPlaying | function | | Sent when playback has been started or resumed. |
+| onPause | function | | Sent when playback has been paused. |
+| onEnd | function | | Sent when playback has stopped. |
+| onStateChange | function | | |
+| onPlaybackRateChange | function | | |
+| onPlaybackQualityChange | function | | |
+
+## Related
- [react-youtube][] - A widely-used YouTube component. Its API matches the YouTube iframe API more closely, and it doesn't support prop-based volume/quality/playback changes.
- [@u-wave/react-vimeo][] - A Vimeo component with a similar declarative API.
## License
-
[MIT][]
[Install]: #install
[Usage]: #usage
-[Props]: #props
+[Component API]: #component
+[Hook API]: #hook
[Demo]: https://u-wave.net/react-youtube
[Demo source code]: ./example
[MIT]: ./LICENSE
diff --git a/example/.gitignore b/example/.gitignore
index 0e804e3a..1521c8b7 100644
--- a/example/.gitignore
+++ b/example/.gitignore
@@ -1 +1 @@
-bundle.js
+dist
diff --git a/example/app.js b/example/app.js
deleted file mode 100644
index 0429d91d..00000000
--- a/example/app.js
+++ /dev/null
@@ -1,132 +0,0 @@
-/* eslint-env browser */
-import React from 'react';
-import ReactDOM from 'react-dom';
-import YouTube from '@u-wave/react-youtube'; // eslint-disable-line import/no-unresolved
-
-const {
- useCallback,
- useState,
-} = React;
-
-const videos = [
- { id: 'ZuuVjuLNvFY', name: 'JUNNY - kontra (Feat. Lil Gimch, Keeflow)' },
- { id: 'PYE7jXNjFWw', name: 'T W L V - Follow' },
- { id: 'ld8ugY47cps', name: 'SLCHLD - I can\'t love you anymore' },
- { id: null, name: '' },
-];
-
-const qualities = ['auto', '240', '380', '480', '720', '1080', '1440', '2160'];
-
-const hashVideoRx = /^#!\/video\/(\d)$/;
-const hash = typeof window.location !== 'undefined'
- ? window.location.hash : ''; // eslint-disable-line no-undef
-const defaultVideo = hashVideoRx.test(hash)
- ? parseInt(hash.replace(hashVideoRx, '$1'), 10)
- : 0;
-
-function App() {
- const [videoIndex, setVideoIndex] = useState(defaultVideo);
- const [suggestedQuality, setSuggestedQuality] = useState('auto');
- const [volume, setVolume] = useState(1);
- const [paused, setPaused] = useState(false);
-
- const video = videos[videoIndex];
-
- function selectVideo(index) {
- setVideoIndex(index);
- }
-
- const handlePause = useCallback((event) => {
- setPaused(event.target.checked);
- }, []);
-
- const handlePlayerPause = useCallback(() => {
- setPaused(true);
- }, []);
-
- const handlePlayerPlay = useCallback(() => {
- setPaused(false);
- }, []);
-
- const handleVolume = useCallback((event) => {
- setVolume(parseFloat(event.target.value));
- }, []);
-
- const handleQuality = useCallback((event) => {
- setSuggestedQuality(qualities[event.target.selectedIndex]);
- }, []);
-
- return (
-