├── .gitignore
├── docs
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── index.html
├── App.tsx
├── app.css
└── index.js
├── .npmignore
├── tsconfig.test.json
├── .mergify.yml
├── tsconfig.json
├── .babelrc
├── setupTests.js
├── .github
├── dependabot.yml
└── workflows
│ └── main.yml
├── src
├── __tests__
│ ├── stringify.test.ts
│ ├── Client.test.ts
│ ├── mockResults.ts
│ ├── Result.test.tsx
│ ├── testServer.ts
│ └── Tenor.test.tsx
├── TenorAPI.ts
├── Result.tsx
├── Client.ts
├── styles.css
├── Search.tsx
└── Tenor.tsx
├── webpack.config.babel.ts
├── LICENSE
├── package.json
├── CODE_OF_CONDUCT.md
├── README.md
└── CHANGELOG.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /.eslintcache
2 | /coverage/
3 | /dist/
4 | /node_modules/
5 |
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CultureHQ/react-tenor/HEAD/docs/favicon.ico
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /.*
2 | /coverage
3 | /webpack*
4 | /docs/
5 | /example/
6 | /src/
7 | /test/
8 | /yarn*
9 |
--------------------------------------------------------------------------------
/docs/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CultureHQ/react-tenor/HEAD/docs/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CultureHQ/react-tenor/HEAD/docs/favicon-32x32.png
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "declaration": false,
5 | "noEmit": true
6 | },
7 | "exclude": []
8 | }
9 |
--------------------------------------------------------------------------------
/.mergify.yml:
--------------------------------------------------------------------------------
1 | pull_request_rules:
2 | - name: Automatically merge dependencies
3 | conditions:
4 | - base=master
5 | - label=dependencies
6 | - status-success=CI
7 | actions:
8 | merge:
9 | strict: true
10 | delete_head_branch: {}
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "jsx": "react",
5 | "module": "commonjs",
6 | "moduleResolution": "node",
7 | "outDir": "dist",
8 | "sourceMap": false,
9 | "strict": true,
10 | "target": "es5"
11 | },
12 | "include": ["src"],
13 | "exclude": ["**/__tests__/*"]
14 | }
15 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["@babel/proposal-class-properties"],
3 | "presets": [
4 | "@babel/preset-env",
5 | "@babel/preset-react",
6 | "@babel/preset-typescript"
7 | ],
8 | "env": {
9 | "test": {
10 | "presets": [
11 | ["@babel/preset-env", { "targets": { "node": "current" } }],
12 | ]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/setupTests.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 |
3 | import { configure } from "enzyme";
4 | import Adapter from "enzyme-adapter-react-16";
5 |
6 | import { startTestServer, stopTestServer } from "./src/__tests__/testServer";
7 |
8 | configure({ adapter: new Adapter() });
9 |
10 | beforeAll(() => startTestServer());
11 | afterAll(() => stopTestServer());
12 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "10:00"
8 | open-pull-requests-limit: 10
9 | ignore:
10 | - dependency-name: typescript
11 | versions:
12 | - 4.1.3
13 | - 4.1.4
14 | - 4.1.5
15 | - 4.2.2
16 | - 4.2.3
17 | - dependency-name: css-loader
18 | versions:
19 | - 5.2.0
20 |
--------------------------------------------------------------------------------
/src/__tests__/stringify.test.ts:
--------------------------------------------------------------------------------
1 | import { stringify } from "../Client";
2 |
3 | test("works for empty objects", () => {
4 | const stringified = stringify({});
5 |
6 | expect(stringified).toEqual("?");
7 | });
8 |
9 | test("works for single values", () => {
10 | const stringified = stringify({ foo: "bar" });
11 |
12 | expect(stringified).toEqual("?foo=bar");
13 | });
14 |
15 | test("works for multiple values", () => {
16 | const stringified = stringify({ foo: "bar", bar: "baz" });
17 |
18 | expect(stringified).toEqual("?foo=bar&bar=baz");
19 | });
20 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | react-tenor
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/__tests__/Client.test.ts:
--------------------------------------------------------------------------------
1 | import Client from "../Client";
2 | import mockResults from "./mockResults";
3 | import testServer from "./testServer";
4 |
5 | test("sets sane defaults", () => {
6 | const client = new Client({});
7 |
8 | expect(client.base).toContain("api.tenor.com");
9 | expect(typeof client.token).toEqual("string");
10 | });
11 |
12 | test("fetches the expected results", async () => {
13 | const client = new Client({ base: `http://localhost:${testServer.port}`, token: "token" });
14 | const response = await client.search("Happy");
15 |
16 | expect(response.results).toEqual(mockResults.search);
17 | });
18 |
--------------------------------------------------------------------------------
/webpack.config.babel.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 |
3 | export default {
4 | output: {
5 | path: path.join(__dirname, "docs"),
6 | filename: "index.js"
7 | },
8 | entry: path.join(__dirname, "docs", "App.tsx"),
9 | resolve: {
10 | extensions: [".js", ".ts", ".tsx"]
11 | },
12 | module: {
13 | rules: [
14 | { test: /\.tsx?$/, use: "awesome-typescript-loader" },
15 | {
16 | test: /\.css$/,
17 | use: [{ loader: "style-loader" }, { loader: "css-loader" }],
18 | exclude: /node_modules/
19 | }
20 | ]
21 | },
22 | devServer: {
23 | contentBase: path.join(__dirname, "docs")
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/TenorAPI.ts:
--------------------------------------------------------------------------------
1 | type MediaType = {
2 | preview: string;
3 | url: string;
4 | dims: number[];
5 | size: number;
6 | };
7 |
8 | type Media = {
9 | tinygif: MediaType;
10 | gif: MediaType;
11 | mp4: MediaType;
12 | };
13 |
14 | export type Result = {
15 | created: number;
16 | hasaudio: boolean;
17 | id: string;
18 | media: Media[];
19 | tags: string[];
20 | itemurl: string;
21 | hascaption: boolean;
22 | url: string;
23 | };
24 |
25 | export type AutocompleteResponse = {
26 | results: string[];
27 | };
28 |
29 | export type SearchResponse = {
30 | next?: string;
31 | results: Result[];
32 | };
33 |
34 | export type SuggestionsResponse = {
35 | results: string[];
36 | };
37 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Main
2 | on: push
3 | jobs:
4 | ci:
5 | name: CI
6 | runs-on: ubuntu-latest
7 | env:
8 | CI: true
9 | steps:
10 | - uses: actions/checkout@master
11 | - uses: actions/setup-node@v2-beta
12 | with:
13 | node-version: 14.x
14 | - id: yarn-cache
15 | run: echo "::set-output name=directory::$(yarn cache dir)"
16 | - uses: actions/cache@v1
17 | with:
18 | path: ${{ steps.yarn-cache.outputs.directory }}
19 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
20 | restore-keys: |
21 | ${{ runner.os }}-yarn-
22 | - run: |
23 | yarn install --frozen-lockfile
24 | yarn lint
25 | yarn compile
26 | yarn test
27 |
--------------------------------------------------------------------------------
/src/__tests__/mockResults.ts:
--------------------------------------------------------------------------------
1 | import * as TenorAPI from "../TenorAPI";
2 |
3 | let counter = 0;
4 |
5 | const makeId = () => {
6 | counter += 1;
7 | return counter.toString();
8 | };
9 |
10 | const makeResult = (): TenorAPI.Result => {
11 | const media = {
12 | preview: "https://via.placeholder.com/10x10",
13 | url: "https://via.placeholder.com/10x10",
14 | dims: [10, 10],
15 | size: 100
16 | };
17 |
18 | return {
19 | created: 12345,
20 | hasaudio: false,
21 | id: makeId(),
22 | media: [{ tinygif: media, gif: media, mp4: media }],
23 | tags: [],
24 | itemurl: "https://tenor.com/view/this-is-a-test-gif-12345",
25 | hascaption: false,
26 | url: "https://tenor.com/12345"
27 | };
28 | };
29 |
30 | const mockResults = { /* eslint-disable camelcase */
31 | autocomplete: ["test", "testing", "test2", "testingtesting", "testy testerson"],
32 | search_suggestions: ["test", "unit test", "acceptance test", "testing", "how to test"],
33 | search: [makeResult(), makeResult(), makeResult(), makeResult(), makeResult()]
34 | };
35 |
36 | export default mockResults;
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018-present CultureHQ
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/__tests__/Result.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 |
4 | import Result from "../Result";
5 | import mockResults from "./mockResults";
6 |
7 | const result = mockResults.search[0];
8 | type OnLoadCallback = (event: Event) => void;
9 |
10 | test("renders without crashing", () => {
11 | const onSelect = jest.fn();
12 | const component = shallow();
13 |
14 | expect(component.type()).toEqual("button");
15 |
16 | component.simulate("click");
17 | expect(onSelect).toHaveBeenCalledWith(result);
18 | });
19 |
20 | test("loads the image in the background", () => {
21 | const component = shallow();
22 | expect(component.find("span")).toHaveLength(0);
23 |
24 | const { image } = component.instance();
25 | (image.onload as OnLoadCallback)(new Event("onload"));
26 |
27 | component.update();
28 | expect(component.find("span")).toHaveLength(1);
29 | });
30 |
31 | test("does not attempt to set state if the image finishes after unmount", () => {
32 | const component = shallow();
33 | const { image } = component.instance();
34 |
35 | component.unmount();
36 | (image.onload as OnLoadCallback)(new Event("onload"));
37 | });
38 |
--------------------------------------------------------------------------------
/docs/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as ReactDOM from "react-dom";
3 |
4 | import Tenor, { Result } from "../src/Tenor";
5 | import "../src/styles.css";
6 |
7 | const App = () => {
8 | const [selected, setSelected] = React.useState(null);
9 |
10 | return (
11 | <>
12 |
13 |
14 |
17 |
18 | {selected &&

}
19 |
20 |
21 |
22 | {ReactDOM.createPortal(
23 | ,
36 | document.body
37 | )}
38 | >
39 | );
40 | };
41 |
42 | ReactDOM.render(, document.getElementById("main"));
43 |
--------------------------------------------------------------------------------
/src/__tests__/testServer.ts:
--------------------------------------------------------------------------------
1 | import { createServer } from "http";
2 |
3 | import mockResults from "./mockResults";
4 |
5 | type TestServer = ReturnType & {
6 | port: number;
7 | requests: Record;
8 | };
9 |
10 | const testServer = createServer() as TestServer;
11 |
12 | testServer.port = 8080;
13 | testServer.requests = {
14 | autocomplete: 0,
15 | search_suggestions: 0,
16 | search: 0
17 | };
18 |
19 | const getRequestKey = (url: string) => url.slice(1).substring(0, url.indexOf("?") - 1);
20 |
21 | export const startTestServer = (): Promise => new Promise(resolve => {
22 | testServer.on("request", (request, response) => {
23 | const requestKey = getRequestKey(request.url) as keyof typeof mockResults;
24 | testServer.requests[requestKey] += 1;
25 |
26 | response.writeHead(200, {
27 | "Content-Type": "application/json",
28 | "Access-Control-Allow-Origin": "*",
29 | "Access-Control-Allow-Methods": "OPTIONS, GET"
30 | });
31 |
32 | response.write(JSON.stringify({ results: mockResults[requestKey], next: "12" }));
33 | response.end();
34 | });
35 |
36 | testServer.on("error", () => {
37 | testServer.close(() => {
38 | testServer.port += 1;
39 | testServer.listen(testServer.port);
40 | });
41 | });
42 |
43 | testServer.on("listening", resolve);
44 |
45 | testServer.listen({ port: testServer.port, host: "localhost", exclusive: true });
46 | });
47 |
48 | export const stopTestServer = (): Promise => (
49 | new Promise(resolve => {
50 | testServer.close(() => resolve());
51 | })
52 | );
53 |
54 | export default testServer;
55 |
--------------------------------------------------------------------------------
/docs/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | margin: 0;
4 | }
5 |
6 | body {
7 | background-color: #f7f7f7;
8 | color: #5c5f67;
9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
10 | sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
11 | font-size: 16px;
12 | line-height: 1.42857143;
13 | margin: 0;
14 | padding: 0;
15 | }
16 |
17 | #main {
18 | box-sizing: border-box;
19 | min-height: 100%;
20 | }
21 |
22 | main {
23 | box-sizing: border-box;
24 | margin: 0 auto;
25 | padding: 40px 0 170px 0;
26 | text-align: center;
27 | }
28 |
29 | nav {
30 | background-color: #2c3e4f;
31 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2);
32 | color: #f7f7f7;
33 | min-height: 50px;
34 | padding: 5px 20px 0;
35 | font-size: 18px;
36 | line-height: 42px;
37 | box-sizing: border-box;
38 | }
39 |
40 | h1 a {
41 | color: #2c3e4f;
42 | text-decoration: none;
43 | }
44 |
45 | h1 a:hover {
46 | color: #6a89af;
47 | text-decoration: underline;
48 | }
49 |
50 | .selected {
51 | background: #6a89af;
52 | background-image: repeating-linear-gradient(
53 | 45deg,
54 | rgba(255, 255, 255, .1),
55 | rgba(255, 255, 255, .1) 15px,
56 | transparent 0,
57 | transparent 30px
58 | );
59 | display: inline-block;
60 | margin-bottom: 20px;
61 | min-height: 100px;
62 | min-width: 100px;
63 | }
64 |
65 | .react-tenor {
66 | margin: 0 auto;
67 | text-align: left;
68 | }
69 |
70 | footer {
71 | background-color: #2c3e4f;
72 | box-shadow: inset 0 2px 2px rgba(0, 0, 0, .4);
73 | color: #f7f7f7;
74 | text-align: center;
75 | height: 130px;
76 | margin-top: -130px;
77 | }
78 |
79 | footer p {
80 | box-sizing: border-box;
81 | margin: 0;
82 | padding: 32px 0 10px;
83 | }
84 |
85 | footer a, footer a:hover {
86 | color: #f7f7f7;
87 | }
88 |
--------------------------------------------------------------------------------
/src/Result.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import * as TenorAPI from "./TenorAPI";
4 |
5 | const BASE = "https://tenor.com/view/";
6 |
7 | type ResultProps = {
8 | onSelect: (result: TenorAPI.Result) => void;
9 | result: TenorAPI.Result;
10 | };
11 |
12 | type ResultState = {
13 | loaded: boolean;
14 | };
15 |
16 | class Result extends React.Component {
17 | private componentIsMounted: boolean;
18 |
19 | public image: HTMLImageElement;
20 |
21 | constructor(props: ResultProps) {
22 | super(props);
23 |
24 | this.componentIsMounted = false;
25 | this.image = new Image();
26 |
27 | this.state = { loaded: false };
28 | }
29 |
30 | componentDidMount(): void {
31 | this.componentIsMounted = true;
32 |
33 | const { result } = this.props;
34 |
35 | this.image.src = result.media[0].tinygif.url;
36 | this.image.onload = () => {
37 | if (this.componentIsMounted) {
38 | this.setState({ loaded: true });
39 | }
40 | };
41 | }
42 |
43 | componentWillUnmount(): void {
44 | this.componentIsMounted = false;
45 | }
46 |
47 | getLabel(): string {
48 | const { result: { itemurl } } = this.props;
49 |
50 | return itemurl.replace(BASE, "").replace(/-gif-\d+$/, "").replace(/-/g, " ");
51 | }
52 |
53 | handleClick = (): void => {
54 | const { result, onSelect } = this.props;
55 |
56 | onSelect(result);
57 | };
58 |
59 | render(): React.ReactElement {
60 | const { loaded } = this.state;
61 |
62 | return (
63 |
71 | );
72 | }
73 | }
74 |
75 | export default Result;
76 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-tenor",
3 | "version": "2.2.0",
4 | "description": "Integrate with the Tenor GIF API",
5 | "main": "dist/Tenor.js",
6 | "types": "dist/Tenor.d.ts",
7 | "scripts": {
8 | "compile": "tsc --project tsconfig.test.json",
9 | "docs": "webpack --mode production",
10 | "lint": "chq-scripts lint",
11 | "prepublishOnly": "rm -rf dist && yarn tsc && cp src/styles.css dist/styles.css",
12 | "start": "webpack-dev-server --mode development --hot",
13 | "test": "chq-scripts test"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/CultureHQ/react-tenor.git"
18 | },
19 | "author": "Kevin Deisz",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/CultureHQ/react-tenor/issues"
23 | },
24 | "homepage": "https://github.com/CultureHQ/react-tenor#readme",
25 | "peerDependencies": {
26 | "react": "^16",
27 | "react-dom": "^16"
28 | },
29 | "devDependencies": {
30 | "@babel/cli": "^7.12.1",
31 | "@babel/core": "^7.12.1",
32 | "@babel/plugin-proposal-class-properties": "^7.12.1",
33 | "@babel/preset-env": "^7.12.1",
34 | "@babel/preset-react": "^7.12.1",
35 | "@babel/preset-typescript": "^7.12.1",
36 | "@babel/register": "^7.12.1",
37 | "@culturehq/scripts": "^6.0.1",
38 | "@types/enzyme": "^3.10.7",
39 | "@types/jest": "^27.0.0",
40 | "@types/react": "^17.0.0",
41 | "@types/react-dom": "^17.0.3",
42 | "awesome-typescript-loader": "^5.2.1",
43 | "babel-loader": "^8.0.5",
44 | "css-loader": "^6.0.0",
45 | "enzyme": "^3.11.0",
46 | "enzyme-adapter-react-16": "^1.15.2",
47 | "react": "^16.9.0",
48 | "react-dom": "^16.14.0",
49 | "style-loader": "^3.0.0",
50 | "typescript": "^4.0.3",
51 | "webpack": "^5.1.3",
52 | "webpack-cli": "^4.0.0",
53 | "webpack-dev-server": "^4.0.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Client.ts:
--------------------------------------------------------------------------------
1 | import * as TenorAPI from "./TenorAPI";
2 |
3 | type Query = {
4 | [key: string]: string | number | undefined;
5 | };
6 |
7 | export const stringify = (query: Query): string => {
8 | const keyValuePairs: string[] = [];
9 |
10 | Object.keys(query).forEach(key => {
11 | if (query[key] !== undefined) {
12 | keyValuePairs.push(`${key}=${query[key]}`);
13 | }
14 | });
15 |
16 | return encodeURI(`?${keyValuePairs.join("&")}`);
17 | };
18 |
19 | const fetch = (
20 | >(base: string, path: string, query: Query): Promise => (
21 | new Promise((resolve, reject) => {
22 | const xhr = new XMLHttpRequest();
23 |
24 | xhr.onreadystatechange = () => {
25 | if (xhr.readyState !== 4) {
26 | return;
27 | }
28 |
29 | if (xhr.status >= 200 && xhr.status < 300) {
30 | resolve(JSON.parse(xhr.responseText));
31 | } else {
32 | reject(new Error(xhr.responseText));
33 | }
34 | };
35 |
36 | xhr.open("GET", `${base}${path}${stringify(query)}`);
37 | xhr.send();
38 | })
39 | )
40 | );
41 |
42 | type ClientOptions = {
43 | base?: string;
44 | token?: string;
45 | locale?: string;
46 | contentFilter?: string;
47 | mediaFilter?: string;
48 | defaultResults?: boolean;
49 | limit?: number;
50 | };
51 |
52 | class Client {
53 | public base: string;
54 |
55 | public token: string;
56 |
57 | private locale: string;
58 |
59 | private contentFilter: string;
60 |
61 | private mediaFilter: string;
62 |
63 | private defaultResults: boolean;
64 |
65 | private limit: number;
66 |
67 | constructor(opts: ClientOptions) {
68 | this.base = opts.base || "https://api.tenor.com/v1";
69 | this.token = opts.token || "LIVDSRZULELA";
70 | this.locale = opts.locale || "en_US";
71 | this.contentFilter = opts.contentFilter || "mild";
72 | this.mediaFilter = opts.mediaFilter || "minimal";
73 | this.defaultResults = opts.defaultResults || false;
74 | this.limit = opts.limit || 12;
75 | }
76 |
77 | autocomplete(search: string): Promise {
78 | return fetch(this.base, "/autocomplete", {
79 | key: this.token,
80 | q: search,
81 | limit: 1,
82 | locale: "en_US"
83 | });
84 | }
85 |
86 | search(search: string, pos?: string): Promise {
87 | const searchQuery = (this.defaultResults && !search) ? "/trending" : "/search";
88 |
89 | return fetch(this.base, searchQuery, {
90 | key: this.token,
91 | q: search,
92 | limit: this.limit,
93 | locale: this.locale,
94 | contentfilter: this.contentFilter,
95 | media_filter: this.mediaFilter,
96 | ar_range: "all",
97 | pos
98 | });
99 | }
100 |
101 | suggestions(search: string): Promise {
102 | return fetch(this.base, "/search_suggestions", {
103 | key: this.token,
104 | q: search,
105 | limit: 5,
106 | locale: this.locale
107 | });
108 | }
109 | }
110 |
111 | export default Client;
112 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at support@culturehq.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-tenor
2 |
3 | [](https://github.com/CultureHQ/react-tenor/actions)
4 | [](https://www.npmjs.com/package/react-tenor)
5 |
6 | A React component for selected GIFs from [Tenor](https://tenor.com/gifapi).
7 |
8 | ## Getting started
9 |
10 | First, add `react-tenor` to your `package.json` `dependencies`, then install using either `npm install` or `yarn install`. Then, get your API key from tenor. Finally, you can add the selector component by adding:
11 |
12 | ```jsx
13 | console.log(result)} />
14 | ```
15 |
16 | ### Styles
17 |
18 | To get the styles, be sure it import `react-tenor/dist/styles.css` into your application. You can style it appropriately for your app by overriding the CSS classes used internally. They are listed in [`styles.css`](src/styles.css).
19 |
20 | ### Props
21 |
22 | Below is a list of all of the props you can pass to the `Tenor` component.
23 |
24 | | Name | Type | Default | Description |
25 | | ------------------- | ---------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
26 | | `autoFocus` | `boolean` | `false` | Indicates that the search bar of the component should request focus when it first mounts. |
27 | | `base` | `string` | `"api.tenor.com/v1"` | The base of the API that this component hits. |
28 | | `contentFilter` | `string` | `"mild"` | The content filter that gets passed up to tenor. See the [tenor API docs](https://tenor.com/gifapi/documentation#contentfilter) for details. |
29 | | `contentRef` | `Ref` | `null` | A ref to the `div` that the `Tenor` component renders. |
30 | | `defaultResults` | `boolean` | `false` | Indicates that the component should automatically search for trending results if the search input is empty. |
31 | | `initialSearch` | `string` | `""` | The starting value of the search bar. |
32 | | `limit` | `number` | `12` | The number of results to return for each search. |
33 | | `locale` | `string` | `"en_US"` | The locale that gets passed up to tenor. See the [tenor API docs](https://tenor.com/gifapi/documentation) for details. |
34 | | `mediaFilter` | `string` | `"minimal"` | The media filter that gets passed up to tenor. See the [tenor API docs](https://tenor.com/gifapi/documentation) for details. |
35 | | `onSelect` | `Result => void` | | A callback for when the user selects a GIF. |
36 | | `searchPlaceholder` | `string` | `"Search Tenor"` | The placeholder that is applied to the search input field. |
37 | | `token` | `string` | | The tenor API token. See the [tenor API docs](https://tenor.com/gifapi/documentation) for details. |
38 |
39 | ## Testing locally
40 |
41 | You can run the tests by running `yarn test` and lint by running `yarn lint`. You can run the local server by running `yarn start` which will start the docs server on `http://localhost:8080`.
42 |
43 | ## Contributing
44 |
45 | Bug reports and pull requests are welcome on GitHub at https://github.com/CultureHQ/react-tenor.
46 |
47 | ## License
48 |
49 | The code is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
50 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | .react-tenor {
2 | background-color: #f7f7f7;
3 | border: 1px solid #ccc;
4 | max-width: 480px;
5 | }
6 |
7 | .react-tenor-active {
8 | box-shadow: 0 0 5px 1px rgba(0, 0, 0, .2);
9 | }
10 |
11 | .react-tenor--search-bar {
12 | position: relative;
13 | }
14 |
15 | .react-tenor--search {
16 | background-color: white;
17 | border: 1px solid #f7f7f7;
18 | box-sizing: border-box;
19 | color: #555;
20 | font-family: Arial;
21 | font-size: 1em;
22 | line-height: 1.3;
23 | overflow: visible;
24 | padding: .25em .5em;
25 | width: 100%;
26 | }
27 |
28 | .react-tenor--search:focus {
29 | box-shadow: 0 0 2px 2px #6a89af;
30 | outline: none;
31 | }
32 |
33 | .react-tenor--autocomplete {
34 | box-sizing: border-box;
35 | color: #aaa;
36 | font-family: Arial;
37 | font-size: 1em;
38 | line-height: 1.3;
39 | left: 0;
40 | padding: .25em .5em;
41 | pointer-events: none;
42 | position: absolute;
43 | top: 1px;
44 | }
45 |
46 | .react-tenor--autocomplete span {
47 | visibility: hidden;
48 | }
49 |
50 | .react-tenor--spinner {
51 | animation: react-tenor-spin 1s linear infinite;
52 | height: 22px;
53 | position: absolute;
54 | right: 4px;
55 | top: 3px;
56 | width: 22px;
57 | }
58 |
59 | .react-tenor--spinner path {
60 | fill: #999;
61 | }
62 |
63 | .react-tenor--suggestions {
64 | overflow-x: auto;
65 | padding: .5em .5em;
66 | white-space: nowrap;
67 | }
68 |
69 | .react-tenor--suggestions button {
70 | background: #6a89af;
71 | border: 1px solid #f7f7f7;
72 | border-radius: 5px;
73 | color: white;
74 | cursor: pointer;
75 | display: inline-block;
76 | font-size: 1em;
77 | padding: 3px 5px;
78 | }
79 |
80 | .react-tenor--suggestions button:focus {
81 | box-shadow: 0 0 2px 2px #6a89af;
82 | outline: none;
83 | }
84 |
85 | .react-tenor--suggestions button + button {
86 | margin-left: .5em;
87 | }
88 |
89 | .react-tenor--results {
90 | display: flex;
91 | flex-wrap: wrap;
92 | position: relative;
93 | }
94 |
95 | .react-tenor--result {
96 | background: #6a89af;
97 | background-image: repeating-linear-gradient(
98 | 45deg,
99 | rgba(255, 255, 255, .1),
100 | rgba(255, 255, 255, .1) 15px,
101 | transparent 0,
102 | transparent 30px
103 | );
104 | border: 0;
105 | cursor: pointer;
106 | display: inline-block;
107 | flex-basis: 25%;
108 | height: 120px;
109 | opacity: 1;
110 | padding: 0;
111 | transition: opacity .3s;
112 | width: 120px;
113 | }
114 |
115 | .react-tenor--result span {
116 | animation: react-tenor-fade-in .2s;
117 | background-size: cover;
118 | display: block;
119 | height: 100%;
120 | width: 100%;
121 | }
122 |
123 | .react-tenor--result:focus {
124 | box-shadow: 0 0 2px 2px #6a89af;
125 | border: 1px solid #f7f7f7;
126 | outline: none;
127 | z-index: 1;
128 | }
129 |
130 | .react-tenor--result:hover {
131 | opacity: .5;
132 | }
133 |
134 | @media screen and (max-width: 480px) {
135 | .react-tenor--result {
136 | flex-basis: 33%;
137 | }
138 | }
139 |
140 | .react-tenor--page-left,
141 | .react-tenor--page-right {
142 | background: #6a89af;
143 | border: 0;
144 | cursor: pointer;
145 | height: 1.8em;
146 | position: absolute;
147 | top: calc(50% - .9em);
148 | opacity: .5;
149 | position: absolute;
150 | transition: opacity .2s, left .2s, right .2s;
151 | width: 1.8em;
152 | z-index: -1;
153 | }
154 |
155 | .react-tenor--results:hover .react-tenor--page-left,
156 | .react-tenor--results:hover .react-tenor--page-right {
157 | opacity: 1;
158 | z-index: 1;
159 | }
160 |
161 | .react-tenor--results:hover .react-tenor--page-left {
162 | left: -1em;
163 | }
164 |
165 | .react-tenor--results:hover .react-tenor--page-right {
166 | right: -1em;
167 | }
168 |
169 | .react-tenor--page-left div,
170 | .react-tenor--page-right div {
171 | background: inherit;
172 | height: 1.6em;
173 | transform: rotate(45deg);
174 | top: .1em;
175 | position: absolute;
176 | width: 1.6em;
177 | }
178 |
179 | .react-tenor--page-left {
180 | left: -.3em;
181 | }
182 |
183 | .react-tenor--page-left div {
184 | left: -.7em;
185 | }
186 |
187 | .react-tenor--page-right {
188 | right: -.3em;
189 | }
190 |
191 | .react-tenor--page-right div {
192 | right: -.7em;
193 | }
194 |
195 | @keyframes react-tenor-fade-in {
196 | from { opacity: 0; }
197 | to { opacity: 1; }
198 | }
199 |
200 | @keyframes react-tenor-spin {
201 | from { transform: rotate(0deg); }
202 | to { transform: rotate(360deg); }
203 | }
204 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 |
9 | ## [2.2.0] - 2020-03-13
10 |
11 | ### Added
12 |
13 | - The `limit` prop to support setting how many results to return.
14 |
15 | ## [2.1.1] - 2019-09-23
16 |
17 | ### Changed
18 |
19 | - Correctly pass down the `searchPlaceholder` prop.
20 |
21 | ## [2.1.0] - 2019-09-19
22 |
23 | ### Added
24 |
25 | - The `searchPlaceholder` prop for configuring the placeholder on the search input.
26 |
27 | ## [2.0.0] - 2019-09-13
28 |
29 | ### Changed
30 |
31 | - Renamed the `safesearch` param on the component to `contentFilter`.
32 |
33 | ## [1.5.0] - 2019-09-12
34 |
35 | ### Added
36 |
37 | - The `locale`, `mediaFilter`, and `safesearch` props.
38 |
39 | ### Changed
40 |
41 | - Switch to TypeScript for development.
42 | - Do not fire off autocomplete and suggestion requests when the search is blank but you have `defaultResults` selected.
43 |
44 | ## [1.4.0] - 2019-08-09
45 |
46 | ### Added
47 |
48 | - The `autoFocus` prop to focus on the input when the component mounts.
49 | - The `defaultResults` prop to display trending results in empty search.
50 |
51 | ### Changed
52 |
53 | - No longer rely on `fetch` being available.
54 | - [INTERNAL] Refactor Client.js to be simpler.
55 | - Ensure `pointer` cursor on pagination controls.
56 | - Align typeahead text with current text.
57 |
58 | ## [1.3.2] - 2019-06-07
59 |
60 | ### Changed
61 |
62 | - Use the `prepublishOnly` npm script so that the `dist` directory does not need to be checked into the repository.
63 |
64 | ## [1.3.1] - 2019-06-07
65 |
66 | ### Changed
67 |
68 | - Properly rebuild dist.
69 |
70 | ## [1.3.0] - 2019-06-06
71 |
72 | ### Added
73 |
74 | - The `initialSearch` prop.
75 |
76 | ## [1.2.1] - 2019-06-05
77 |
78 | ### Changed
79 |
80 | - Rebuilt dist with correct capitalization of file names.
81 |
82 | ## [1.2.0] - 2019-05-22
83 |
84 | ### Added
85 |
86 | - Switched to using `@culturehq/scripts` for development.
87 | - Added `:focus` styles for relevant components for better keyboard support.
88 |
89 | ## [1.1.0] - 2018-10-03
90 |
91 | ### Added
92 |
93 | - Type ahead through the use of Tenor's autocomplete feature.
94 | - Suggestions are now rendered through the user of Tenor's suggestions feature.
95 | - Pagination is now supported. Additionally you can use the meta-key plus left arrow and right arrow to go between pages.
96 |
97 | ### Changed
98 |
99 | - Now encoding the URI being sent to tenor to ensure it's a valid URL.
100 | - If you click outside the component, the component now knows about that click and closes the selector.
101 | - The search bar is now of type "search" which builds in some nicities from the browsers that support it.
102 | - Added better accessibility support by properly naming the GIF buttons.
103 | - Load preview GIFs in the background and then set them to fade in once they are loaded.
104 |
105 | ## [1.0.0] - 2018-09-26
106 |
107 | ### Added
108 |
109 | - The optional `contentRef` prop to get access to the actual div that is being rendered.
110 | - The optional `base` prop that will specify the base of the API for the search URLs that are generated.
111 | - The `focus()` function on the main component to allow consumers to focus into the input field.
112 |
113 | ### Changed
114 |
115 | - Removed the style import by default. It's now up to the consumers of this package to import `react-tenor/dist/styles.css` into their applications. This avoids a lot of weird webpack bugs.
116 |
117 | ## [0.2.0] - 2018-07-18
118 |
119 | ### Changed
120 |
121 | - Don't build the final distribution with `webpack`, just use `babel`.
122 | - Rename `example` to `docs` so we can publish to github pages.
123 |
124 | [unreleased]: https://github.com/CultureHQ/react-tenor/compare/v2.2.0...HEAD
125 | [2.2.0]: https://github.com/CultureHQ/react-tenor/compare/v2.1.1...v2.2.0
126 | [2.1.1]: https://github.com/CultureHQ/react-tenor/compare/v2.1.0...v2.1.1
127 | [2.1.0]: https://github.com/CultureHQ/react-tenor/compare/v2.0.0...v2.1.0
128 | [2.0.0]: https://github.com/CultureHQ/react-tenor/compare/v1.5.0...v2.0.0
129 | [1.5.0]: https://github.com/CultureHQ/react-tenor/compare/v1.4.0...v1.5.0
130 | [1.4.0]: https://github.com/CultureHQ/react-tenor/compare/v1.3.2...v1.4.0
131 | [1.3.2]: https://github.com/CultureHQ/react-tenor/compare/v1.3.1...v1.3.2
132 | [1.3.1]: https://github.com/CultureHQ/react-tenor/compare/v1.3.0...v1.3.1
133 | [1.3.0]: https://github.com/CultureHQ/react-tenor/compare/v1.2.1...v1.3.0
134 | [1.2.1]: https://github.com/CultureHQ/react-tenor/compare/v1.2.0...v1.2.1
135 | [1.2.0]: https://github.com/CultureHQ/react-tenor/compare/v1.1.0...v1.2.0
136 | [1.1.0]: https://github.com/CultureHQ/react-tenor/compare/v1.0.0...v1.1.0
137 | [1.0.0]: https://github.com/CultureHQ/react-tenor/compare/v0.2.0...v1.0.0
138 | [0.2.0]: https://github.com/CultureHQ/react-tenor/compare/2d68b4...v0.2.0
139 |
--------------------------------------------------------------------------------
/src/Search.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import * as TenorAPI from "./TenorAPI";
4 | import Result from "./Result";
5 |
6 | type AutoCompleteProps = {
7 | autoComplete: string;
8 | search: string;
9 | };
10 |
11 | const AutoComplete: React.FC = ({ autoComplete, search }) => {
12 | const prefix = search.toLowerCase().replace(/\s/g, "");
13 | const typeahead = autoComplete.toLowerCase().replace(prefix, "");
14 |
15 | return (
16 |
17 | {search}
18 | {typeahead}
19 |
20 | );
21 | };
22 |
23 | const Spinner: React.FC = () => (
24 |
27 | );
28 |
29 | type SearchBarProps = {
30 | autoComplete: string | null;
31 | inputRef: React.RefObject;
32 | onSearchChange: (event: React.ChangeEvent) => void;
33 | onSearchKeyDown: (event: React.KeyboardEvent) => void;
34 | search: string;
35 | searching: boolean;
36 | placeholder?: string;
37 | };
38 |
39 | const SearchBar: React.FC = ({
40 | autoComplete, inputRef, search, searching, onSearchChange, onSearchKeyDown, placeholder
41 | }) => (
42 |
43 |
53 | {autoComplete && search && (
54 |
55 | )}
56 | {searching &&
}
57 |
58 | );
59 |
60 | type SuggestionProps = {
61 | onSuggestionClick: (suggestion: string) => void;
62 | suggestion: string;
63 | };
64 |
65 | const Suggestion: React.FC = ({ suggestion, onSuggestionClick }) => {
66 | const onClick = () => onSuggestionClick(suggestion);
67 |
68 | return ;
69 | };
70 |
71 | type SuggestionsProps = {
72 | onSuggestionClick: (suggestion: string) => void;
73 | suggestions: string[];
74 | };
75 |
76 | const Suggestions: React.FC = ({ suggestions, onSuggestionClick }) => (
77 |
78 | {suggestions.map(suggestion => (
79 |
84 | ))}
85 |
86 | );
87 |
88 | type PageControlProps = {
89 | direction: "left" | "right";
90 | onClick: (event: React.MouseEvent) => void;
91 | };
92 |
93 | const PageControl: React.FC = ({ direction, onClick }) => (
94 |
102 | );
103 |
104 | type ResultProps = {
105 | onPageLeft: (event: React.MouseEvent) => void;
106 | onPageRight: (event: React.MouseEvent) => void;
107 | onSelect: (result: TenorAPI.Result) => void;
108 | results: TenorAPI.Result[];
109 | };
110 |
111 | const Results: React.FC = ({ results, onPageLeft, onPageRight, onSelect }) => (
112 |
113 | {results.map(result => (
114 |
115 | ))}
116 |
117 |
118 |
119 | );
120 |
121 | type SearchProps = SearchBarProps & ResultProps & SuggestionsProps & {
122 | contentRef: React.RefObject;
123 | };
124 |
125 | const Search: React.FC = ({
126 | autoComplete, contentRef, inputRef, onPageLeft, onPageRight, onSearchChange,
127 | onSearchKeyDown, onSuggestionClick, onSelect, results, search, searching,
128 | suggestions, placeholder
129 | }) => {
130 | let classList = "react-tenor";
131 | if (suggestions.length > 0 || results.length > 0) {
132 | classList = `${classList} react-tenor-active`;
133 | }
134 |
135 | return (
136 |
137 |
146 | {suggestions.length > 0 && (
147 |
151 | )}
152 | {results.length > 0 && (
153 |
159 | )}
160 |
161 | );
162 | };
163 |
164 | export default Search;
165 |
--------------------------------------------------------------------------------
/src/Tenor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import * as TenorAPI from "./TenorAPI";
4 | import Client from "./Client";
5 | import Search from "./Search";
6 |
7 | export const defaultState = {
8 | autoComplete: null,
9 | autoFocus: false,
10 | page: 0,
11 | pages: [],
12 | search: "",
13 | searching: false,
14 | suggestions: []
15 | };
16 |
17 | const searchDelay = 250;
18 |
19 | const keyCodes = {
20 | Tab: 9,
21 | ArrowLeft: 37,
22 | ArrowRight: 39
23 | };
24 |
25 | type TenorProps = {
26 | autoFocus?: boolean;
27 | base?: string;
28 | contentRef?: React.RefObject;
29 | defaultResults?: boolean;
30 | initialSearch?: string;
31 | onSelect: (result: TenorAPI.Result) => void;
32 | token: string;
33 | locale?: string;
34 | mediaFilter?: string;
35 | contentFilter?: string;
36 | searchPlaceholder?: string;
37 | limit?: number;
38 | };
39 |
40 | type TenorState = {
41 | autoComplete: string | null;
42 | page: number;
43 | pages: TenorAPI.SearchResponse[];
44 | search: string;
45 | searching: boolean;
46 | suggestions: string[];
47 | };
48 |
49 | type SetState = (
50 | ((prev: TenorState) => Pick) | Pick
51 | );
52 |
53 | class Tenor extends React.Component {
54 | public client: Client;
55 |
56 | public componentIsMounted: boolean;
57 |
58 | public contentRef: React.RefObject;
59 |
60 | public inputRef: React.RefObject;
61 |
62 | public timeout: ReturnType | null;
63 |
64 | constructor(props: TenorProps) {
65 | super(props);
66 |
67 | this.client = this.makeClient();
68 |
69 | this.contentRef = React.createRef();
70 | this.inputRef = React.createRef();
71 |
72 | this.timeout = null;
73 | this.componentIsMounted = false;
74 |
75 | this.state = {
76 | ...defaultState,
77 | search: props.initialSearch || "",
78 | searching: !!(props.initialSearch || props.defaultResults)
79 | };
80 | }
81 |
82 | componentDidMount(): void {
83 | const { autoFocus, initialSearch, defaultResults } = this.props;
84 |
85 | this.componentIsMounted = true;
86 | window.addEventListener("keydown", this.handleWindowKeyDown);
87 | window.addEventListener("click", this.handleWindowClick);
88 |
89 | if (initialSearch) {
90 | this.fetchAutoComplete(initialSearch);
91 | this.fetchSuggestions(initialSearch);
92 | }
93 |
94 | if (initialSearch || defaultResults) {
95 | this.performSearch(initialSearch || "");
96 | }
97 |
98 | if (autoFocus) {
99 | this.focus();
100 | }
101 | }
102 |
103 | componentDidUpdate(prevProps: TenorProps): void {
104 | const { base, token, locale, mediaFilter, contentFilter, defaultResults, limit } = this.props;
105 |
106 | if (
107 | base !== prevProps.base
108 | || token !== prevProps.token
109 | || locale !== prevProps.locale
110 | || mediaFilter !== prevProps.mediaFilter
111 | || contentFilter !== prevProps.contentFilter
112 | || defaultResults !== prevProps.defaultResults
113 | || limit !== prevProps.limit
114 | ) {
115 | this.client = this.makeClient();
116 | }
117 | }
118 |
119 | componentWillUnmount(): void {
120 | window.removeEventListener("click", this.handleWindowClick);
121 | window.removeEventListener("keydown", this.handleWindowKeyDown);
122 | this.componentIsMounted = false;
123 | }
124 |
125 | fetchAutoComplete = (currentSearch: string): Promise => (
126 | this.client.autocomplete(currentSearch).then(({ results: [autoComplete] }) => {
127 | const { search } = this.state;
128 |
129 | if (search === currentSearch) {
130 | this.mountedSetState({ autoComplete });
131 | }
132 | })
133 | );
134 |
135 | fetchSuggestions = (currentSearch: string): Promise => (
136 | this.client.suggestions(currentSearch).then(({ results: suggestions }) => {
137 | const { search } = this.state;
138 |
139 | if (search === currentSearch) {
140 | this.mountedSetState({ suggestions });
141 | }
142 | })
143 | );
144 |
145 | handleWindowClick = (event: MouseEvent): void => {
146 | const { contentRef } = this.props;
147 | const { search } = this.state;
148 |
149 | if (!search) {
150 | return;
151 | }
152 |
153 | const container = (contentRef || this.contentRef).current;
154 | if (container && (event.target instanceof Element) && container.contains(event.target)) {
155 | return;
156 | }
157 |
158 | if (this.timeout) {
159 | clearTimeout(this.timeout);
160 | }
161 |
162 | this.setState(defaultState);
163 | };
164 |
165 | handleWindowKeyDown = (event: KeyboardEvent): void => {
166 | const { contentRef } = this.props;
167 | const container = (contentRef || this.contentRef).current;
168 |
169 | if (
170 | (container && (event.target instanceof Element) && !container.contains(event.target))
171 | || ([keyCodes.ArrowLeft, keyCodes.ArrowRight].indexOf(event.keyCode) === -1)
172 | || !event.metaKey
173 | ) {
174 | return;
175 | }
176 |
177 | event.preventDefault();
178 |
179 | if (event.keyCode === keyCodes.ArrowLeft) {
180 | this.handlePageLeft();
181 | } else {
182 | this.handlePageRight();
183 | }
184 | };
185 |
186 | handlePageLeft = (): void => {
187 | this.setState(({ page }) => ({ page: page === 0 ? 0 : page - 1 }));
188 | };
189 |
190 | handlePageRight = (): Promise => {
191 | const { defaultResults } = this.props;
192 | const {
193 | page,
194 | pages,
195 | search,
196 | searching
197 | } = this.state;
198 |
199 | if ((!defaultResults && !search) || searching) {
200 | return Promise.resolve();
201 | }
202 |
203 | if (page < pages.length - 1) {
204 | this.setState(({ page: prevPage }) => ({ page: prevPage + 1 }));
205 | return Promise.resolve();
206 | }
207 |
208 | return this.client.search(search, pages[page].next)
209 | .then((nextPage: TenorAPI.SearchResponse) => {
210 | if (nextPage.results) {
211 | this.mountedSetState(({ page: prevPage, pages: prevPages }) => ({
212 | page: prevPage + 1,
213 | pages: prevPages.concat([nextPage]),
214 | searching: false
215 | }));
216 | }
217 | }).catch(() => {
218 | this.mountedSetState({ searching: false });
219 | });
220 | };
221 |
222 | handleSearchChange = (event: React.ChangeEvent): void => {
223 | const { defaultResults } = this.props;
224 | const search = event.target.value;
225 |
226 | if (this.timeout) {
227 | clearTimeout(this.timeout);
228 | }
229 |
230 | if (!search.length) {
231 | if (defaultResults) {
232 | this.setState({ ...defaultState, searching: true });
233 | this.performSearch(search);
234 | } else {
235 | this.setState(defaultState);
236 | }
237 | return;
238 | }
239 |
240 | this.setState({ autoComplete: null, search, searching: true });
241 | this.fetchAutoComplete(search);
242 | this.fetchSuggestions(search);
243 | this.timeout = setTimeout(() => this.performSearch(search), searchDelay);
244 | };
245 |
246 | handleSearchKeyDown = (event: React.KeyboardEvent): void => {
247 | const { autoComplete, search: prevSearch } = this.state;
248 |
249 | if (event.keyCode !== keyCodes.Tab || !autoComplete || !prevSearch) {
250 | return;
251 | }
252 |
253 | const lowerAutoComplete = autoComplete.toLowerCase();
254 | const lowerSearch = prevSearch.toLowerCase().replace(/\s/g, "");
255 |
256 | if (lowerAutoComplete === lowerSearch) {
257 | return;
258 | }
259 |
260 | event.preventDefault();
261 |
262 | const typeahead = lowerAutoComplete.replace(lowerSearch, "");
263 | const search = `${prevSearch}${typeahead}`;
264 |
265 | this.setState({ autoComplete: null, search, searching: true });
266 | this.fetchSuggestions(search);
267 | this.performSearch(search);
268 | };
269 |
270 | handleSuggestionClick = (suggestion: string): void => {
271 | if (this.timeout) {
272 | clearTimeout(this.timeout);
273 | }
274 |
275 | this.setState({ search: suggestion, searching: true });
276 | this.performSearch(suggestion);
277 | };
278 |
279 | performSearch = (search: string): Promise => {
280 | if (!this.componentIsMounted) {
281 | return Promise.resolve();
282 | }
283 |
284 | return this.client.search(search).then(page => {
285 | this.mountedSetState({ page: 0, pages: [page], searching: false });
286 | }).catch(() => {
287 | this.mountedSetState({ searching: false });
288 | });
289 | };
290 |
291 | mountedSetState = (state: SetState): void => {
292 | if (this.componentIsMounted) {
293 | this.setState(state);
294 | }
295 | };
296 |
297 | makeClient(): Client {
298 | const { base, token, locale, mediaFilter, contentFilter, defaultResults, limit } = this.props;
299 |
300 | return new Client({
301 | base,
302 | token,
303 | locale,
304 | mediaFilter,
305 | contentFilter,
306 | defaultResults,
307 | limit
308 | });
309 | }
310 |
311 | focus(): void {
312 | const input = this.inputRef.current;
313 |
314 | if (input) {
315 | input.focus();
316 | }
317 | }
318 |
319 | render(): React.ReactElement {
320 | const { contentRef, onSelect, searchPlaceholder } = this.props;
321 | const {
322 | autoComplete, page, pages, search, searching, suggestions
323 | } = this.state;
324 |
325 | return (
326 |
342 | );
343 | }
344 | }
345 |
346 | export { Result } from "./TenorAPI";
347 | export default Tenor;
348 |
--------------------------------------------------------------------------------
/src/__tests__/Tenor.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { mount, ReactWrapper } from "enzyme";
3 |
4 | import * as TenorAPI from "../TenorAPI";
5 | import Tenor, { defaultState } from "../Tenor";
6 | import Result from "../Result";
7 | import mockResults from "./mockResults";
8 | import testServer from "./testServer";
9 |
10 | const ARROW_LEFT_KEY = 37;
11 | const ARROW_RIGHT_KEY = 39;
12 |
13 | type TenorProps = React.ComponentProps;
14 | type TenorState = Tenor["state"];
15 |
16 | type MountedTenor = ReactWrapper & {
17 | pressKey: (keyCode: number) => void;
18 | pressArrowLeftKey: () => void;
19 | pressArrowRightKey: () => void;
20 | };
21 |
22 | type MountedTenorProps = Partial;
23 | type MountedTenorState = Partial;
24 |
25 | const mountTenor = (props: MountedTenorProps = {}, state: MountedTenorState = {}): MountedTenor => {
26 | const component = mount(
27 |
35 | ) as MountedTenor;
36 |
37 | component.setState({ ...defaultState, ...state });
38 |
39 | component.pressKey = keyCode => {
40 | component.instance().handleWindowKeyDown({
41 | ...new KeyboardEvent("keydown"),
42 | keyCode,
43 | metaKey: true,
44 | target: component.instance().contentRef.current,
45 | preventDefault: () => {}
46 | });
47 | };
48 |
49 | component.pressArrowLeftKey = () => component.pressKey(ARROW_LEFT_KEY);
50 | component.pressArrowRightKey = () => component.pressKey(ARROW_RIGHT_KEY);
51 |
52 | return component;
53 | };
54 |
55 | test("performs searches", async () => {
56 | let selected: TenorAPI.Result | null = null;
57 | const onSelect = (result: TenorAPI.Result) => {
58 | selected = result;
59 | };
60 |
61 | const search = "Happy";
62 | const component = mountTenor({ onSelect });
63 | component.find("input").simulate("change", { target: { value: search } });
64 |
65 | component.update();
66 | expect(component.state().search).toEqual(search);
67 | expect(component.find("svg")).toHaveLength(1);
68 |
69 | await component.instance().performSearch(search);
70 |
71 | component.update();
72 | expect(component.find(Result)).toHaveLength(mockResults.search.length);
73 |
74 | component.find(Result).at(3).simulate("click");
75 | expect(selected).toEqual(mockResults.search[3]);
76 |
77 | component.unmount();
78 | });
79 |
80 | test("dedups fast searches", () => {
81 | const component = mountTenor();
82 | const previousSearches = testServer.requests.search;
83 |
84 | const search = "Happy";
85 | search.split("").forEach((_, index) => {
86 | const value = search.slice(0, index + 1);
87 | component.find("input").simulate("change", { target: { value } });
88 | });
89 |
90 | return new Promise(resolve => {
91 | // Yeah this is not great, but if you're going to test setTimeout,
92 | // sometimes you just want to use setTimeout.
93 | setTimeout(() => {
94 | expect(testServer.requests.search).toEqual(previousSearches + 1);
95 |
96 | resolve();
97 | }, 300);
98 | });
99 | });
100 |
101 | test("allows passing an initialSearch prop", async () => {
102 | const component = mountTenor({ initialSearch: "happy" });
103 |
104 | await component.instance().performSearch("");
105 | component.update();
106 |
107 | expect(component.find(Result)).toHaveLength(mockResults.search.length);
108 | });
109 |
110 | test("does not enqueue searches for empty inputs", () => {
111 | const component = mountTenor();
112 |
113 | component.find("input").simulate("change", { target: { value: "" } });
114 |
115 | expect(component.instance().timeout).toBe(null);
116 | });
117 |
118 | test("handles the contentRef prop", () => {
119 | const contentRef = React.createRef();
120 | const component = mountTenor({ contentRef });
121 |
122 | expect(contentRef.current).not.toBe(null);
123 | component.unmount();
124 | });
125 |
126 | describe("suggestions", () => {
127 | test("handles clicking a suggestion", async () => {
128 | const component = mountTenor();
129 |
130 | component.setState({ search: "test" });
131 | await component.instance().fetchSuggestions("test");
132 | component.update();
133 |
134 | expect(component.find("Suggestion")).toHaveLength(5);
135 |
136 | component.find("Suggestion").at(2).find("button").simulate("click");
137 |
138 | expect(component.state().search).toEqual(mockResults.search_suggestions[2]);
139 | await component.instance().performSearch(mockResults.search_suggestions[2]);
140 | });
141 |
142 | test("clears the timeout", () => {
143 | const component = mountTenor();
144 | component.setState({ search: "t", suggestions: ["test"] });
145 |
146 | component.instance().client.search = () => Promise.resolve({ results: mockResults.search });
147 | component.instance().timeout = setTimeout(() => {}, 1000);
148 |
149 | component.find("Suggestion").find("button").simulate("click");
150 | expect(component.state().search).toEqual("test");
151 | });
152 | });
153 |
154 | describe("tab completion", () => {
155 | const BACKSPACE_KEY = 8;
156 | const TAB_KEY = 9;
157 |
158 | test("handles tab completing the typeahead", async () => {
159 | const component = mountTenor();
160 |
161 | component.setState({ search: "t" });
162 | await component.instance().fetchAutoComplete("t");
163 | component.update();
164 |
165 | expect(component.find("AutoComplete")).toHaveLength(1);
166 |
167 | component.find("input").simulate("keyDown", { keyCode: TAB_KEY });
168 | expect(component.state().search).toEqual(mockResults.autocomplete[0]);
169 |
170 | await component.instance().performSearch(mockResults.autocomplete[0]);
171 | });
172 |
173 | test("ignores other key inputs", () => {
174 | const component = mountTenor();
175 |
176 | component.find("input").simulate("keyDown", { keyCode: BACKSPACE_KEY });
177 | expect(component.state().search).toEqual("");
178 | });
179 |
180 | test("ignores when the autoComplete matches the search", () => {
181 | const component = mountTenor();
182 | component.setState({ autoComplete: "test", search: "test" });
183 |
184 | component.find("input").simulate("keyDown", { keyCode: TAB_KEY });
185 | expect(component.state().search).toEqual("test");
186 | });
187 | });
188 |
189 | describe("auto close", () => {
190 | test("handles clicking outside the component", () => {
191 | const contentRef = React.createRef();
192 | const component = mountTenor({ contentRef });
193 |
194 | component.instance().handleWindowClick(new MouseEvent("click"));
195 | expect(component.state().search).toEqual("");
196 |
197 | component.setState({ search: "t" });
198 | component.instance().handleWindowClick({ ...new MouseEvent("click"), target: contentRef.current });
199 | expect(component.state().search).toEqual("t");
200 |
201 | component.instance().handleWindowClick(new MouseEvent("click"));
202 | expect(component.state().search).toEqual("");
203 | });
204 |
205 | test("clears the timeout", () => {
206 | const component = mountTenor();
207 | component.setState({ search: "t" });
208 |
209 | component.instance().timeout = setTimeout(() => {}, 1000);
210 | component.instance().handleWindowClick(new MouseEvent("click"));
211 | expect(component.state().search).toEqual("");
212 | });
213 | });
214 |
215 | test("creates a new client when the token or base changes", () => {
216 | const component = mountTenor({ base: "https://example.com" });
217 |
218 | component.setProps({ token: "other-token" });
219 | expect(component.instance().client.token).toEqual("other-token");
220 |
221 | component.setProps({ base: "https://other-example.com" });
222 | expect(component.instance().client.base).toEqual("https://other-example.com");
223 | });
224 |
225 | test("handles when the search returns an error", () => {
226 | const component = mountTenor();
227 | component.instance().client.search = () => Promise.reject(new Error("error"));
228 |
229 | component.instance().performSearch("test");
230 | expect(component.state().searching).toBe(false);
231 | });
232 |
233 | test("unmounts cleanly", async () => {
234 | const component = mountTenor();
235 | const instance = component.instance();
236 |
237 | setTimeout(() => instance.mountedSetState({ search: "foobar" }), 100);
238 | component.unmount();
239 |
240 | await new Promise(resolve => {
241 | setTimeout(resolve, 100);
242 | });
243 | });
244 |
245 | test("searchPlaceholder", () => {
246 | const searchPlaceholder = "Search GIFs!!";
247 | const component = mountTenor({ searchPlaceholder });
248 |
249 | const input = component.find("input");
250 | expect(input.prop("placeholder")).toEqual(searchPlaceholder);
251 | });
252 |
253 | describe("pagination", () => {
254 | test("paging left", () => {
255 | const component = mountTenor();
256 | expect(component.state().page).toEqual(0);
257 |
258 | component.instance().handlePageLeft();
259 | expect(component.state().page).toEqual(0);
260 |
261 | component.setState({ page: 1 });
262 | component.instance().handlePageLeft();
263 | expect(component.state().page).toEqual(0);
264 | });
265 |
266 | test("paging left with the keys", () => {
267 | const component = mountTenor({}, { page: 1 });
268 |
269 | component.pressArrowLeftKey();
270 |
271 | expect(component.state().page).toEqual(0);
272 | });
273 |
274 | test("paging right when not at the end", () => {
275 | const component = mountTenor({}, {
276 | page: 0,
277 | pages: [
278 | { results: mockResults.search, next: "12" },
279 | { results: mockResults.search, next: "24" }
280 | ],
281 | search: "test",
282 | searching: false
283 | });
284 |
285 | component.instance().handlePageRight();
286 | expect(component.state().page).toEqual(1);
287 | });
288 |
289 | test("paging right when at the end", async () => {
290 | const component = mountTenor({ token: "token" }, { search: "test" });
291 |
292 | await component.instance().performSearch("test");
293 | component.update();
294 |
295 | await component.instance().handlePageRight();
296 | expect(component.state().page).toEqual(1);
297 | });
298 |
299 | test("paging right with the keys", () => {
300 | const component = mountTenor({}, {
301 | page: 0,
302 | pages: [
303 | { results: mockResults.search, next: "12" },
304 | { results: mockResults.search, next: "24" }
305 | ],
306 | search: "test",
307 | searching: false
308 | });
309 |
310 | component.pressArrowRightKey();
311 |
312 | expect(component.state().page).toEqual(1);
313 | });
314 |
315 | test("ignores other key presses", () => {
316 | const component = mountTenor({}, { page: 1 });
317 |
318 | component.instance().handleWindowKeyDown({
319 | ...new KeyboardEvent("keydown"),
320 | keyCode: ARROW_LEFT_KEY,
321 | metaKey: true,
322 | target: document.body,
323 | preventDefault() {}
324 | });
325 |
326 | expect(component.state().page).toEqual(1);
327 | });
328 |
329 | test("does not page right if currently searching", () => {
330 | const component = mountTenor({}, {
331 | page: 0,
332 | pages: [
333 | { results: mockResults.search, next: "12" },
334 | { results: mockResults.search, next: "24" }
335 | ],
336 | search: "test",
337 | searching: true
338 | });
339 |
340 | component.pressArrowRightKey();
341 |
342 | expect(component.state().page).toEqual(0);
343 | });
344 |
345 | test("resets the searching state if the pagination call fails", () => {
346 | const component = mountTenor({}, {
347 | page: 0,
348 | pages: [{ results: mockResults.search, next: "12" }],
349 | search: "test",
350 | searching: false
351 | });
352 |
353 | component.instance().client.search = () => Promise.reject(new Error("error"));
354 | component.pressArrowRightKey();
355 |
356 | expect(component.state().page).toEqual(0);
357 | expect(component.state().searching).toBe(false);
358 | });
359 |
360 | test("does not increment page if the next page has no results", () => {
361 | const component = mountTenor({}, {
362 | page: 0,
363 | pages: [{ results: mockResults.search, next: "12" }],
364 | search: "test",
365 | searching: false
366 | });
367 |
368 | component.instance().client.search = () => Promise.resolve({ results: [] });
369 | component.pressArrowRightKey();
370 |
371 | expect(component.state().page).toEqual(0);
372 | expect(component.state().searching).toBe(false);
373 | });
374 | });
375 |
--------------------------------------------------------------------------------
/docs/index.js:
--------------------------------------------------------------------------------
1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var l=t[r]={i:r,l:!1,exports:{}};return e[r].call(l.exports,l,l.exports,n),l.l=!0,l.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var l in e)n.d(r,l,function(t){return e[t]}.bind(null,l));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=2)}([function(e,t,n){"use strict";e.exports=n(3)},function(e,t,n){"use strict";
2 | /*
3 | object-assign
4 | (c) Sindre Sorhus
5 | @license MIT
6 | */var r=Object.getOwnPropertySymbols,l=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;function o(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,i,u=o(e),c=1;cR.length&&R.push(e)}function I(e,t,n){return null==e?0:function e(t,n,r,l){var i=typeof t;"undefined"!==i&&"boolean"!==i||(t=null);var u=!1;if(null===t)u=!0;else switch(i){case"string":case"number":u=!0;break;case"object":switch(t.$$typeof){case a:case o:u=!0}}if(u)return r(l,t,""===n?"."+F(t,0):n),1;if(u=0,n=""===n?".":n+":",Array.isArray(t))for(var c=0;ct}return!1}(t,n,l,r)&&(n=null),r||null===l?function(e){return!!me.call(ge,e)||!me.call(he,e)&&(pe.test(e)?ge[e]=!0:(he[e]=!0,!1))}(t)&&(null===n?e.removeAttribute(t):e.setAttribute(t,""+n)):l.mustUseProperty?e[l.propertyName]=null===n?3!==l.type&&"":n:(t=l.attributeName,r=l.attributeNamespace,null===n?e.removeAttribute(t):(n=3===(l=l.type)||4===l&&!0===n?"":""+n,r?e.setAttributeNS(r,t,n):e.setAttribute(t,n))))}function xe(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===t||"radio"===t)}function Se(e){e._valueTracker||(e._valueTracker=function(e){var t=xe(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&void 0!==n&&"function"==typeof n.get&&"function"==typeof n.set){var l=n.get,a=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(e){r=""+e,a.call(this,e)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(e){r=""+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e))}function Te(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=xe(e)?e.checked?"true":"false":e.value),(e=r)!==n&&(t.setValue(e),!0)}function Ce(e,t){var n=t.checked;return l({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=n?n:e._wrapperState.initialChecked})}function _e(e,t){var n=null==t.defaultValue?"":t.defaultValue,r=null!=t.checked?t.checked:t.defaultChecked;n=ke(null!=t.value?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:"checkbox"===t.type||"radio"===t.type?null!=t.checked:null!=t.value}}function Pe(e,t){null!=(t=t.checked)&&Ee(e,"checked",t,!1)}function Ne(e,t){Pe(e,t);var n=ke(t.value),r=t.type;if(null!=n)"number"===r?(0===n&&""===e.value||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if("submit"===r||"reset"===r)return void e.removeAttribute("value");t.hasOwnProperty("value")?Re(e,t.type,n):t.hasOwnProperty("defaultValue")&&Re(e,t.type,ke(t.defaultValue)),null==t.checked&&null!=t.defaultChecked&&(e.defaultChecked=!!t.defaultChecked)}function Oe(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!("submit"!==r&&"reset"!==r||void 0!==t.value&&null!==t.value))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}""!==(n=e.name)&&(e.name=""),e.defaultChecked=!e.defaultChecked,e.defaultChecked=!!e._wrapperState.initialChecked,""!==n&&(e.name=n)}function Re(e,t,n){"number"===t&&e.ownerDocument.activeElement===e||(null==n?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}function ze(e,t){return e=l({children:void 0},t),(t=function(e){var t="";return r.Children.forEach(e,(function(e){null!=e&&(t+=e)})),t}(t.children))&&(e.children=t),e}function Me(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l=t.length))throw Error(o(93));t=t[0]}n=t}null==n&&(n="")}e._wrapperState={initialValue:ke(n)}}function Le(e,t){var n=ke(t.value),r=ke(t.defaultValue);null!=n&&((n=""+n)!==e.value&&(e.value=n),null==t.defaultValue&&e.defaultValue!==n&&(e.defaultValue=n)),null!=r&&(e.defaultValue=""+r)}function Ue(e){var t=e.textContent;t===e._wrapperState.initialValue&&""!==t&&null!==t&&(e.value=t)}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach((function(e){var t=e.replace(be,we);ye[t]=new ve(t,1,!1,e,null,!1)})),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach((function(e){var t=e.replace(be,we);ye[t]=new ve(t,1,!1,e,"http://www.w3.org/1999/xlink",!1)})),["xml:base","xml:lang","xml:space"].forEach((function(e){var t=e.replace(be,we);ye[t]=new ve(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1)})),["tabIndex","crossOrigin"].forEach((function(e){ye[e]=new ve(e,1,!1,e.toLowerCase(),null,!1)})),ye.xlinkHref=new ve("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0),["src","href","action","formAction"].forEach((function(e){ye[e]=new ve(e,1,!1,e.toLowerCase(),null,!0)}));var De="http://www.w3.org/1999/xhtml",Ae="http://www.w3.org/2000/svg";function je(e){switch(e){case"svg":return"http://www.w3.org/2000/svg";case"math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function We(e,t){return null==e||"http://www.w3.org/1999/xhtml"===e?je(t):"http://www.w3.org/2000/svg"===e&&"foreignObject"===t?"http://www.w3.org/1999/xhtml":e}var Ve,Be=function(e){return"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(t,n,r,l){MSApp.execUnsafeLocalFunction((function(){return e(t,n)}))}:e}((function(e,t){if(e.namespaceURI!==Ae||"innerHTML"in e)e.innerHTML=t;else{for((Ve=Ve||document.createElement("div")).innerHTML="",t=Ve.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}}));function He(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t}function $e(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var Qe={animationend:$e("Animation","AnimationEnd"),animationiteration:$e("Animation","AnimationIteration"),animationstart:$e("Animation","AnimationStart"),transitionend:$e("Transition","TransitionEnd")},Ke={},qe={};function Ye(e){if(Ke[e])return Ke[e];if(!Qe[e])return e;var t,n=Qe[e];for(t in n)if(n.hasOwnProperty(t)&&t in qe)return Ke[e]=n[t];return e}J&&(qe=document.createElement("div").style,"AnimationEvent"in window||(delete Qe.animationend.animation,delete Qe.animationiteration.animation,delete Qe.animationstart.animation),"TransitionEvent"in window||delete Qe.transitionend.transition);var Xe=Ye("animationend"),Ge=Ye("animationiteration"),Ze=Ye("animationstart"),Je=Ye("transitionend"),et="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting".split(" ");function tt(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do{0!=(1026&(t=e).effectTag)&&(n=t.return),e=t.return}while(e)}return 3===t.tag?n:null}function nt(e){if(13===e.tag){var t=e.memoizedState;if(null===t&&(null!==(e=e.alternate)&&(t=e.memoizedState)),null!==t)return t.dehydrated}return null}function rt(e){if(tt(e)!==e)throw Error(o(188))}function lt(e){if(!(e=function(e){var t=e.alternate;if(!t){if(null===(t=tt(e)))throw Error(o(188));return t!==e?null:e}for(var n=e,r=t;;){var l=n.return;if(null===l)break;var a=l.alternate;if(null===a){if(null!==(r=l.return)){n=r;continue}break}if(l.child===a.child){for(a=l.child;a;){if(a===n)return rt(l),e;if(a===r)return rt(l),t;a=a.sibling}throw Error(o(188))}if(n.return!==r.return)n=l,r=a;else{for(var i=!1,u=l.child;u;){if(u===n){i=!0,n=l,r=a;break}if(u===r){i=!0,r=l,n=a;break}u=u.sibling}if(!i){for(u=a.child;u;){if(u===n){i=!0,n=a,r=l;break}if(u===r){i=!0,r=a,n=l;break}u=u.sibling}if(!i)throw Error(o(189))}}if(n.alternate!==r)throw Error(o(190))}if(3!==n.tag)throw Error(o(188));return n.stateNode.current===n?e:t}(e)))return null;for(var t=e;;){if(5===t.tag||6===t.tag)return t;if(t.child)t.child.return=t,t=t.child;else{if(t===e)break;for(;!t.sibling;){if(!t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}}return null}var at,ot,it,ut=!1,ct=[],st=null,ft=null,dt=null,pt=new Map,mt=new Map,ht=[],gt="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput close cancel copy cut paste click change contextmenu reset submit".split(" "),vt="focus blur dragenter dragleave mouseover mouseout pointerover pointerout gotpointercapture lostpointercapture".split(" ");function yt(e,t,n,r){return{blockedOn:e,topLevelType:t,eventSystemFlags:32|n,nativeEvent:r}}function bt(e,t){switch(e){case"focus":case"blur":st=null;break;case"dragenter":case"dragleave":ft=null;break;case"mouseover":case"mouseout":dt=null;break;case"pointerover":case"pointerout":pt.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":mt.delete(t.pointerId)}}function wt(e,t,n,r,l){return null===e||e.nativeEvent!==l?(e=yt(t,n,r,l),null!==t&&(null!==(t=cr(t))&&ot(t)),e):(e.eventSystemFlags|=r,e)}function kt(e){var t=ur(e.target);if(null!==t){var n=tt(t);if(null!==n)if(13===(t=n.tag)){if(null!==(t=nt(n)))return e.blockedOn=t,void a.unstable_runWithPriority(e.priority,(function(){it(n)}))}else if(3===t&&n.stateNode.hydrate)return void(e.blockedOn=3===n.tag?n.stateNode.containerInfo:null)}e.blockedOn=null}function Et(e){if(null!==e.blockedOn)return!1;var t=Rn(e.topLevelType,e.eventSystemFlags,e.nativeEvent);if(null!==t){var n=cr(t);return null!==n&&ot(n),e.blockedOn=t,!1}return!0}function xt(e,t,n){Et(e)&&n.delete(t)}function St(){for(ut=!1;0this.eventPool.length&&this.eventPool.push(e)}function At(e){e.eventPool=[],e.getPooled=Ut,e.release=Dt}l(Lt.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=It)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=It)},persist:function(){this.isPersistent=It},isPersistent:Ft,destructor:function(){var e,t=this.constructor.Interface;for(e in t)this[e]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null,this.isPropagationStopped=this.isDefaultPrevented=Ft,this._dispatchInstances=this._dispatchListeners=null}}),Lt.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null},Lt.extend=function(e){function t(){}function n(){return r.apply(this,arguments)}var r=this;t.prototype=r.prototype;var a=new t;return l(a,n.prototype),n.prototype=a,n.prototype.constructor=n,n.Interface=l({},r.Interface,e),n.extend=r.extend,At(n),n},At(Lt);var jt=Lt.extend({animationName:null,elapsedTime:null,pseudoElement:null}),Wt=Lt.extend({clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}}),Vt=Lt.extend({view:null,detail:null}),Bt=Vt.extend({relatedTarget:null});function Ht(e){var t=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&13===t&&(e=13):e=t,10===e&&(e=13),32<=e||13===e?e:0}var $t={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},Qt={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},Kt={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function qt(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):!!(e=Kt[e])&&!!t[e]}function Yt(){return qt}for(var Xt=Vt.extend({key:function(e){if(e.key){var t=$t[e.key]||e.key;if("Unidentified"!==t)return t}return"keypress"===e.type?13===(e=Ht(e))?"Enter":String.fromCharCode(e):"keydown"===e.type||"keyup"===e.type?Qt[e.keyCode]||"Unidentified":""},location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:Yt,charCode:function(e){return"keypress"===e.type?Ht(e):0},keyCode:function(e){return"keydown"===e.type||"keyup"===e.type?e.keyCode:0},which:function(e){return"keypress"===e.type?Ht(e):"keydown"===e.type||"keyup"===e.type?e.keyCode:0}}),Gt=0,Zt=0,Jt=!1,en=!1,tn=Vt.extend({screenX:null,screenY:null,clientX:null,clientY:null,pageX:null,pageY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:Yt,button:null,buttons:null,relatedTarget:function(e){return e.relatedTarget||(e.fromElement===e.srcElement?e.toElement:e.fromElement)},movementX:function(e){if("movementX"in e)return e.movementX;var t=Gt;return Gt=e.screenX,Jt?"mousemove"===e.type?e.screenX-t:0:(Jt=!0,0)},movementY:function(e){if("movementY"in e)return e.movementY;var t=Zt;return Zt=e.screenY,en?"mousemove"===e.type?e.screenY-t:0:(en=!0,0)}}),nn=tn.extend({pointerId:null,width:null,height:null,pressure:null,tangentialPressure:null,tiltX:null,tiltY:null,twist:null,pointerType:null,isPrimary:null}),rn=tn.extend({dataTransfer:null}),ln=Vt.extend({touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:Yt}),an=Lt.extend({propertyName:null,elapsedTime:null,pseudoElement:null}),on=tn.extend({deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:null,deltaMode:null}),un=[["blur","blur",0],["cancel","cancel",0],["click","click",0],["close","close",0],["contextmenu","contextMenu",0],["copy","copy",0],["cut","cut",0],["auxclick","auxClick",0],["dblclick","doubleClick",0],["dragend","dragEnd",0],["dragstart","dragStart",0],["drop","drop",0],["focus","focus",0],["input","input",0],["invalid","invalid",0],["keydown","keyDown",0],["keypress","keyPress",0],["keyup","keyUp",0],["mousedown","mouseDown",0],["mouseup","mouseUp",0],["paste","paste",0],["pause","pause",0],["play","play",0],["pointercancel","pointerCancel",0],["pointerdown","pointerDown",0],["pointerup","pointerUp",0],["ratechange","rateChange",0],["reset","reset",0],["seeked","seeked",0],["submit","submit",0],["touchcancel","touchCancel",0],["touchend","touchEnd",0],["touchstart","touchStart",0],["volumechange","volumeChange",0],["drag","drag",1],["dragenter","dragEnter",1],["dragexit","dragExit",1],["dragleave","dragLeave",1],["dragover","dragOver",1],["mousemove","mouseMove",1],["mouseout","mouseOut",1],["mouseover","mouseOver",1],["pointermove","pointerMove",1],["pointerout","pointerOut",1],["pointerover","pointerOver",1],["scroll","scroll",1],["toggle","toggle",1],["touchmove","touchMove",1],["wheel","wheel",1],["abort","abort",2],[Xe,"animationEnd",2],[Ge,"animationIteration",2],[Ze,"animationStart",2],["canplay","canPlay",2],["canplaythrough","canPlayThrough",2],["durationchange","durationChange",2],["emptied","emptied",2],["encrypted","encrypted",2],["ended","ended",2],["error","error",2],["gotpointercapture","gotPointerCapture",2],["load","load",2],["loadeddata","loadedData",2],["loadedmetadata","loadedMetadata",2],["loadstart","loadStart",2],["lostpointercapture","lostPointerCapture",2],["playing","playing",2],["progress","progress",2],["seeking","seeking",2],["stalled","stalled",2],["suspend","suspend",2],["timeupdate","timeUpdate",2],[Je,"transitionEnd",2],["waiting","waiting",2]],cn={},sn={},fn=0;fn=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=Qn(r)}}function qn(){for(var e=window,t=$n();t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(e){n=!1}if(!n)break;t=$n((e=t.contentWindow).document)}return t}function Yn(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var Xn=null,Gn=null;function Zn(e,t){switch(e){case"button":case"input":case"select":case"textarea":return!!t.autoFocus}return!1}function Jn(e,t){return"textarea"===e||"option"===e||"noscript"===e||"string"==typeof t.children||"number"==typeof t.children||"object"==typeof t.dangerouslySetInnerHTML&&null!==t.dangerouslySetInnerHTML&&null!=t.dangerouslySetInnerHTML.__html}var er="function"==typeof setTimeout?setTimeout:void 0,tr="function"==typeof clearTimeout?clearTimeout:void 0;function nr(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break}return e}function rr(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("$"===n||"$!"===n||"$?"===n){if(0===t)return e;t--}else"/$"===n&&t++}e=e.previousSibling}return null}var lr=Math.random().toString(36).slice(2),ar="__reactInternalInstance$"+lr,or="__reactEventHandlers$"+lr,ir="__reactContainere$"+lr;function ur(e){var t=e[ar];if(t)return t;for(var n=e.parentNode;n;){if(t=n[ir]||n[ar]){if(n=t.alternate,null!==t.child||null!==n&&null!==n.child)for(e=rr(e);null!==e;){if(n=e[ar])return n;e=rr(e)}return t}n=(e=n).parentNode}return null}function cr(e){return!(e=e[ar]||e[ir])||5!==e.tag&&6!==e.tag&&13!==e.tag&&3!==e.tag?null:e}function sr(e){if(5===e.tag||6===e.tag)return e.stateNode;throw Error(o(33))}function fr(e){return e[or]||null}var dr=null,pr=null,mr=null;function hr(){if(mr)return mr;var e,t,n=pr,r=n.length,l="value"in dr?dr.value:dr.textContent,a=l.length;for(e=0;e=wr),xr=String.fromCharCode(32),Sr={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},Tr=!1;function Cr(e,t){switch(e){case"keyup":return-1!==yr.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"blur":return!0;default:return!1}}function _r(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var Pr=!1;var Nr={eventTypes:Sr,extractEvents:function(e,t,n,r){var l;if(br)e:{switch(e){case"compositionstart":var a=Sr.compositionStart;break e;case"compositionend":a=Sr.compositionEnd;break e;case"compositionupdate":a=Sr.compositionUpdate;break e}a=void 0}else Pr?Cr(e,n)&&(a=Sr.compositionEnd):"keydown"===e&&229===n.keyCode&&(a=Sr.compositionStart);return a?(Er&&"ko"!==n.locale&&(Pr||a!==Sr.compositionStart?a===Sr.compositionEnd&&Pr&&(l=hr()):(pr="value"in(dr=r)?dr.value:dr.textContent,Pr=!0)),a=gr.getPooled(a,t,n,r),l?a.data=l:null!==(l=_r(n))&&(a.data=l),Mt(a),l=a):l=null,(e=kr?function(e,t){switch(e){case"compositionend":return _r(t);case"keypress":return 32!==t.which?null:(Tr=!0,xr);case"textInput":return(e=t.data)===xr&&Tr?null:e;default:return null}}(e,n):function(e,t){if(Pr)return"compositionend"===e||!br&&Cr(e,t)?(e=hr(),mr=pr=dr=null,Pr=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=document.documentMode,el={select:{phasedRegistrationNames:{bubbled:"onSelect",captured:"onSelectCapture"},dependencies:"blur contextmenu dragend focus keydown keyup mousedown mouseup selectionchange".split(" ")}},tl=null,nl=null,rl=null,ll=!1;function al(e,t){var n=t.window===t?t.document:9===t.nodeType?t:t.ownerDocument;return ll||null==tl||tl!==$n(n)?null:("selectionStart"in(n=tl)&&Yn(n)?n={start:n.selectionStart,end:n.selectionEnd}:n={anchorNode:(n=(n.ownerDocument&&n.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:n.anchorOffset,focusNode:n.focusNode,focusOffset:n.focusOffset},rl&&Zr(rl,n)?null:(rl=n,(e=Lt.getPooled(el.select,nl,e,t)).type="select",e.target=tl,Mt(e),e))}var ol={eventTypes:el,extractEvents:function(e,t,n,r){var l,a=r.window===r?r.document:9===r.nodeType?r:r.ownerDocument;if(!(l=!a)){e:{a=In(a),l=m.onSelect;for(var o=0;oul||(e.current=il[ul],il[ul]=null,ul--)}function sl(e,t){ul++,il[ul]=e.current,e.current=t}var fl={},dl={current:fl},pl={current:!1},ml=fl;function hl(e,t){var n=e.type.contextTypes;if(!n)return fl;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l,a={};for(l in n)a[l]=t[l];return r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=a),a}function gl(e){return null!=(e=e.childContextTypes)}function vl(e){cl(pl),cl(dl)}function yl(e){cl(pl),cl(dl)}function bl(e,t,n){if(dl.current!==fl)throw Error(o(168));sl(dl,t),sl(pl,n)}function wl(e,t,n){var r=e.stateNode;if(e=t.childContextTypes,"function"!=typeof r.getChildContext)return n;for(var a in r=r.getChildContext())if(!(a in e))throw Error(o(108,G(t)||"Unknown",a));return l({},n,{},r)}function kl(e){var t=e.stateNode;return t=t&&t.__reactInternalMemoizedMergedChildContext||fl,ml=dl.current,sl(dl,t),sl(pl,pl.current),!0}function El(e,t,n){var r=e.stateNode;if(!r)throw Error(o(169));n?(t=wl(e,t,ml),r.__reactInternalMemoizedMergedChildContext=t,cl(pl),cl(dl),sl(dl,t)):cl(pl),sl(pl,n)}var xl=a.unstable_runWithPriority,Sl=a.unstable_scheduleCallback,Tl=a.unstable_cancelCallback,Cl=a.unstable_shouldYield,_l=a.unstable_requestPaint,Pl=a.unstable_now,Nl=a.unstable_getCurrentPriorityLevel,Ol=a.unstable_ImmediatePriority,Rl=a.unstable_UserBlockingPriority,zl=a.unstable_NormalPriority,Ml=a.unstable_LowPriority,Il=a.unstable_IdlePriority,Fl={},Ll=void 0!==_l?_l:function(){},Ul=null,Dl=null,Al=!1,jl=Pl(),Wl=1e4>jl?Pl:function(){return Pl()-jl};function Vl(){switch(Nl()){case Ol:return 99;case Rl:return 98;case zl:return 97;case Ml:return 96;case Il:return 95;default:throw Error(o(332))}}function Bl(e){switch(e){case 99:return Ol;case 98:return Rl;case 97:return zl;case 96:return Ml;case 95:return Il;default:throw Error(o(332))}}function Hl(e,t){return e=Bl(e),xl(e,t)}function $l(e,t,n){return e=Bl(e),Sl(e,t,n)}function Ql(e){return null===Ul?(Ul=[e],Dl=Sl(Ol,ql)):Ul.push(e),Fl}function Kl(){if(null!==Dl){var e=Dl;Dl=null,Tl(e)}ql()}function ql(){if(!Al&&null!==Ul){Al=!0;var e=0;try{var t=Ul;Hl(99,(function(){for(;e=t&&(Wo=!0),e.firstContext=null)}function ia(e,t){if(ta!==e&&!1!==t&&0!==t)if("number"==typeof t&&1073741823!==t||(ta=e,t=1073741823),t={context:e,observedBits:t,next:null},null===ea){if(null===Jl)throw Error(o(308));ea=t,Jl.dependencies={expirationTime:0,firstContext:t,responders:null}}else ea=ea.next=t;return e._currentValue}var ua=!1;function ca(e){return{baseState:e,firstUpdate:null,lastUpdate:null,firstCapturedUpdate:null,lastCapturedUpdate:null,firstEffect:null,lastEffect:null,firstCapturedEffect:null,lastCapturedEffect:null}}function sa(e){return{baseState:e.baseState,firstUpdate:e.firstUpdate,lastUpdate:e.lastUpdate,firstCapturedUpdate:null,lastCapturedUpdate:null,firstEffect:null,lastEffect:null,firstCapturedEffect:null,lastCapturedEffect:null}}function fa(e,t){return{expirationTime:e,suspenseConfig:t,tag:0,payload:null,callback:null,next:null,nextEffect:null}}function da(e,t){null===e.lastUpdate?e.firstUpdate=e.lastUpdate=t:(e.lastUpdate.next=t,e.lastUpdate=t)}function pa(e,t){var n=e.alternate;if(null===n){var r=e.updateQueue,l=null;null===r&&(r=e.updateQueue=ca(e.memoizedState))}else r=e.updateQueue,l=n.updateQueue,null===r?null===l?(r=e.updateQueue=ca(e.memoizedState),l=n.updateQueue=ca(n.memoizedState)):r=e.updateQueue=sa(l):null===l&&(l=n.updateQueue=sa(r));null===l||r===l?da(r,t):null===r.lastUpdate||null===l.lastUpdate?(da(r,t),da(l,t)):(da(r,t),l.lastUpdate=t)}function ma(e,t){var n=e.updateQueue;null===(n=null===n?e.updateQueue=ca(e.memoizedState):ha(e,n)).lastCapturedUpdate?n.firstCapturedUpdate=n.lastCapturedUpdate=t:(n.lastCapturedUpdate.next=t,n.lastCapturedUpdate=t)}function ha(e,t){var n=e.alternate;return null!==n&&t===n.updateQueue&&(t=e.updateQueue=sa(t)),t}function ga(e,t,n,r,a,o){switch(n.tag){case 1:return"function"==typeof(e=n.payload)?e.call(o,r,a):e;case 3:e.effectTag=-4097&e.effectTag|64;case 0:if(null==(a="function"==typeof(e=n.payload)?e.call(o,r,a):e))break;return l({},r,a);case 2:ua=!0}return r}function va(e,t,n,r,l){ua=!1;for(var a=(t=ha(e,t)).baseState,o=null,i=0,u=t.firstUpdate,c=a;null!==u;){var s=u.expirationTime;sh?(g=f,f=null):g=f.sibling;var v=p(l,f,i[h],u);if(null===v){null===f&&(f=g);break}e&&f&&null===v.alternate&&t(l,f),o=a(v,o,h),null===s?c=v:s.sibling=v,s=v,f=g}if(h===i.length)return n(l,f),c;if(null===f){for(;hg?(v=h,h=null):v=h.sibling;var b=p(l,h,y.value,c);if(null===b){null===h&&(h=v);break}e&&h&&null===b.alternate&&t(l,h),i=a(b,i,g),null===f?s=b:f.sibling=b,f=b,h=v}if(y.done)return n(l,h),s;if(null===h){for(;!y.done;g++,y=u.next())null!==(y=d(l,y.value,c))&&(i=a(y,i,g),null===f?s=y:f.sibling=y,f=y);return s}for(h=r(l,h);!y.done;g++,y=u.next())null!==(y=m(h,l,g,y.value,c))&&(e&&null!==y.alternate&&h.delete(null===y.key?g:y.key),i=a(y,i,g),null===f?s=y:f.sibling=y,f=y);return e&&h.forEach((function(e){return t(l,e)})),s}return function(e,r,a,u){var c="object"==typeof a&&null!==a&&a.type===D&&null===a.key;c&&(a=a.props.children);var s="object"==typeof a&&null!==a;if(s)switch(a.$$typeof){case L:e:{for(s=a.key,c=r;null!==c;){if(c.key===s){if(7===c.tag?a.type===D:c.elementType===a.type){n(e,c.sibling),(r=l(c,a.type===D?a.props.children:a.props)).ref=Na(e,c,a),r.return=e,e=r;break e}n(e,c);break}t(e,c),c=c.sibling}a.type===D?((r=Iu(a.props.children,e.mode,u,a.key)).return=e,e=r):((u=Mu(a.type,a.key,a.props,null,e.mode,u)).ref=Na(e,r,a),u.return=e,e=u)}return i(e);case U:e:{for(c=a.key;null!==r;){if(r.key===c){if(4===r.tag&&r.stateNode.containerInfo===a.containerInfo&&r.stateNode.implementation===a.implementation){n(e,r.sibling),(r=l(r,a.children||[])).return=e,e=r;break e}n(e,r);break}t(e,r),r=r.sibling}(r=Lu(a,e.mode,u)).return=e,e=r}return i(e)}if("string"==typeof a||"number"==typeof a)return a=""+a,null!==r&&6===r.tag?(n(e,r.sibling),(r=l(r,a)).return=e,e=r):(n(e,r),(r=Fu(a,e.mode,u)).return=e,e=r),i(e);if(Pa(a))return h(e,r,a,u);if(X(a))return g(e,r,a,u);if(s&&Oa(e,a),void 0===a&&!c)switch(e.tag){case 1:case 0:throw e=e.type,Error(o(152,e.displayName||e.name||"Component"))}return n(e,r)}}var za=Ra(!0),Ma=Ra(!1),Ia={},Fa={current:Ia},La={current:Ia},Ua={current:Ia};function Da(e){if(e===Ia)throw Error(o(174));return e}function Aa(e,t){sl(Ua,t),sl(La,e),sl(Fa,Ia);var n=t.nodeType;switch(n){case 9:case 11:t=(t=t.documentElement)?t.namespaceURI:We(null,"");break;default:t=We(t=(n=8===n?t.parentNode:t).namespaceURI||null,n=n.tagName)}cl(Fa),sl(Fa,t)}function ja(e){cl(Fa),cl(La),cl(Ua)}function Wa(e){Da(Ua.current);var t=Da(Fa.current),n=We(t,e.type);t!==n&&(sl(La,e),sl(Fa,n))}function Va(e){La.current===e&&(cl(Fa),cl(La))}var Ba={current:0};function Ha(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||"$?"===n.data||"$!"===n.data))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(0!=(64&t.effectTag))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function $a(e,t){return{responder:e,props:t}}var Qa=M.ReactCurrentDispatcher,Ka=M.ReactCurrentBatchConfig,qa=0,Ya=null,Xa=null,Ga=null,Za=null,Ja=null,eo=null,to=0,no=null,ro=0,lo=!1,ao=null,oo=0;function io(){throw Error(o(321))}function uo(e,t){if(null===t)return!1;for(var n=0;nto&&du(to=f)):(fu(f,c.suspenseConfig),a=c.eagerReducer===e?c.eagerState:e(a,c.action)),i=c,c=c.next}while(null!==c&&c!==r);s||(u=i,l=a),Xr(a,t.memoizedState)||(Wo=!0),t.memoizedState=a,t.baseUpdate=u,t.baseState=l,n.lastRenderedState=a}return[t.memoizedState,n.dispatch]}function go(e){var t=fo();return"function"==typeof e&&(e=e()),t.memoizedState=t.baseState=e,e=(e=t.queue={last:null,dispatch:null,lastRenderedReducer:mo,lastRenderedState:e}).dispatch=_o.bind(null,Ya,e),[t.memoizedState,e]}function vo(e){return ho(mo)}function yo(e,t,n,r){return e={tag:e,create:t,destroy:n,deps:r,next:null},null===no?(no={lastEffect:null}).lastEffect=e.next=e:null===(t=no.lastEffect)?no.lastEffect=e.next=e:(n=t.next,t.next=e,e.next=n,no.lastEffect=e),e}function bo(e,t,n,r){var l=fo();ro|=e,l.memoizedState=yo(t,n,void 0,void 0===r?null:r)}function wo(e,t,n,r){var l=po();r=void 0===r?null:r;var a=void 0;if(null!==Xa){var o=Xa.memoizedState;if(a=o.destroy,null!==r&&uo(r,o.deps))return void yo(0,n,a,r)}ro|=e,l.memoizedState=yo(t,n,a,r)}function ko(e,t){return bo(516,192,e,t)}function Eo(e,t){return wo(516,192,e,t)}function xo(e,t){return"function"==typeof t?(e=e(),t(e),function(){t(null)}):null!=t?(e=e(),t.current=e,function(){t.current=null}):void 0}function So(){}function To(e,t){return fo().memoizedState=[e,void 0===t?null:t],e}function Co(e,t){var n=po();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&uo(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function _o(e,t,n){if(!(25>oo))throw Error(o(301));var r=e.alternate;if(e===Ya||null!==r&&r===Ya)if(lo=!0,e={expirationTime:qa,suspenseConfig:null,action:n,eagerReducer:null,eagerState:null,next:null},null===ao&&(ao=new Map),void 0===(n=ao.get(t)))ao.set(t,e);else{for(t=n;null!==t.next;)t=t.next;t.next=e}else{var l=Zi(),a=wa.suspense;a={expirationTime:l=Ji(l,e,a),suspenseConfig:a,action:n,eagerReducer:null,eagerState:null,next:null};var i=t.last;if(null===i)a.next=a;else{var u=i.next;null!==u&&(a.next=u),i.next=a}if(t.last=a,0===e.expirationTime&&(null===r||0===r.expirationTime)&&null!==(r=t.lastRenderedReducer))try{var c=t.lastRenderedState,s=r(c,n);if(a.eagerReducer=r,a.eagerState=s,Xr(s,c))return}catch(e){}eu(e,l)}}var Po={readContext:ia,useCallback:io,useContext:io,useEffect:io,useImperativeHandle:io,useLayoutEffect:io,useMemo:io,useReducer:io,useRef:io,useState:io,useDebugValue:io,useResponder:io,useDeferredValue:io,useTransition:io},No={readContext:ia,useCallback:To,useContext:ia,useEffect:ko,useImperativeHandle:function(e,t,n){return n=null!=n?n.concat([e]):null,bo(4,36,xo.bind(null,t,e),n)},useLayoutEffect:function(e,t){return bo(4,36,e,t)},useMemo:function(e,t){var n=fo();return t=void 0===t?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=fo();return t=void 0!==n?n(t):t,r.memoizedState=r.baseState=t,e=(e=r.queue={last:null,dispatch:null,lastRenderedReducer:e,lastRenderedState:t}).dispatch=_o.bind(null,Ya,e),[r.memoizedState,e]},useRef:function(e){return e={current:e},fo().memoizedState=e},useState:go,useDebugValue:So,useResponder:$a,useDeferredValue:function(e,t){var n=go(e),r=n[0],l=n[1];return ko((function(){a.unstable_next((function(){var n=Ka.suspense;Ka.suspense=void 0===t?null:t;try{l(e)}finally{Ka.suspense=n}}))}),[e,t]),r},useTransition:function(e){var t=go(!1),n=t[0],r=t[1];return[To((function(t){r(!0),a.unstable_next((function(){var n=Ka.suspense;Ka.suspense=void 0===e?null:e;try{r(!1),t()}finally{Ka.suspense=n}}))}),[e,n]),n]}},Oo={readContext:ia,useCallback:Co,useContext:ia,useEffect:Eo,useImperativeHandle:function(e,t,n){return n=null!=n?n.concat([e]):null,wo(4,36,xo.bind(null,t,e),n)},useLayoutEffect:function(e,t){return wo(4,36,e,t)},useMemo:function(e,t){var n=po();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&uo(t,r[1])?r[0]:(e=e(),n.memoizedState=[e,t],e)},useReducer:ho,useRef:function(){return po().memoizedState},useState:vo,useDebugValue:So,useResponder:$a,useDeferredValue:function(e,t){var n=vo(),r=n[0],l=n[1];return Eo((function(){a.unstable_next((function(){var n=Ka.suspense;Ka.suspense=void 0===t?null:t;try{l(e)}finally{Ka.suspense=n}}))}),[e,t]),r},useTransition:function(e){var t=vo(),n=t[0],r=t[1];return[Co((function(t){r(!0),a.unstable_next((function(){var n=Ka.suspense;Ka.suspense=void 0===e?null:e;try{r(!1),t()}finally{Ka.suspense=n}}))}),[e,n]),n]}},Ro=null,zo=null,Mo=!1;function Io(e,t){var n=Ou(5,null,null,0);n.elementType="DELETED",n.type="DELETED",n.stateNode=t,n.return=e,n.effectTag=8,null!==e.lastEffect?(e.lastEffect.nextEffect=n,e.lastEffect=n):e.firstEffect=e.lastEffect=n}function Fo(e,t){switch(e.tag){case 5:var n=e.type;return null!==(t=1!==t.nodeType||n.toLowerCase()!==t.nodeName.toLowerCase()?null:t)&&(e.stateNode=t,!0);case 6:return null!==(t=""===e.pendingProps||3!==t.nodeType?null:t)&&(e.stateNode=t,!0);case 13:default:return!1}}function Lo(e){if(Mo){var t=zo;if(t){var n=t;if(!Fo(e,t)){if(!(t=nr(n.nextSibling))||!Fo(e,t))return e.effectTag=-1025&e.effectTag|2,Mo=!1,void(Ro=e);Io(Ro,n)}Ro=e,zo=nr(t.firstChild)}else e.effectTag=-1025&e.effectTag|2,Mo=!1,Ro=e}}function Uo(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag&&13!==e.tag;)e=e.return;Ro=e}function Do(e){if(e!==Ro)return!1;if(!Mo)return Uo(e),Mo=!0,!1;var t=e.type;if(5!==e.tag||"head"!==t&&"body"!==t&&!Jn(t,e.memoizedProps))for(t=zo;t;)Io(e,t),t=nr(t.nextSibling);if(Uo(e),13===e.tag){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(o(317));e:{for(e=e.nextSibling,t=0;e;){if(8===e.nodeType){var n=e.data;if("/$"===n){if(0===t){zo=nr(e.nextSibling);break e}t--}else"$"!==n&&"$!"!==n&&"$?"!==n||t++}e=e.nextSibling}zo=null}}else zo=Ro?nr(e.stateNode.nextSibling):null;return!0}function Ao(){zo=Ro=null,Mo=!1}var jo=M.ReactCurrentOwner,Wo=!1;function Vo(e,t,n,r){t.child=null===e?Ma(t,null,n,r):za(t,e.child,n,r)}function Bo(e,t,n,r,l){n=n.render;var a=t.ref;return oa(t,l),r=co(e,t,n,r,a,l),null===e||Wo?(t.effectTag|=1,Vo(e,t,r,l),t.child):(t.updateQueue=e.updateQueue,t.effectTag&=-517,e.expirationTime<=l&&(e.expirationTime=0),ai(e,t,l))}function Ho(e,t,n,r,l,a){if(null===e){var o=n.type;return"function"!=typeof o||Ru(o)||void 0!==o.defaultProps||null!==n.compare||void 0!==n.defaultProps?((e=Mu(n.type,null,r,null,t.mode,a)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=o,$o(e,t,o,r,l,a))}return o=e.child,lt)&&qi.set(e,t))}}function tu(e,t){e.expirationTime(e=e.nextKnownPendingLevel)?t:e:t}function ru(e){if(0!==e.lastExpiredTime)e.callbackExpirationTime=1073741823,e.callbackPriority=99,e.callbackNode=Ql(au.bind(null,e));else{var t=nu(e),n=e.callbackNode;if(0===t)null!==n&&(e.callbackNode=null,e.callbackExpirationTime=0,e.callbackPriority=90);else{var r=Zi();if(1073741823===t?r=99:1===t||2===t?r=95:r=0>=(r=10*(1073741821-t)-10*(1073741821-r))?99:250>=r?98:5250>=r?97:95,null!==n){var l=e.callbackPriority;if(e.callbackExpirationTime===t&&l>=r)return;n!==Fl&&Tl(n)}e.callbackExpirationTime=t,e.callbackPriority=r,t=1073741823===t?Ql(au.bind(null,e)):$l(r,lu.bind(null,e),{timeout:10*(1073741821-t)-Wl()}),e.callbackNode=t}}}function lu(e,t){if(Gi=0,t)return Wu(e,t=Zi()),ru(e),null;var n=nu(e);if(0!==n){if(t=e.callbackNode,0!=(48&Ni))throw Error(o(327));if(ku(),e===Oi&&n===zi||uu(e,n),null!==Ri){var r=Ni;Ni|=16;for(var l=su();;)try{mu();break}catch(t){cu(e,t)}if(na(),Ni=r,_i.current=l,1===Mi)throw t=Ii,uu(e,n),Au(e,n),ru(e),t;if(null===Ri)switch(l=e.finishedWork=e.current.alternate,e.finishedExpirationTime=n,r=Mi,Oi=null,r){case 0:case 1:throw Error(o(345));case 2:Wu(e,2=n){e.lastPingedTime=n,uu(e,n);break}}if(0!==(a=nu(e))&&a!==n)break;if(0!==r&&r!==n){e.lastPingedTime=r;break}e.timeoutHandle=er(yu.bind(null,e),l);break}yu(e);break;case 4:if(Au(e,n),n===(r=e.lastSuspendedTime)&&(e.nextKnownPendingLevel=vu(l)),Ai&&(0===(l=e.lastPingedTime)||l>=n)){e.lastPingedTime=n,uu(e,n);break}if(0!==(l=nu(e))&&l!==n)break;if(0!==r&&r!==n){e.lastPingedTime=r;break}if(1073741823!==Li?r=10*(1073741821-Li)-Wl():1073741823===Fi?r=0:(r=10*(1073741821-Fi)-5e3,0>(r=(l=Wl())-r)&&(r=0),(n=10*(1073741821-n)-l)<(r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Ci(r/1960))-r)&&(r=n)),10=(r=0|i.busyMinDurationMs)?r=0:(l=0|i.busyDelayMs,r=(a=Wl()-(10*(1073741821-a)-(0|i.timeoutMs||5e3)))<=l?0:l+r-a),10 component higher in the tree to provide a loading indicator or placeholder to display."+Z(l))}5!==Mi&&(Mi=2),a=ci(a,l),u=r;do{switch(u.tag){case 3:o=a,u.effectTag|=4096,u.expirationTime=t,ma(u,xi(u,o,t));break e;case 1:o=a;var v=u.type,y=u.stateNode;if(0==(64&u.effectTag)&&("function"==typeof v.getDerivedStateFromError||null!==y&&"function"==typeof y.componentDidCatch&&(null===Hi||!Hi.has(y)))){u.effectTag|=4096,u.expirationTime=t,ma(u,Si(u,o,t));break e}}u=u.return}while(null!==u)}Ri=gu(Ri)}catch(e){t=e;continue}break}}function su(){var e=_i.current;return _i.current=Po,null===e?Po:e}function fu(e,t){eDi&&(Di=e)}function pu(){for(;null!==Ri;)Ri=hu(Ri)}function mu(){for(;null!==Ri&&!Cl();)Ri=hu(Ri)}function hu(e){var t=Ti(e.alternate,e,zi);return e.memoizedProps=e.pendingProps,null===t&&(t=gu(e)),Pi.current=null,t}function gu(e){Ri=e;do{var t=Ri.alternate;if(e=Ri.return,0==(2048&Ri.effectTag)){e:{var n=t,r=zi,a=(t=Ri).pendingProps;switch(t.tag){case 2:case 16:break;case 15:case 0:break;case 1:gl(t.type)&&vl();break;case 3:ja(),yl(),(a=t.stateNode).pendingContext&&(a.context=a.pendingContext,a.pendingContext=null),(null===n||null===n.child)&&Do(t)&&oi(t);break;case 5:Va(t),r=Da(Ua.current);var i=t.type;if(null!==n&&null!=t.stateNode)Zo(n,t,i,a,r),n.ref!==t.ref&&(t.effectTag|=128);else if(a){var u=Da(Fa.current);if(Do(t)){var c=(a=t).stateNode;n=a.type;var s=a.memoizedProps,f=r;switch(c[ar]=a,c[or]=s,i=void 0,r=c,n){case"iframe":case"object":case"embed":Tn("load",r);break;case"video":case"audio":for(c=0;c<\/script>",c=s.removeChild(s.firstChild)):"string"==typeof s.is?c=c.createElement(f,{is:s.is}):(c=c.createElement(f),"select"===f&&(f=c,s.multiple?f.multiple=!0:s.size&&(f.size=s.size))):c=c.createElementNS(u,f),(s=c)[ar]=n,s[or]=a,Go(s,t),t.stateNode=s;var d=r,m=Vn(f=i,n=a);switch(f){case"iframe":case"object":case"embed":Tn("load",s),r=n;break;case"video":case"audio":for(r=0;ra.tailExpiration&&1i&&(i=n),(s=r.childExpirationTime)>i&&(i=s),r=r.sibling;a.childExpirationTime=i}if(null!==t)return t;null!==e&&0==(2048&e.effectTag)&&(null===e.firstEffect&&(e.firstEffect=Ri.firstEffect),null!==Ri.lastEffect&&(null!==e.lastEffect&&(e.lastEffect.nextEffect=Ri.firstEffect),e.lastEffect=Ri.lastEffect),1(e=e.childExpirationTime)?t:e}function yu(e){var t=Vl();return Hl(99,bu.bind(null,e,t)),null}function bu(e,t){do{ku()}while(null!==Qi);if(0!=(48&Ni))throw Error(o(327));var n=e.finishedWork,r=e.finishedExpirationTime;if(null===n)return null;if(e.finishedWork=null,e.finishedExpirationTime=0,n===e.current)throw Error(o(177));e.callbackNode=null,e.callbackExpirationTime=0,e.callbackPriority=90,e.nextKnownPendingLevel=0;var l=vu(n);if(e.firstPendingTime=l,r<=e.lastSuspendedTime?e.firstSuspendedTime=e.lastSuspendedTime=e.nextKnownPendingLevel=0:r<=e.firstSuspendedTime&&(e.firstSuspendedTime=r-1),r<=e.lastPingedTime&&(e.lastPingedTime=0),r<=e.lastExpiredTime&&(e.lastExpiredTime=0),e===Oi&&(Ri=Oi=null,zi=0),1u&&(s=u,u=i,i=s),s=Kn(w,i),f=Kn(w,u),s&&f&&(1!==E.rangeCount||E.anchorNode!==s.node||E.anchorOffset!==s.offset||E.focusNode!==f.node||E.focusOffset!==f.offset)&&((k=k.createRange()).setStart(s.node,s.offset),E.removeAllRanges(),i>u?(E.addRange(k),E.extend(f.node,f.offset)):(k.setEnd(f.node,f.offset),E.addRange(k))))),k=[];for(E=w;E=E.parentNode;)1===E.nodeType&&k.push({element:E,left:E.scrollLeft,top:E.scrollTop});for("function"==typeof w.focus&&w.focus(),w=0;w=n?ti(e,t,n):(sl(Ba,1&Ba.current),null!==(t=ai(e,t,n))?t.sibling:null);sl(Ba,1&Ba.current);break;case 19:if(r=t.childExpirationTime>=n,0!=(64&e.effectTag)){if(r)return li(e,t,n);t.effectTag|=64}if(null!==(l=t.memoizedState)&&(l.rendering=null,l.tail=null),sl(Ba,Ba.current),!r)return null}return ai(e,t,n)}Wo=!1}}else Wo=!1;switch(t.expirationTime=0,t.tag){case 2:if(r=t.type,null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),e=t.pendingProps,l=hl(t,dl.current),oa(t,n),l=co(null,t,r,e,l,n),t.effectTag|=1,"object"==typeof l&&null!==l&&"function"==typeof l.render&&void 0===l.$$typeof){if(t.tag=1,so(),gl(r)){var a=!0;kl(t)}else a=!1;t.memoizedState=null!==l.state&&void 0!==l.state?l.state:null;var i=r.getDerivedStateFromProps;"function"==typeof i&&Ea(t,r,i,e),l.updater=xa,t.stateNode=l,l._reactInternalFiber=t,_a(t,r,e,n),t=Yo(null,t,r,!0,a,n)}else t.tag=0,Vo(null,t,l,n),t=t.child;return t;case 16:if(l=t.elementType,null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),e=t.pendingProps,function(e){if(-1===e._status){e._status=0;var t=e._ctor;t=t(),e._result=t,t.then((function(t){0===e._status&&(t=t.default,e._status=1,e._result=t)}),(function(t){0===e._status&&(e._status=2,e._result=t)}))}}(l),1!==l._status)throw l._result;switch(l=l._result,t.type=l,a=t.tag=function(e){if("function"==typeof e)return Ru(e)?1:0;if(null!=e){if((e=e.$$typeof)===H)return 11;if(e===K)return 14}return 2}(l),e=Gl(l,e),a){case 0:t=Ko(null,t,l,e,n);break;case 1:t=qo(null,t,l,e,n);break;case 11:t=Bo(null,t,l,e,n);break;case 14:t=Ho(null,t,l,Gl(l.type,e),r,n);break;default:throw Error(o(306,l,""))}return t;case 0:return r=t.type,l=t.pendingProps,Ko(e,t,r,l=t.elementType===r?l:Gl(r,l),n);case 1:return r=t.type,l=t.pendingProps,qo(e,t,r,l=t.elementType===r?l:Gl(r,l),n);case 3:if(Xo(t),null===(r=t.updateQueue))throw Error(o(282));if(l=null!==(l=t.memoizedState)?l.element:null,va(t,r,t.pendingProps,null,n),(r=t.memoizedState.element)===l)Ao(),t=ai(e,t,n);else{if((l=t.stateNode.hydrate)&&(zo=nr(t.stateNode.containerInfo.firstChild),Ro=t,l=Mo=!0),l)for(n=Ma(t,null,r,n),t.child=n;n;)n.effectTag=-3&n.effectTag|1024,n=n.sibling;else Vo(e,t,r,n),Ao();t=t.child}return t;case 5:return Wa(t),null===e&&Lo(t),r=t.type,l=t.pendingProps,a=null!==e?e.memoizedProps:null,i=l.children,Jn(r,l)?i=null:null!==a&&Jn(r,a)&&(t.effectTag|=16),Qo(e,t),4&t.mode&&1!==n&&l.hidden?(t.expirationTime=t.childExpirationTime=1,t=null):(Vo(e,t,i,n),t=t.child),t;case 6:return null===e&&Lo(t),null;case 13:return ti(e,t,n);case 4:return Aa(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=za(t,null,r,n):Vo(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,Bo(e,t,r,l=t.elementType===r?l:Gl(r,l),n);case 7:return Vo(e,t,t.pendingProps,n),t.child;case 8:case 12:return Vo(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,ra(t,a=l.value),null!==i){var u=i.value;if(0===(a=Xr(u,a)?0:0|("function"==typeof r._calculateChangedBits?r._calculateChangedBits(u,a):1073741823))){if(i.children===l.children&&!pl.current){t=ai(e,t,n);break e}}else for(null!==(u=t.child)&&(u.return=t);null!==u;){var c=u.dependencies;if(null!==c){i=u.child;for(var s=c.firstContext;null!==s;){if(s.context===r&&0!=(s.observedBits&a)){1===u.tag&&((s=fa(n,null)).tag=2,pa(u,s)),u.expirationTime=t&&e<=t}function Au(e,t){var n=e.firstSuspendedTime,r=e.lastSuspendedTime;nt||0===n)&&(e.lastSuspendedTime=t),t<=e.lastPingedTime&&(e.lastPingedTime=0),t<=e.lastExpiredTime&&(e.lastExpiredTime=0)}function ju(e,t){t>e.firstPendingTime&&(e.firstPendingTime=t);var n=e.firstSuspendedTime;0!==n&&(t>=n?e.firstSuspendedTime=e.lastSuspendedTime=e.nextKnownPendingLevel=0:t>=e.lastSuspendedTime&&(e.lastSuspendedTime=t+1),t>e.nextKnownPendingLevel&&(e.nextKnownPendingLevel=t))}function Wu(e,t){var n=e.lastExpiredTime;(0===n||n>t)&&(e.lastExpiredTime=t)}function Vu(e,t,n,r){var l=t.current,a=Zi(),i=wa.suspense;a=Ji(a,l,i);e:if(n){t:{if(tt(n=n._reactInternalFiber)!==n||1!==n.tag)throw Error(o(170));var u=n;do{switch(u.tag){case 3:u=u.stateNode.context;break t;case 1:if(gl(u.type)){u=u.stateNode.__reactInternalMemoizedMergedChildContext;break t}}u=u.return}while(null!==u);throw Error(o(171))}if(1===n.tag){var c=n.type;if(gl(c)){n=wl(n,c,u);break e}}n=u}else n=fl;return null===t.context?t.context=n:t.pendingContext=n,(t=fa(a,i)).payload={element:e},null!==(r=void 0===r?null:r)&&(t.callback=r),pa(l,t),eu(l,a),a}function Bu(e){if(!(e=e.current).child)return null;switch(e.child.tag){case 5:default:return e.child.stateNode}}function Hu(e,t){null!==(e=e.memoizedState)&&null!==e.dehydrated&&e.retryTime=E},i=function(){},t.unstable_forceFrameRate=function(e){0>e||125P(o,n))void 0!==u&&0>P(u,o)?(e[r]=u,e[i]=n,r=i):(e[r]=o,e[a]=n,r=a);else{if(!(void 0!==u&&0>P(u,n)))break e;e[r]=u,e[i]=n,r=i}}}return t}return null}function P(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var N=[],O=[],R=1,z=null,M=3,I=!1,F=!1,L=!1;function U(e){for(var t=C(O);null!==t;){if(null===t.callback)_(O);else{if(!(t.startTime<=e))break;_(O),t.sortIndex=t.expirationTime,T(N,t)}t=C(O)}}function D(e){if(L=!1,U(e),!F)if(null!==C(N))F=!0,r(A);else{var t=C(O);null!==t&&l(D,t.startTime-e)}}function A(e,n){F=!1,L&&(L=!1,a()),I=!0;var r=M;try{for(U(n),z=C(N);null!==z&&(!(z.expirationTime>n)||e&&!o());){var i=z.callback;if(null!==i){z.callback=null,M=z.priorityLevel;var u=i(z.expirationTime<=n);n=t.unstable_now(),"function"==typeof u?z.callback=u:z===C(N)&&_(N),U(n)}else _(N);z=C(N)}if(null!==z)var c=!0;else{var s=C(O);null!==s&&l(D,s.startTime-n),c=!1}return c}finally{z=null,M=r,I=!1}}function j(e){switch(e){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1e4;default:return 5e3}}var W=i;t.unstable_ImmediatePriority=1,t.unstable_UserBlockingPriority=2,t.unstable_NormalPriority=3,t.unstable_IdlePriority=5,t.unstable_LowPriority=4,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=M;M=e;try{return t()}finally{M=n}},t.unstable_next=function(e){switch(M){case 1:case 2:case 3:var t=3;break;default:t=M}var n=M;M=t;try{return e()}finally{M=n}},t.unstable_scheduleCallback=function(e,n,o){var i=t.unstable_now();if("object"==typeof o&&null!==o){var u=o.delay;u="number"==typeof u&&0i?(e.sortIndex=u,T(O,e),null===C(N)&&e===C(O)&&(L?a():L=!0,l(D,u-i))):(e.sortIndex=o,T(N,e),F||I||(F=!0,r(A))),e},t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_wrapCallback=function(e){var t=M;return function(){var n=M;M=t;try{return e.apply(this,arguments)}finally{M=n}}},t.unstable_getCurrentPriorityLevel=function(){return M},t.unstable_shouldYield=function(){var e=t.unstable_now();U(e);var n=C(N);return n!==z&&null!==z&&null!==n&&null!==n.callback&&n.startTime<=e&&n.expirationTime=200&&o.status<300?l(JSON.parse(o.responseText)):a(new Error(o.responseText)))},o.open("GET",""+e+n+t.stringify(r)),o.send()}))},l=function(){function e(e){var t=e.base,n=e.token,r=e.locale,l=e.mediaFilter,a=e.contentFilter,o=e.defaultResults;this.base=t||"https://api.tenor.com/v1",this.token=n||"LIVDSRZULELA",this.locale=r||"en_US",this.contentFilter=a||"mild",this.mediaFilter=l||"minimal",this.defaultResults=o||!1}return e.prototype.autocomplete=function(e){return r(this.base,"/autocomplete",{key:this.token,q:e,limit:1,locale:"en_US"})},e.prototype.search=function(e,t){var n=this.defaultResults&&!e?"/trending":"/search";return r(this.base,n,{key:this.token,q:e,limit:12,locale:this.locale,contentfilter:this.contentFilter,media_filter:this.mediaFilter,ar_range:"all",pos:t})},e.prototype.suggestions=function(e){return r(this.base,"/search_suggestions",{key:this.token,q:e,limit:5,locale:this.locale})},e}();t.default=l},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(0),l=n(11),a=function(e){var t=e.autoComplete,n=e.search,l=n.toLowerCase().replace(/\s/g,""),a=t.toLowerCase().replace(l,"");return r.createElement("div",{className:"react-tenor--autocomplete"},r.createElement("span",{"aria-hidden":!0},n),a)},o=function(){return r.createElement("svg",{className:"react-tenor--spinner",viewBox:"0 0 1024 1024"},r.createElement("path",{d:"M959.6 452.2c-2.8-17.4-6.2-34.6-10.6-51.6-5.6-21.6-12.8-43-21.6-63.6-17.8-42.4-42.2-82-71.8-117.2-32-37.8-70.6-70.4-113.4-95.4-42.2-24.8-88.2-42.4-136.2-52.2-24.8-5-49.8-8-75.2-8.2-19.8-0.2-39.6 0.6-59.2 2.4-51 5-101.4 19.2-147.8 41-39.8 18.8-76.8 43.2-109.6 72.4s-61.4 63.2-84.4 100.6c-25.4 41.6-44.4 87-54.8 134.6-8.4 38-12.4 77.2-10.4 116.2 1.8 37.8 7.6 75.6 19 111.8 7.2 23 15.8 45.4 26.6 67.2 10.6 21.4 23 42 36.8 61.4 27.6 38.6 61.2 72.8 99.6 101 39.2 29 83.4 51.4 129.8 66.2 48.4 15.4 99.8 22.6 150.6 20.8 49.6-1.6 98.8-11.2 145.2-29 44.6-17.2 86.4-41.8 123-72.6 18.4-15.6 34.8-33.2 50.2-51.8 15.6-18.8 29.6-38.6 41.2-60 10-18.4 18.4-37.6 25.6-57 3.6-9.6 7-19.2 9.8-29.2 3-10.6 5.2-21.6 7.2-32.4 3-17 4.2-34.6 2.6-51.8-1.4 7.6-2.6 15-4.4 22.4-2.2 8.6-5 17-8.2 25.2-6.4 17.4-14.4 34.2-22 51-9.8 21.4-21.2 41.8-33.6 61.6-6.4 10.2-13 20.2-20.2 29.8s-15.4 18.8-23.6 27.8-34.2 34.4-54 48.8c-20.2 14.8-41.6 27.8-64.2 38.6-45.2 22-94.6 35.2-144.6 39.6-51.2 4.4-103.4-0.6-152.6-15.2-46.8-13.8-91.2-36.2-130.2-65.6-37.8-28.6-70.6-63.8-96.4-103.6-27-40.6-45.6-86.4-55.8-134.2-2.6-12.4-4.6-25.2-6-37.8-1.2-10.8-2.2-21.8-2.6-32.8-0.6-22.6 0.8-46 4.2-68.4 7.4-49.2 23.4-96.6 48.2-139.8 22-38.6 50.6-73.4 84.2-102.8 33.6-29.6 72-53.4 113.6-70.2 24-9.8 49.2-17 74.8-21.8 13-2.4 26-4.4 39-5.4 6.4-0.6 12.6-0.6 19-1.2 2.6 0 5.2 0.2 7.8 0.2 43.4-0.8 87 4.8 128.4 17.8 44.6 14 86.6 36.6 123 66 38.2 30.8 70 68.8 94 111.6 20.4 36.4 35 75.6 43.8 116.4 2 9.4 3.6 18.8 5.2 28.2 1.4 8.6 6.2 16.6 13.6 21.4 15.6 10.4 37.4 3.4 45-13.4 2.6-5.8 3.4-12 2.4-17.8z"}))},i=function(e){var t=e.autoComplete,n=e.inputRef,l=e.search,i=e.searching,u=e.onSearchChange,c=e.onSearchKeyDown,s=e.placeholder;return r.createElement("div",{className:"react-tenor--search-bar"},r.createElement("input",{ref:n,"aria-label":"Search",className:"react-tenor--search",type:"search",value:l,onChange:u,onKeyDown:c,placeholder:s||"Search Tenor"}),t&&l&&r.createElement(a,{autoComplete:t,search:l}),i&&r.createElement(o,null))},u=function(e){var t=e.suggestion,n=e.onSuggestionClick;return r.createElement("button",{type:"button",onClick:function(){return n(t)}},t)},c=function(e){var t=e.suggestions,n=e.onSuggestionClick;return r.createElement("div",{className:"react-tenor--suggestions"},t.map((function(e){return r.createElement(u,{key:e,suggestion:e,onSuggestionClick:n})})))},s=function(e){var t=e.direction,n=e.onClick;return r.createElement("button",{"aria-label":"Page "+t,className:"react-tenor--page-"+t,type:"button",onClick:n},r.createElement("div",null))},f=function(e){var t=e.results,n=e.onPageLeft,a=e.onPageRight,o=e.onSelect;return r.createElement("div",{className:"react-tenor--results"},t.map((function(e){return r.createElement(l.default,{key:e.id,result:e,onSelect:o})})),r.createElement(s,{direction:"left",onClick:n}),r.createElement(s,{direction:"right",onClick:a}))};t.default=function(e){var t=e.autoComplete,n=e.contentRef,l=e.inputRef,a=e.onPageLeft,o=e.onPageRight,u=e.onSearchChange,s=e.onSearchKeyDown,d=e.onSuggestionClick,p=e.onSelect,m=e.results,h=e.search,g=e.searching,v=e.suggestions,y=e.placeholder,b="react-tenor";return(v.length>0||m.length>0)&&(b+=" react-tenor-active"),r.createElement("div",{className:b,ref:n},r.createElement(i,{autoComplete:t,inputRef:l,search:h,searching:g,onSearchChange:u,onSearchKeyDown:s,placeholder:y}),v.length>0&&r.createElement(c,{suggestions:v,onSuggestionClick:d}),m.length>0&&r.createElement(f,{results:m,onPageLeft:a,onPageRight:o,onSelect:p}))}},function(e,t,n){"use strict";var r,l=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)});Object.defineProperty(t,"__esModule",{value:!0});var a=n(0),o=function(e){function t(t){var n=e.call(this,t)||this;return n.handleClick=function(){var e=n.props,t=e.result;(0,e.onSelect)(t)},n.componentIsMounted=!1,n.image=new Image,n.state={loaded:!1},n}return l(t,e),t.prototype.componentDidMount=function(){var e=this;this.componentIsMounted=!0;var t=this.props.result;this.image.src=t.media[0].tinygif.url,this.image.onload=function(){e.componentIsMounted&&e.setState({loaded:!0})}},t.prototype.componentWillUnmount=function(){this.componentIsMounted=!1},t.prototype.getLabel=function(){return this.props.result.itemurl.replace("https://tenor.com/view/","").replace(/-gif-\d+$/,"").replace(/-/g," ")},t.prototype.render=function(){var e=this.state.loaded;return a.createElement("button",{"aria-label":this.getLabel(),type:"button",onClick:this.handleClick,className:"react-tenor--result"},e&&a.createElement("span",{style:{backgroundImage:"url("+this.image.src+")"}}))},t}(a.Component);t.default=o},function(e,t,n){var r=n(13),l=n(14);"string"==typeof(l=l.__esModule?l.default:l)&&(l=[[e.i,l,""]]);var a={insert:"head",singleton:!1},o=(r(l,a),l.locals?l.locals:{});e.exports=o},function(e,t,n){"use strict";var r,l=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},a=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),o=[];function i(e){for(var t=-1,n=0;n