import { setupRerender } from 'preact/test-utils';
import { createElement, render } from 'preact';
import { setupScratch, teardown } from '../../../test/_util/helpers';

/** @jsx createElement */

// Common behaviors between all effect hooks
export function useEffectAssertions(useEffect, scheduleEffectAssert) {
	/** @type {HTMLDivElement} */
	let scratch;

	/** @type {() => void} */
	let rerender;

	beforeEach(() => {
		scratch = setupScratch();
		rerender = setupRerender();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('performs the effect after every render by default', () => {
		const callback = sinon.spy();

		function Comp() {
			useEffect(callback);
			return null;
		}

		render(<Comp />, scratch);

		return scheduleEffectAssert(() => expect(callback).to.be.calledOnce)
			.then(() => scheduleEffectAssert(() => expect(callback).to.be.calledOnce))
			.then(() => render(<Comp />, scratch))
			.then(() =>
				scheduleEffectAssert(() => expect(callback).to.be.calledTwice)
			);
	});

	it('performs the effect only if one of the inputs changed', () => {
		const callback = sinon.spy();

		function Comp(props) {
			useEffect(callback, [props.a, props.b]);
			return null;
		}

		render(<Comp a={1} b={2} />, scratch);

		return scheduleEffectAssert(() => expect(callback).to.be.calledOnce)
			.then(() => render(<Comp a={1} b={2} />, scratch))
			.then(() => scheduleEffectAssert(() => expect(callback).to.be.calledOnce))
			.then(() => render(<Comp a={2} b={2} />, scratch))
			.then(() =>
				scheduleEffectAssert(() => expect(callback).to.be.calledTwice)
			)
			.then(() => render(<Comp a={2} b={2} />, scratch))
			.then(() =>
				scheduleEffectAssert(() => expect(callback).to.be.calledTwice)
			);
	});

	it('performs the effect at mount time and never again if an empty input Array is passed', () => {
		const callback = sinon.spy();

		function Comp() {
			useEffect(callback, []);
			return null;
		}

		render(<Comp />, scratch);
		render(<Comp />, scratch);

		expect(callback).to.be.calledOnce;

		return scheduleEffectAssert(() => expect(callback).to.be.calledOnce)
			.then(() => render(<Comp />, scratch))
			.then(() =>
				scheduleEffectAssert(() => expect(callback).to.be.calledOnce)
			);
	});

	it('calls the cleanup function followed by the effect after each render', () => {
		const cleanupFunction = sinon.spy();
		const callback = sinon.spy(() => cleanupFunction);

		function Comp() {
			useEffect(callback);
			return null;
		}

		render(<Comp />, scratch);

		return scheduleEffectAssert(() => {
			expect(cleanupFunction).to.be.not.called;
			expect(callback).to.be.calledOnce;
		})
			.then(() => scheduleEffectAssert(() => expect(callback).to.be.calledOnce))
			.then(() => render(<Comp />, scratch))
			.then(() =>
				scheduleEffectAssert(() => {
					expect(cleanupFunction).to.be.calledOnce;
					expect(callback).to.be.calledTwice;
					expect(callback.lastCall.calledAfter(cleanupFunction.lastCall));
				})
			);
	});

	it('cleanups the effect when the component get unmounted if the effect was called before', () => {
		const cleanupFunction = sinon.spy();
		const callback = sinon.spy(() => cleanupFunction);

		function Comp() {
			useEffect(callback);
			return null;
		}

		render(<Comp />, scratch);

		return scheduleEffectAssert(() => {
			render(null, scratch);
			rerender();
			expect(cleanupFunction).to.be.calledOnce;
		});
	});

	it('works with closure effect callbacks capturing props', () => {
		const values = [];

		function Comp(props) {
			useEffect(() => values.push(props.value));
			return null;
		}

		render(<Comp value={1} />, scratch);
		render(<Comp value={2} />, scratch);

		return scheduleEffectAssert(() => expect(values).to.deep.equal([1, 2]));
	});
}


class App extends Component {
componentDidCatch(err) {
spy();
errored = err;
}
render(props, state) {
if (errored) {
return
Error
;

keep keep keep add keep keep keep keep keep

import { computed, observable } from "mobx"
import { observer } from "mobx-react"
import { Component, h } from "preact"
import { RouteChildProps } from "./router"
import { store } from "./store"

export type ProfileProps = RouteChildProps
@observer
export class Profile extends Component<ProfileProps> {
  @observable id = ""
  @observable busy = false

  componentDidMount() {
    this.id = this.props.route
  }

  componentWillReceiveProps(props: ProfileProps) {
    this.id = props.route
  }

  render() {
    const user = this.user
    if (user == null) return null
    return (

          {user.name.first} {user.name.last}

            {user.gender === "female" ? "👩" : "👨"} {user.id}

🖂 {user.email}

          <button
            class={this.busy ? "secondary busy" : "secondary"}
            disabled={this.busy}
            onClick={this.remove}
          >
            Remove contact
          </button>

    )
  }

  @computed get user() {
    return store.users.find(u => u.id === this.id)
  }

  remove = async () => {
    this.busy = true
    await new Promise<void>(cb => setTimeout(cb, 1500))
    store.deleteUser(this.id)
    this.busy = false
  }
}


import { act } from 'preact/test-utils';
import { createElement, render, Fragment, Component } from 'preact';
import { setupScratch, teardown } from '../../../test/_util/helpers';
import { useEffect, useState } from 'preact/hooks';
import { useEffectAssertions } from './useEffectAssertions.test';
import { scheduleEffectAssert } from '../_util/useEffectUtil';

/** @jsx createElement */

describe('useEffect', () => {
	/** @type {HTMLDivElement} */
	let scratch;

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	useEffectAssertions(useEffect, scheduleEffectAssert);

	it('calls the effect immediately if another render is about to start', () => {
		const cleanupFunction = sinon.spy();
		const callback = sinon.spy(() => cleanupFunction);

		function Comp() {
			useEffect(callback);
			return null;
		}

		render(<Comp />, scratch);
		render(<Comp />, scratch);

		expect(cleanupFunction).to.be.not.called;
		expect(callback).to.be.calledOnce;

		render(<Comp />, scratch);

		expect(cleanupFunction).to.be.calledOnce;
		expect(callback).to.be.calledTwice;
	});

	it('cancels the effect when the component get unmounted before it had the chance to run it', () => {
		const cleanupFunction = sinon.spy();
		const callback = sinon.spy(() => cleanupFunction);

		function Comp() {
			useEffect(callback);
			return null;
		}

		render(<Comp />, scratch);
		render(null, scratch);

		return scheduleEffectAssert(() => {
			expect(cleanupFunction).to.not.be.called;
			expect(callback).to.not.be.called;
		});
	});

	it('should execute multiple effects in same component in the right order', () => {
		let executionOrder = [];
		const App = ({ i }) => {
			executionOrder = [];
			useEffect(() => {
				executionOrder.push('action1');
				return () => executionOrder.push('cleanup1');
			}, [i]);
			useEffect(() => {
				executionOrder.push('action2');
				return () => executionOrder.push('cleanup2');
			}, [i]);
			return
Test
;
		};
		act(() => render(<App i={0} />, scratch));
		act(() => render(<App i={2} />, scratch));
		expect(executionOrder).to.deep.equal([
			'cleanup1',
			'cleanup2',
			'action1',
			'action2'
		]);
	});

	it('should throw an error upwards', () => {
		const spy = sinon.spy();
		let errored = false;

		const Page1 = () => {
			const [state, setState] = useState('loading');
			useEffect(() => {
				setState('loaded');
			}, []);
			return
{state}
;
		};

		const Page2 = () => {
			useEffect(() => {
				throw new Error('err');
			}, []);
			return
invisible
;
		};

		class App extends Component {
			componentDidCatch(err) {
				spy();
				errored = err;
			}

			render(props, state) {
				if (errored) {
					return
Error
;
				}

				return <Fragment>{props.page === 1 ? <Page1 /> : <Page2 />}</Fragment>;
			}
		}

		act(() => render(<App page={1} />, scratch));
		expect(spy).to.not.be.called;
		expect(scratch.innerHTML).to.equal('
loaded
');

		act(() => render(<App page={2} />, scratch));
		expect(spy).to.be.calledOnce;
		expect(scratch.innerHTML).to.equal('
Error
');
		errored = false;

		act(() => render(<App page={1} />, scratch));
		expect(spy).to.be.calledOnce;
		expect(scratch.innerHTML).to.equal('
loaded
');
	});

	it('should throw an error upwards from return', () => {
		const spy = sinon.spy();
		let errored = false;

		const Page1 = () => {
			const [state, setState] = useState('loading');
			useEffect(() => {
				setState('loaded');
			}, []);
			return
{state}
;
		};

		const Page2 = () => {
			useEffect(() => {
				return () => {
					throw new Error('err');
				};
			}, []);
			return
Load
;
		};

		class App extends Component {
			componentDidCatch(err) {
				spy();
				errored = err;
			}

			render(props, state) {
				if (errored) {
					return
Error
;
				}

				return <Fragment>{props.page === 1 ? <Page1 /> : <Page2 />}</Fragment>;
			}
		}

		act(() => render(<App page={2} />, scratch));
		expect(scratch.innerHTML).to.equal('
Load
');

		act(() => render(<App page={1} />, scratch));
		expect(spy).to.be.calledOnce;
		expect(scratch.innerHTML).to.equal('
Error
');
	});

	it('catches errors when error is invoked during render', () => {
		const spy = sinon.spy();
		let errored;

		function Comp() {
			useEffect(() => {
				throw new Error('hi');
			});
			return null;
		}

		class App extends Component {
			componentDidCatch(err) {
				spy();
				errored = err;
			}

			render(props, state) {
				if (errored) {
					return
Error
;
				}

				return <Comp />;
			}
		}

		render(<App />, scratch);
		act(() => {
			render(<App />, scratch);
		});
		expect(spy).to.be.calledOnce;
		expect(scratch.innerHTML).to.equal('
Error
');
	});

	it('should allow creating a new root', () => {
		const root = document.createElement('div');
		const global = document.createElement('div');
		scratch.appendChild(root);
		scratch.appendChild(global);

		const Modal = props => {
			let [, setCanProceed] = useState(true);
			let ChildProp = props.content;

			return (

					<ChildProp setCanProceed={setCanProceed} />

			);
		};

		const Inner = () => {
			useEffect(() => {
				render(
global
, global);
			}, []);

			return
Inner
;
		};

		act(() => {
			render(
				<Modal
					content={props => {
						props.setCanProceed(false);
						return <Inner />;
					}}
				/>,
				root
			);
		});

		expect(scratch.innerHTML).to.equal(
			'

Inner

global

'
		);
	});

	it('should not crash when effect returns truthy non-function value', () => {
		const callback = sinon.spy(() => 'truthy');
		function Comp() {
			useEffect(callback);
			return null;
		}

		render(<Comp />, scratch);
		render(<Comp />, scratch);

		expect(callback).to.have.been.calledOnce;

		render(
Replacement
, scratch);
	});
});


import { Component as PreactComponent } from '../../src/internal';
import { Reducer } from '.';

export { PreactContext } from '../../src/internal';

/**
 * The type of arguments passed to a Hook function. While this type is not
 * strictly necessary, they are given a type name to make it easier to read
 * the following types and trace the flow of data.
 */
export type HookArgs = any;

/**
 * The return type of a Hook function. While this type is not
 * strictly necessary, they are given a type name to make it easier to read
 * the following types and trace the flow of data.
 */
export type HookReturnValue = any;

/** The public function a user invokes to use a Hook */
export type Hook = (...args: HookArgs[]) => HookReturnValue;

// Hook tracking

export interface ComponentHooks {
	/** The list of hooks a component uses */
	_list: HookState[];
	/** List of Effects to be invoked after the next frame is rendered */
	_pendingEffects: EffectHookState[];
}

export interface Component extends PreactComponent<any, any> {
	__hooks?: ComponentHooks;
}

export type HookState = EffectHookState | MemoHookState | ReducerHookState;

export type Effect = () => void | Cleanup;
export type Cleanup = () => void;

export interface EffectHookState {
	_value?: Effect;
	_args?: any[];
	_cleanup?: Cleanup;
}

export interface MemoHookState {
	_value?: any;
	_args?: any[];
	_factory?: () => any;
}

export interface ReducerHookState {
	_value?: any;
	_component?: Component;
	_reducer?: Reducer<any, any>;
}


# Contributing

This document is intended for developers interest in making contributions to Preact and document our internal processes like releasing a new version.

## Getting Started

This steps will help you to set up your development environment. That includes all dependencies we use to build Preact and developer tooling like git commit hooks.

1. Clone the git repository: `git clone git@github.com:preactjs/preact.git`
2. Go into the cloned folder: `cd preact/`
3. Install all dependencies: `npm install`

## The Repo Structure

This repository contains Preact itself, as well as several addons like the debugging package for example. This is reflected in the directory structure of this repository. Each package has a `src/` folder where the source code can be found, a `test` folder for all sorts of tests that check if the code in `src/` is correct, and a `dist/` folder where you can find the bundled artifacts. Note that the `dist/` folder may not be present initially. It will be created as soon as you run any of the build scripts inside `package.json`. More on that later ;)

A quick overview of our repository:

```bash
# The repo root (folder where you cloned the repo into)
/
  src/  # Source code of our core
  test/ # Unit tests for core
  dist/ # Build artifacts for publishing on npm (may not be present)

  # Sub-package, can be imported via `preact/compat` by users.
  # Compat stands for react-compatibility layer which tries to mirror the
  # react API as close as possible (mostly legacy APIs)
  compat/
  	src/  # Source code of the compat addon
  	test/ # Tests related to the compat addon
  	dist/ # Build artifacts for publishing on npm (may not be present)

  # Sub-package, can be imported via `preact/hooks` by users.
  # The hooks API is an effect based API to deal with component lifcycles.
  # It's similar to hooks in React
  hooks/
  	src/  # Source code of the hooks addon
  	test/ # Tests related to the hooks addon
  	dist/ # Build artifacts for publishing on npm (may not be present)

  # Sub-package, can be imported via `preact/debug` by users.
  # Includes debugging warnings and error messages for common mistakes found
  # in Preact application. Also hosts the devtools bridge
  debug/
  	src/  # Source code of the debug addon
  	test/ # Tests related to the debug addon
  	dist/ # Build artifacts for publishing on npm (may not be present)

  # Sub-package, can be imported via `preact/test-utils` by users.
  # Provides helpers to make testing Preact applications easier
  test-utils/
  	src/  # Source code of the test-utils addon
  	test/ # Tests related to the test-utils addon
  	dist/ # Build artifacts for publishing on npm (may not be present)

  # A demo application that we use to debug tricky errors and play with new
  # features.
  demo/

  # Contains build scripts and dependencies for development
  package.json
```

_Note: The code for rendering Preact on the server lives in another repo and is a completely separate npm package. It can be found here: [https://github.com/preactjs/preact-render-to-string](https://github.com/preactjs/preact-render-to-string)_

### What does `mangle.json` do?

It's a special file that can be used to specify how `terser` (previously known as `uglify`) will minify variable names. Because each sub-package has it's own distribution files we need to ensure that the variable names stay consistent across bundles.

## What does `options.js` do?

Unique to Preact we do support several ways to hook into our renderer. All our addons use that to inject code at different stages of a render process. They are documented in our typings in `internal.d.ts`. The core itself doesn't make use of them, which is why the file only contains an empty `object`.

## Important Branches

We merge every PR into the `master` branch which is the one that we'll use to publish code to npm. For the previous Preact release line we have a branch called `8` which is in maintenance mode. As a new contributor you won't have to deal with that ;)

## Creating your first Pull-Request

We try to make it as easy as possible to contribute to Preact and make heavy use of GitHub's "Draft PR" feature which tags Pull-Requests (short = PR) as work in progress. PRs tend to be published as soon as there is an idea that the developer deems worthwhile to include into Preact and has written some rough code. The PR doesn't have to be perfect or anything really ;)

Once a PR or a Draft PR has been created our community typically joins the discussion about the proposed change. Sometimes that includes ideas for test cases or even different ways to go about implementing a feature. Often this also includes ideas on how to make the code smaller. We usually refer to the latter as "code-golfing" or just "golfing".

When everything is good to go someone will approve the PR and the changes will be merged into the `master` branch and we usually cut a release a few days/ a week later.

_The big takeaway for you here is, that we will guide you along the way. We're here to help to make a PR ready for approval!_

The short summary is:

1. Make changes and submit a PR
2. Modify change according to feedback (if there is any)
3. PR will be merged into `master`
4. A new release will be cut (every 2-3 weeks).

## Commonly used scripts for contributions

Scripts can be executed via `npm run [script]` or `yarn [script]` respectively.

- `build` - compiles all packages ready for publishing to npm
- `build:core` - builds just Preact itself
- `build:debug` - builds the debug addon only
- `build:hooks` - builds the hook addon only
- `build:test-utils` - builds the test-utils addon only
- `test:ts` - Run all tests for TypeScript definitions
- `test:karma` - Run all unit/integration tests.
- `test:karma:watch` - Same as above, but it will automatically re-run the test suite if a code change was detected.

But to be fair, the only ones we use ourselves are `build` and `test:karma:watch`. The other ones are mainly used on our CI pipeline and we rarely use them.

_Note: Both `test:karma` and `test:karma:watch` listen to the environment variable `COVERAGE=true`. Disabling code coverage can significantly speed up the time it takes to complete the test suite._

_Note2: The test suite is based on `karma` and `mocha`. Individual tests can be executed by appending `.only`:_

```jsx
it.only('should test something', () => {
	expect(1).to.equal(1);
});
```

## Common terminology and variable names

- `vnode` -> shorthand for `virtual-node` which is an object that specifies how a Component or DOM-node looks like
- `commit` -> A commit is the moment in time when you flush all changes to the DOM
- `c` -> The variable `c` always refers to a `component` instance throughout our code base.
- `diff/diffing` -> Diffing describes the process of comparing two "things". In our case we compare the previous `vnode` tree with the new one and apply the delta to the DOM.
- `root` -> The topmost node of a `vnode` tree

## Tips for getting to know the code base

- Check the JSDoc block right above the function definition to understand what it does. It contains a short description of each function argument and what it does.
- Check the callsites of a function to understand how it's used. Modern editors/IDEs allow you to quickly find those, or use the plain old search feature instead.

## FAQ

### Why does the JSDoc use TypeScript syntax to specify types?

Several members of the team are very fond of TypeScript and we wanted to leverage as many of its advantages, like improved autocompletion, for Preact. We even attempted to port Preact to TypeScript a few times, but we ran into many issues with the DOM typings. Those would force us to fill our codebase with many `any` castings, making our code very noisy.

Luckily TypeScript has a mode where it can somewhat reliably typecheck JavaScript code by reusing the types defined in JSDoc blocks. It's not perfect and it often has trouble inferring the correct types the further one strays away from the function arguments, but it's good enough that it helps us a lot with autocompletion. Another plus is that we can make sure that our TypeScript definitons are correct at the same time.

Check out the [official TypeScript documentation](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html) for more information.

_Note that we have separate tests for our TypeScript definition files. We only use `ts-check` for local development and don't check it anywhere else like on the CI._

### Why does the code base often use `let` instead of `const`?

There is no real reason for that other a historical one. Back before auto-formatting via prettier was a thing and minifiers weren't as advanced as they are today we used a pretty terse code-style. The code-style deliberately was aimed at making code look as concise and short as possible. The `let` keyword is a bit shorter than `const` to write, so we only used that. This was done only for stylistic reasons.

This helped our minds to not lose sight of focusing on size, but made it difficult for newcomers to start contributing to Preact. For that reason alone we switched to `prettier` and loosened our rule regarding usage of `let` or `const`. Today we use both, but you can still find many existing places where `let` is still in use.

In the end there is no effect on size regardless if you use `const`, `let` or use both. Our code is downtranspiled to `ES5` for npm so both will be replaced with `var` anyways. Therefore it doesn't really matter at all which one is used in our codebase.

This will only become important once shipping modern JavaScript code on npm becomes a thing and bundlers follow suit.

## How to create a good bug report

To be able to fix issues we need to see them on our machine. This is only possible when we can reproduce the error. The easiest way to do that is narrow down the problem to specific components or combination of them. This can be done by removing as much unrelated code as possible.

The perfect way to do that is to make a [codesandbox](https://codesandbox.io/). That way you can easily share the problematic code and ensure that others can see the same issue you are seeing.

For us a [codesandbox](https://codesandbox.io/) says more than a 1000 words :tada:

## I have more questions on how to contribute to Preact. How can I reach you?

We closely watch our issues and have a pretty active [Slack workspace](https://chat.preactjs.com/). Nearly all our communication happens via these two forms of communication.

## Releasing Preact (Maintainers only)

This guide is intended for core team members that have the necessary
rights to publish new releases on npm.

1. [Write the release notes](#writing-release-notes) and keep them as a draft in GitHub
   1. I'd recommend writing them in an offline editor because each edit to a draft will change the URL in GitHub.
2. Make a PR where **only** the version number is incremented in `package.json` (note: We follow `SemVer` conventions)
3. Wait until the PR is approved and merged.
4. Switch back to the `master` branch and pull the merged PR
5. Run `npm run build && npm publish`
   1. Make sure you have 2FA enabled in npm, otherwise the above command will fail.
   2. If you're doing a pre-release add `--tag next` to the `npm publish` command to publish it under a different tag (default is `latest`)
6. Publish the release notes and create the correct git tag.
7. Tweet it out

## Legacy Releases (8.x)

> **ATTENTION:** Make sure that you've cleared the project correctly
> when switching from a 10.x branch.

0. Run `rm -rf dist node_modules && npm i` to make sure to have the correct dependencies.

Apart from that it's the same as above.

## Writing release notes

The release notes have become a sort of tiny blog post about what's
happening in preact-land. The title usually has this format:

```txt
Version Name
```

Example:

```txt
10.0.0-beta.1 Los Compresseros
```

The name is optional, we just have fun finding creative names :wink:

To keep them interesting we try to be as
concise as possible and to just reflect where we are. There are some
rules we follow while writing them:

- Be nice, use a positive tone. Avoid negative words
- Show, don't just tell.
- Be honest.
- Don't write too much, keep it simple and short.
- Avoid making promises and don't overpromise. That leads to unhappy users
- Avoid framework comparisons if possible
- Highlight awesome community contributions (or great issue reports)
- If in doubt, praise the users.

After this section we typically follow with a changelog part that's
divided into 4 groups in order of importance for the user:

- Features
- Bug Fixes
- Typings
- Maintenance

We generate it via this handy cli program: [changelogged](https://github.com/marvinhagemeister/changelogged). It will collect and format
the descriptions of all PRs that have been merged between two tags.
The usual command is `changelogged 10.0.0-rc.2..HEAD` similar to how
you'd diff two points in time with git. This will get you 90% there,
but you still need to divide it into groups. It's also a good idea
to unify the formatting of the descriptions, so that they're easier
to read and don't look like a mess.


html, body {
  font: 14px system-ui, sans-serif;
}
.list {
  list-style: none;
  padding: 0;
}
.list > li {
  position: relative;
  padding: 5px 10px;
  animation: fadeIn 1s ease;
}
@keyframes fadeIn {
  0% {
    box-shadow: inset 0 0 2px 2px red,
                0 0 2px 2px red;
  }
}
.list > .odd {
  background-color: #def;
}
.list > .even {
  background-color: #fed;
}


.input-field input[type=search] + label {

.input-field input[type=search] ~ .mdi-navigation-close {

keep keep keep replace keep keep keep keep

.input-field input[type=search] ~ .mdi-navigation-close, .input-field input[type=search] ~ .material-icons {

/* Remove Focus Boxes */
select:focus {
  outline: 1px solid lighten($secondary-color, 47%);
}
button:focus {
  outline: none;
  background-color: lighten($button-color, 4%);
}

label {
  font-size: $label-font-size;
  color: $input-border-color;
}

/***************************
   Text Inputs + Textarea
****************************/

// Style Placeholders
::-webkit-input-placeholder {
   color: lighten($input-border-color, 20%);
}

:-moz-placeholder { /* Firefox 18- */
   color: lighten($input-border-color, 20%);
}

::-moz-placeholder {  /* Firefox 19+ */
   color: lighten($input-border-color, 20%);
}

:-ms-input-placeholder {
   color: lighten($input-border-color, 20%);
}

// Text inputs
input[type=text],
input[type=password],
input[type=email],
input[type=url],
input[type=time],
input[type=date],
input[type=datetime-local],
input[type=tel],
input[type=number],
input[type=search],
textarea.materialize-textarea {

  // General Styles
  background-color: transparent;
  border: none;
  border-bottom: 1px solid $input-border-color;
  border-radius: 0;
  outline: none;
  height: 3rem;
  width: 100%;

  font-size: 1rem;
  margin: 0 0 15px 0;
  padding: 0;
  box-shadow: none;
  @include box-sizing(content-box);
  transition: all .3s;

  // Disabled input style
  &:disabled, &[readonly="readonly"] {
    color: $input-disabled-color;
    border-bottom: 1px dotted $input-disabled-color;
  }
  // Disabled label style
  &:disabled+label, &[readonly="readonly"]+label {
    color: $input-disabled-color;
  }
  // Focused input style
  &:focus:not([readonly]) {
    border-bottom: 1px solid $input-focus-color;
    box-shadow: 0 1px 0 0 $input-focus-color;
  }
  // Focused label style
  &:focus:not([readonly])+label {
    color: $input-focus-color;
  }
  // Valid Input Style
  &.valid,
  &:focus.valid {
    border-bottom: 1px solid $input-success-color;
    box-shadow: 0 1px 0 0 $input-success-color;
  }
  // Custom Success Message
  &.valid + label:after,
  &:focus.valid + label:after {
    content: attr(data-success);
    color: $input-success-color;
    opacity: 1;
  }
  // Invalid Input Style
  &.invalid,
  &:focus.invalid {
    border-bottom: 1px solid $input-error-color;
    box-shadow: 0 1px 0 0 $input-error-color;
  }
  // Custom Error message
  &.invalid + label:after,
  &:focus.invalid + label:after {
    content: attr(data-error);
    color: $input-error-color;
    opacity: 1;
  }

  // Form Message Shared Styles
  & + label:after {
    display: block;
    content: "";
    position: absolute;
    top: 65px;
    opacity: 0;
    transition: .2s opacity ease-out, .2s color ease-out;
  }
}

// Styling for input field wrapper
.input-field {
  position: relative;
  margin-top: 1rem;

  label {
    color: $input-border-color;
    position: absolute;
    top: 0.8rem;
    left: $gutter-width / 2;
    font-size: 1rem;
    cursor: text;
    @include transition(.2s ease-out);
  }
  label.active {
    font-size: $label-font-size;
    @include transform(translateY(-140%));
  }

  // Prefix Icons
  .prefix {
    position: absolute;
    width: 3rem;
    font-size: 2rem;
    @include transition(color .2s);

    &.active { color: $input-focus-color; }
  }
  .prefix ~ input,
  .prefix ~ textarea {
    margin-left: 3rem;
    width: 92%;
    width: calc(100% - 3rem);
  }
  .prefix ~ textarea { padding-top: .8rem; }
  .prefix ~ label { margin-left: 3rem; }

  @media #{$medium-and-down} {
    .prefix ~ input {
      width: 86%;
      width: calc(100% - 3rem);
    }
  }
  @media #{$small-and-down} {
    .prefix ~ input {
      width: 80%;
      width: calc(100% - 3rem);
    }
  }
}

// Search Field

.input-field input[type=search] {
  display: block;
  line-height: inherit;
  padding-left: 4rem;
  width: calc(100% - 4rem);

  &:focus {
    background-color: $input-bg-color;
    border: 0;
    box-shadow: none;
    color: #444;

    & + label i,
    & ~ .mdi-navigation-close {
      color: #444;
    }
  }

  & + label {
    left: 1rem;
  }

  & ~ .mdi-navigation-close {
    position: absolute;
    top: 0;
    right: 1rem;
    color: transparent;
    cursor: pointer;
    font-size: 2rem;
    transition: .3s color;
  }
}

// Default textarea
textarea {
   width: 100%;
   height: 3rem;
   background-color: transparent;

  &.materialize-textarea {
    overflow-y: hidden; /* prevents scroll bar flash */
    padding: 1.6rem 0; /* prevents text jump on Enter keypress */
    resize: none;
    min-height: 3rem;
  }
}

// For textarea autoresize
.hiddendiv {
  display: none;
  white-space: pre-wrap;
  word-wrap: break-word;
  overflow-wrap: break-word; /* future version of deprecated 'word-wrap' */
  padding-top: 1.2rem; /* prevents text jump on Enter keypress */
}

/***************
  Radio Buttons
***************/

/* Remove default Radio Buttons */
[type="radio"]:not(:checked),
[type="radio"]:checked {
  position: absolute;
  left: -9999px;
  visibility: hidden;
}

[type="radio"]:not(:checked) + label,
[type="radio"]:checked + label {
  position: relative;
  padding-left: 35px;
  cursor: pointer;
  display: inline-block;
  height: 25px;
  line-height: 25px;
  font-size: 1rem;
  @include transition(.28s ease);

  -webkit-user-select: none; /* webkit (safari, chrome) browsers */
  -moz-user-select: none; /* mozilla browsers */
  -khtml-user-select: none; /* webkit (konqueror) browsers */
  -ms-user-select: none; /* IE10+ */
}

[type="radio"] + label:before,
[type="radio"] + label:after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  margin: 4px;
  width: 16px;
  height: 16px;
  z-index: 0;
  @include transition(.28s ease);
}

/* Unchecked styles */
[type="radio"]:not(:checked) + label:before {
  border-radius: 50%;
  border: 2px solid $radio-empty-color;
}
[type="radio"]:not(:checked) + label:after {
  border-radius: 50%;
  border: 2px solid $radio-empty-color;
  z-index: -1;

  @include transform(scale(0));
}

/* Checked styles */
[type="radio"]:checked + label:before {
  border-radius: 50%;
  border: 2px solid transparent;
}
[type="radio"]:checked + label:after {
  border-radius: 50%;
  border: 2px solid $radio-fill-color;
  background-color: $radio-fill-color;
  z-index: 0;
  @include transform(scale(1.02));
}

/* Radio With gap */
[type="radio"].with-gap:checked + label:before {
  border-radius: 50%;
  border: 2px solid $radio-fill-color;
}
[type="radio"].with-gap:checked + label:after {
  border-radius: 50%;
  border: 2px solid $radio-fill-color;
  background-color: $radio-fill-color;
  z-index: 0;
  @include transform(scale(.5));
}

/* Disabled Radio With gap */
[type="radio"].with-gap:disabled:checked + label:before {
  border: 2px solid $input-disabled-color;
}
[type="radio"].with-gap:disabled:checked + label:after {
  border: none;
  background-color: $input-disabled-color;
}

/* Disabled style */
[type="radio"]:disabled:not(:checked) + label:before,
[type="radio"]:disabled:checked + label:before {
  background-color: transparent;
  border-color: $input-disabled-color;
}
[type="radio"]:disabled + label {
  color: $input-disabled-color;
}
[type="radio"]:disabled:not(:checked) + label:before {
  border-color: $input-disabled-color;
}
[type="radio"]:disabled:checked + label:after {
  background-color: $input-disabled-color;
  border-color: $input-disabled-solid-color;
}

/***************
   Checkboxes
***************/

/* CUSTOM CSS CHECKBOXES */
form p {
  margin-bottom: 10px;
  text-align: left;
}
form p:last-child {
  margin-bottom: 0;
}

/* Remove default checkbox */
[type="checkbox"]:not(:checked),
[type="checkbox"]:checked {
  position: absolute;
  left: -9999px;
  visibility: hidden;
}

// Checkbox Styles
[type="checkbox"] {

  // Text Label Style
  + label {
    position: relative;
    padding-left: 35px;
    cursor: pointer;
    display: inline-block;
    height: 25px;
    line-height: 25px;
    font-size: 1rem;

    -webkit-user-select: none; /* webkit (safari, chrome) browsers */
    -moz-user-select: none; /* mozilla browsers */
    -khtml-user-select: none; /* webkit (konqueror) browsers */
    -ms-user-select: none; /* IE10+ */
  }

  /* checkbox aspect */
  + label:before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 18px;
    height: 18px;
    z-index: 0;
    border: 2px solid $radio-empty-color;
    border-radius: 1px;
    margin-top: 2px;
    @include transition(.2s);
  }

  &:not(:checked):disabled + label:before {
    border: none;
    background-color: $input-disabled-color;
  }

}

[type="checkbox"]:checked {
  + label:before {
    top: -4px;
    left: -3px;
    width: 12px; height: 22px;
    border-top: 2px solid transparent;
    border-left: 2px solid transparent;
    border-right: 2px solid $radio-fill-color;
    border-bottom: 2px solid $radio-fill-color;
    @include transform(rotate(40deg));
    -webkit-backface-visibility: hidden;
    @include transform-origin(100% 100%);
  }

  &:disabled + label:before {
    border-right: 2px solid $input-disabled-color;
    border-bottom: 2px solid $input-disabled-color;
  }

}

/* Indeterminate checkbox */
[type="checkbox"]:indeterminate {
  +label:before {
    left: -10px;
    top: -11px;
    width: 10px; height: 22px;
    border-top: none;
    border-left: none;
    border-right: 2px solid $radio-fill-color;
    border-bottom: none;
    @include transform(rotate(90deg));
    -webkit-backface-visibility: hidden;
    @include transform-origin(100% 100%);
  }

  // Disabled indeterminate
  &:disabled + label:before {
    border-right: 2px solid $input-disabled-color;
    background-color: transparent;
  }
}

// Filled in Style
[type="checkbox"].filled-in {
  // General
  + label:after {
    border-radius: 2px;
  }
  + label:before,
  + label:after {
    content: '';
    left: 0;
    position: absolute;
    /* .1s delay is for check animation */
    transition: border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;
    z-index: 1;
  }
  // Unchecked style
  &:not(:checked) + label:before {
    width: 0;
     height: 0;
     border: 3px solid transparent;
     left: 6px;
     top: 10px;

     -webkit-transform: rotateZ(37deg);
             transform: rotateZ(37deg);
     -webkit-transform-origin: 20% 40%;
             transform-origin: 100% 100%;
  }
  &:not(:checked) + label:after {
    height: 20px;
    width: 20px;
    background-color: transparent;
    border: 2px solid $radio-empty-color;
    top: 0px;
    z-index: 0;
  }
  // Checked style
  &:checked {
    + label:before {
      top: 0;
      left: 1px;
      width: 8px;
      height: 13px;
      border-top: 2px solid transparent;
      border-left: 2px solid transparent;
      border-right: 2px solid $input-bg-color;
      border-bottom: 2px solid $input-bg-color;
      -webkit-transform: rotateZ(37deg);
      transform: rotateZ(37deg);

      -webkit-transform-origin: 100% 100%;
      transform-origin: 100% 100%;
    }
    + label:after {
      top: 0px;
      width: 20px;
      height: 20px;
      border: 2px solid $secondary-color;
      background-color: $secondary-color;
      z-index: 0;
    }
  }
  // Disabled style
  &:disabled:not(:checked) + label:before {

    background-color: transparent;
    border: 2px solid transparent;
  }
  &:disabled:not(:checked) + label:after {
    border-color: transparent;
    background-color: $input-disabled-solid-color;
  }
  &:disabled:checked + label:before {
    background-color: transparent;

  }
  &:disabled:checked + label:after {
    background-color: $input-disabled-solid-color;
    border-color: $input-disabled-solid-color;
  }

}

/***************
     Switch
***************/
.switch,
.switch * {
    -webkit-user-select: none;
    -moz-user-select: none;
    -khtml-user-select: none;
    -ms-user-select: none;
}
.switch label {
    cursor: pointer;
}
.switch label input[type=checkbox]{
    opacity: 0;
    width: 0;
    height: 0;
}
.switch label input[type=checkbox]:checked + .lever {
    background-color: $switch-checked-lever-bg;
}
.switch label input[type=checkbox]:checked + .lever:after {
    background-color: $switch-bg-color;
}
.switch label .lever {
    content: "";
    display: inline-block;
    position: relative;
    width: 40px;
    height: 15px;
    background-color: $switch-unchecked-lever-bg;
    border-radius: 15px;
    margin-right: 10px;
    transition: background 0.3s ease;
    vertical-align: middle;
    margin: 0 16px;
}
.switch label .lever:after {
    content: "";
    position: absolute;
    display: inline-block;
    width: 21px;
    height: 21px;
    background-color: $switch-unchecked-bg;
    border-radius: 21px;
    box-shadow: 0 1px 3px 1px rgba(0,0,0,.4);
    left: -5px;
    top: -3px;
    transition: left 0.3s ease, background .3s ease, box-shadow 0.1s ease;
}
// Switch active style
input[type=checkbox]:checked:not(:disabled) ~ .lever:active:after {
       box-shadow: 0 1px 3px 1px rgba(0,0,0,.4), 0 0 0 15px transparentize($switch-bg-color, .9);
}
input[type=checkbox]:not(:disabled) ~ .lever:active:after {
       box-shadow: 0 1px 3px 1px rgba(0,0,0,.4), 0 0 0 15px rgba(0, 0, 0, .08);
}
.switch label input[type=checkbox]:checked + .lever:after {
    left: 24px;
}

// Disabled Styles

.switch input[type=checkbox][disabled] + .lever{
  cursor: default;
}
.switch label input[type=checkbox][disabled] + .lever:after,
.switch label input[type=checkbox][disabled]:checked + .lever:after {
    background-color: $input-disabled-solid-color;
}

/***************
  Select Field
***************/

.select-label {
  position: absolute;
}

.select-wrapper {
  position: relative;

  input.select-dropdown {
    position: relative;
    cursor: pointer;
    // color: #444;
    background-color: transparent;
    border: none;
    border-bottom: 1px solid $input-border-color;
    outline: none;
    height: 3rem;
    line-height: 3rem;
    width: 100%;
    font-size: 1rem;
    margin: 0 0 15px 0;
    padding: 0;
    display: block;
  }
  span.caret {
    color: initial;
    position: absolute;
    right: 0;
    top: 16px;
    font-size: 10px;
    &.disabled {
      color: $input-disabled-color;
    }
  }
  & + label {
    position: absolute;
    top: -14px;
    font-size: $label-font-size;
  }
}

select { display: none; }
select.browser-default { display: block; }

// Disabled styles
select:disabled {
  color: rgba(0,0,0,.3);
}
.select-wrapper input.select-dropdown:disabled {
  color: rgba(0,0,0,.3);
  cursor: default;
  -webkit-user-select: none; /* webkit (safari, chrome) browsers */
  -moz-user-select: none; /* mozilla browsers */
  -ms-user-select: none; /* IE10+ */
  border-bottom: 1px solid rgba(0,0,0,.3);
}
.select-wrapper i {
  color: rgba(0,0,0,.3);
}
.select-dropdown li.disabled {
  color: rgba(0,0,0,.3);
  background-color: transparent;
}

/*********************
      File Input
**********************/
.file-field {
  position: relative;

  .file-path-wrapper {
    overflow: hidden;
    padding-left: 10px;
  }
  input.file-path { width: 100%; }

  .btn {
    float: left;
    height: 3rem;
    line-height: 3rem;
  }

  span {
    cursor: pointer;
  }

  input[type=file] {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    bottom: 0;
    width: 100%;
    margin: 0;
    padding: 0;
    font-size: 20px;
    cursor: pointer;
    opacity: 0;
    filter: alpha(opacity=0);
  }
}

/***************
      Range
***************/

.range-field {
  position: relative;
}

input[type=range], input[type=range] + .thumb {
  @extend .no-select;
  cursor: pointer;
}

input[type=range] {
  position: relative;
  background-color: transparent;
  border: none;
  outline: none;
  width: 100%;
  margin: 15px 0px;
  padding: 0;
}
input[type=range] + .thumb {
  position: absolute;
  border: none;
  height: 0;
  width: 0;
  border-radius: 50%;
  background-color: $radio-fill-color;
  top: 10px;
  margin-left: -6px;

  @include transform-origin(50% 50%);
  @include transform(rotate(-45deg));

  .value {
    display: block;
    width: 30px;
    text-align: center;
    color: $radio-fill-color;
    font-size: 0;
    @include transform(rotate(45deg));
  }

  &.active {
    border-radius: 50% 50% 50% 0;

    .value {
      color: $input-bg-color;
      margin-left: -1px;
      margin-top: 8px;
      font-size: 10px;
    }
  }
}

input[type=range]:focus {
  outline: none;
}

// WebKit
input[type=range]{
  -webkit-appearance: none;
}

input[type=range]::-webkit-slider-runnable-track {
  height: 3px;
  background: #c2c0c2;
  border: none;
}

input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
  border: none;
  height: 14px;
  width: 14px;
  border-radius: 50%;
  background-color: $radio-fill-color;
  transform-origin: 50% 50%;
  margin: -5px 0 0 0;
  @include transition(.3s);
}

input[type=range]:focus::-webkit-slider-runnable-track {
  background: #ccc;
}

// FireFox
input[type=range]{
  /* fix for FF unable to apply focus style bug  */
  border: 1px solid white;

  /*required for proper track sizing in FF*/
}

input[type=range]::-moz-range-track {
  height: 3px;
  background: #ddd;
  border: none;
}

input[type=range]::-moz-range-thumb {
  border: none;
  height: 14px;
  width: 14px;
  border-radius: 50%;
  background: $radio-fill-color;
  margin-top: -5px;
}

/*hide the outline behind the border*/
input[type=range]:-moz-focusring{
  outline: 1px solid white;
  outline-offset: -1px;
}

input[type=range]:focus::-moz-range-track {
  background: #ccc;
}

// IE 10+
input[type=range]::-ms-track {
  height: 3px;

  /*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */
  background: transparent;

  /*leave room for the larger thumb to overflow with a transparent border */
  border-color: transparent;
  border-width: 6px 0;

  /*remove default tick marks*/
  color: transparent;
}
input[type=range]::-ms-fill-lower {
  background: #777;
}
input[type=range]::-ms-fill-upper {
  background: #ddd;
}
input[type=range]::-ms-thumb {
  border: none;
  height: 14px;
  width: 14px;
  border-radius: 50%;
  background: $radio-fill-color;
}
input[type=range]:focus::-ms-fill-lower {
  background: #888;
}
input[type=range]:focus::-ms-fill-upper {
  background: #ccc;
}

/***************************
   Text Inputs + Textarea
****************************/

select {
   background-color: rgba(255, 255, 255, 0.90);
   width: 100%;
   padding: 5px;
   border: 1px solid #f2f2f2;
   border-radius: 2px;
   height: 3rem;
   }


//---------------------------------------------------
//  Sass Prefixer
//  -------------------------------------------------
//  TABLE OF CONTENTS
//  (*) denotes a syntax-sugar helper
//  -------------------------------------------------
//
//      animation($args)
//          animation-delay($delay)
//          animation-direction($direction)
//          animation-duration($duration)
//          animation-fill-mode($mode)
//          animation-iteration-count($count)
//          animation-name($name)
//          animation-play-state($state)
//          animation-timing-function($function)
//      background-size($args)
//          inner-shadow($args) *
//      box-sizing($args)
//          border-box() *
//          content-box() *
//      columns($args)
//          column-count($count)
//          column-gap($gap)
//          column-rule($args)
//          column-width($width)
//      flexbox()
//          flex($args)
//          order($args)
//          align($args)
//          justify-content($args)
//      gradient($default,$start,$stop) *
//          linear-gradient-top($default,$color1,$stop1,$color2,$stop2,[$color3,$stop3,$color4,$stop4])*
//          linear-gradient-left($default,$color1,$stop1,$color2,$stop2,[$color3,$stop3,$color4,$stop4])*
//      transform($args)
//          transform-origin($args)
//          transform-style($style)
//          rotate($deg)
//          scale($factor)
//          translate($x,$y)
//          translate3d($x,$y,$z)
//          translateHardware($x,$y) *
//      text-shadow($args)
//      transition($args)
//          transition-delay($delay)
//          transition-duration($duration)
//          transition-property($property)
//          transition-timing-function($function)

// Animation

@mixin animation($args) {
    -webkit-animation: $args;
    -moz-animation: $args;
    -ms-animation: $args;
    -o-animation: $args;
    animation: $args;
}
@mixin animation-delay($delay) {
    -webkit-animation-delay: $delay;
    -moz-animation-delay: $delay;
    -ms-animation-delay: $delay;
    -o-animation-delay: $delay;
    animation-delay: $delay;
}
@mixin animation-direction($direction) {
    -webkit-animation-direction: $direction;
    -moz-animation-direction: $direction;
    -ms-animation-direction: $direction;
    -o-animation-direction: $direction;
}
@mixin animation-duration($duration) {
    -webkit-animation-duration: $duration;
    -moz-animation-duration: $duration;
    -ms-animation-duration: $duration;
    -o-animation-duration: $duration;
}
@mixin animation-fill-mode($mode) {
    -webkit-animation-fill-mode: $mode;
    -moz-animation-fill-mode: $mode;
    -ms-animation-fill-mode: $mode;
    -o-animation-fill-mode: $mode;
    animation-fill-mode: $mode;
}
@mixin animation-iteration-count($count) {
    -webkit-animation-iteration-count: $count;
    -moz-animation-iteration-count: $count;
    -ms-animation-iteration-count: $count;
    -o-animation-iteration-count: $count;
    animation-iteration-count: $count;
}
@mixin animation-name($name) {
    -webkit-animation-name: $name;
    -moz-animation-name: $name;
    -ms-animation-name: $name;
    -o-animation-name: $name;
    animation-name: $name;
}
@mixin animation-play-state($state) {
    -webkit-animation-play-state: $state;
    -moz-animation-play-state: $state;
    -ms-animation-play-state: $state;
    -o-animation-play-state: $state;
    animation-play-state: $state;
}
@mixin animation-timing-function($function) {
    -webkit-animation-timing-function: $function;
    -moz-animation-timing-function: $function;
    -ms-animation-timing-function: $function;
    -o-animation-timing-function: $function;
    animation-timing-function: $function;
}

// Keyframes
@mixin keyframes($animation-name) {
  @-webkit-keyframes #{$animation-name} {
    @content;
  }
  @-moz-keyframes #{$animation-name} {
    @content;
  }
  @keyframes #{$animation-name} {
    @content;
  }
}

// Backface-visibility

@mixin backface-visibility($args) {
    -webkit-backface-visibility: $args;
    -moz-backface-visibility: $args;
    -ms-backface-visibility: $args;
    backface-visibility: $args;
}

// Background Size

@mixin background-size($args) {
    -webkit-background-size: $args;
    background-size: $args;
}

// Box Sizing

@mixin box-sizing($args) {
    -webkit-box-sizing: $args;
    -moz-box-sizing: $args;
    box-sizing: $args;
}
@mixin border-box(){
    @include box-sizing(border-box);
}
@mixin content-box(){
    @include box-sizing(content-box);
}

// Columns

@mixin columns($args) {
    -webkit-columns: $args;
    -moz-columns: $args;
    columns: $args;
}
@mixin column-count($count) {
    -webkit-column-count: $count;
    -moz-column-count: $count;
    column-count: $count;
}
@mixin column-gap($gap) {
    -webkit-column-gap: $gap;
    -moz-column-gap: $gap;
    column-gap: $gap;
}
@mixin column-width($width) {
    -webkit-column-width: $width;
    -moz-column-width: $width;
    column-width: $width;
}
@mixin column-rule($args) {
    -webkit-column-rule: $args;
    -moz-column-rule: $args;
    column-rule: $args;
}

// Filter
@mixin filter($args) {
    -webkit-filter: $args;
    -moz-filter: $args;
    -o-filter: $args;
    -ms-filter: $args;
}

// Flexbox
@mixin flexbox() {
  display: -webkit-box;
  display: -moz-box;
  display: -ms-flexbox;
  display: -webkit-flex;
  display: flex;
}
    @mixin flex($values) {
      -webkit-box-flex: $values;
      -moz-box-flex:  $values;
      -webkit-flex:  $values;
      -ms-flex:  $values;
      flex:  $values;
    }
    @mixin order($val) {
      -webkit-box-ordinal-group: $val;
      -moz-box-ordinal-group: $val;
      -ms-flex-order: $val;
      -webkit-order: $val;
      order: $val;
    }
    @mixin align($align) {
      -webkit-flex-align: $align;
      -ms-flex-align: $align;
      -webkit-align-items: $align;
      align-items: $align;
    }
    @mixin justify-content($val) {
      -webkit-justify-content: $val;
      justify-content: $val;
    }
// Gradients

@mixin gradient($default: #F5F5F5, $start: #EEE, $stop: #FFF) {
    @include linear-gradient-top($default,$start,0%,$stop,100%);
}
@mixin linear-gradient-top($default,$color1,$stop1,$color2,$stop2) {
    background-color: $default;
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop($stop1, $color1), color-stop($stop2 $color2));
    background-image: -webkit-linear-gradient(top, $color1 $stop1, $color2 $stop2);
    background-image: -moz-linear-gradient(top, $color1 $stop1, $color2 $stop2);
    background-image: -ms-linear-gradient(top, $color1 $stop1, $color2 $stop2);
    background-image: -o-linear-gradient(top, $color1 $stop1, $color2 $stop2);
    background-image: linear-gradient(top, $color1 $stop1, $color2 $stop2);
}
@mixin linear-gradient-top2($default,$color1,$stop1,$color2,$stop2,$color3,$stop3) {
    background-color: $default;
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop($stop1, $color1), color-stop($stop2 $color2), color-stop($stop3 $color3));
    background-image: -webkit-linear-gradient(top, $color1 $stop1, $color2 $stop2, $color3 $stop3);
    background-image: -moz-linear-gradient(top, $color1 $stop1, $color2 $stop2, $color3 $stop3);
    background-image: -ms-linear-gradient(top, $color1 $stop1, $color2 $stop2, $color3 $stop3);
    background-image: -o-linear-gradient(top, $color1 $stop1, $color2 $stop2, $color3 $stop3);
    background-image: linear-gradient(top, $color1 $stop1, $color2 $stop2, $color3 $stop3);
}
@mixin linear-gradient-top3($default,$color1,$stop1,$color2,$stop2,$color3,$stop3,$color4,$stop4) {
    background-color: $default;
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop($stop1, $color1), color-stop($stop2 $color2), color-stop($stop3 $color3), color-stop($stop4 $color4));
    background-image: -webkit-linear-gradient(top, $color1 $stop1, $color2 $stop2, $color3 $stop3, $color4 $stop4);
    background-image: -moz-linear-gradient(top, $color1 $stop1, $color2 $stop2, $color3 $stop3, $color4 $stop4);
    background-image: -ms-linear-gradient(top, $color1 $stop1, $color2 $stop2, $color3 $stop3, $color4 $stop4);
    background-image: -o-linear-gradient(top, $color1 $stop1, $color2 $stop2, $color3 $stop3, $color4 $stop4);
    background-image: linear-gradient(top, $color1 $stop1, $color2 $stop2, $color3 $stop3, $color4 $stop4);
}
@mixin linear-gradient-left($default,$color1,$stop1,$color2,$stop2) {
    background-color: $default;
    background-image: -webkit-gradient(linear, left top, left top, color-stop($stop1, $color1), color-stop($stop2 $color2));
    background-image: -webkit-linear-gradient(left, $color1 $stop1, $color2 $stop2);
    background-image: -moz-linear-gradient(left, $color1 $stop1, $color2 $stop2);
    background-image: -ms-linear-gradient(left, $color1 $stop1, $color2 $stop2);
    background-image: -o-linear-gradient(left, $color1 $stop1, $color2 $stop2);
    background-image: linear-gradient(left, $color1 $stop1, $color2 $stop2);
}
@mixin linear-gradient-left2($default,$color1,$stop1,$color2,$stop2,$color3,$stop3) {
    background-color: $default;
    background-image: -webkit-gradient(linear, left top, left top, color-stop($stop1, $color1), color-stop($stop2 $color2), color-stop($stop3 $color3));
    background-image: -webkit-linear-gradient(left, $color1 $stop1, $color2 $stop2, $color3 $stop3);
    background-image: -moz-linear-gradient(left, $color1 $stop1, $color2 $stop2, $color3 $stop3);
    background-image: -ms-linear-gradient(left, $color1 $stop1, $color2 $stop2, $color3 $stop3);
    background-image: -o-linear-gradient(left, $color1 $stop1, $color2 $stop2, $color3 $stop3);
    background-image: linear-gradient(left, $color1 $stop1, $color2 $stop2, $color3 $stop3);
}
@mixin linear-gradient-left3($default,$color1,$stop1,$color2,$stop2,$color3,$stop3,$color4,$stop4) {
    background-color: $default;
    background-image: -webkit-gradient(linear, left top, left top, color-stop($stop1, $color1), color-stop($stop2 $color2), color-stop($stop3 $color3), color-stop($stop4 $color4));
    background-image: -webkit-linear-gradient(left, $color1 $stop1, $color2 $stop2, $color3 $stop3, $color4 $stop4);
    background-image: -moz-linear-gradient(left, $color1 $stop1, $color2 $stop2, $color3 $stop3, $color4 $stop4);
    background-image: -ms-linear-gradient(left, $color1 $stop1, $color2 $stop2, $color3 $stop3, $color4 $stop4);
    background-image: -o-linear-gradient(left, $color1 $stop1, $color2 $stop2, $color3 $stop3, $color4 $stop4);
    background-image: linear-gradient(left, $color1 $stop1, $color2 $stop2, $color3 $stop3, $color4 $stop4);
}

// Text Shadow

@mixin text-shadow($args) {
    text-shadow: $args;
}

// Transforms

@mixin transform($args) {
    -webkit-transform: $args;
    -moz-transform: $args;
    -ms-transform: $args;
    -o-transform: $args;
    transform: $args;
}
@mixin transform-origin($args) {
    -webkit-transform-origin: $args;
    -moz-transform-origin: $args;
    -ms-transform-origin: $args;
    -o-transform-origin: $args;
    transform-origin: $args;
}
@mixin transform-style($style) {
    -webkit-transform-style: $style;
    -moz-transform-style: $style;
    -ms-transform-style: $style;
    -o-transform-style: $style;
    transform-style: $style;
}
@mixin rotate($deg:45deg){
    @include transform(rotate($deg));
}
@mixin scale($factor:.5){
    @include transform(scale($factor));
}
@mixin translate($x,$y){
    @include transform(translate($x,$y));
}
@mixin translate3d($x,$y,$z) {
    @include transform(translate3d($x,$y,$z));
}
@mixin translateHardware($x,$y) {
    @include translate($x,$y);
    -webkit-transform: translate3d($x,$y,0);
    -moz-transform: translate3d($x,$y,0);
    -o-transform: translate3d($x,$y,0);
    -ms-transform: translate3d($x,$y,0);
    transform: translate3d($x,$y,0);
}

// Transitions

@mixin transition($args:200ms) {
    -webkit-transition: $args;
    -moz-transition: $args;
    -o-transition: $args;
    -ms-transition: $args;
    transition: $args;
}
@mixin transition-delay($delay:0) {
    -webkit-transition-delay: $delay;
    -moz-transition-delay: $delay;
    -o-transition-delay: $delay;
    -ms-transition-delay: $delay;
    transition-delay: $delay;
}
@mixin transition-duration($duration:200ms) {
    -webkit-transition-duration: $duration;
    -moz-transition-duration: $duration;
    -o-transition-duration: $duration;
    -ms-transition-duration: $duration;
    transition-duration: $duration;
}
@mixin transition-property($property:all) {
    -webkit-transition-property: $property;
    -moz-transition-property: $property;
    -o-transition-property: $property;
    -ms-transition-property: $property;
    transition-property: $property;
}
@mixin transition-timing-function($function:ease) {
    -webkit-transition-timing-function: $function;
    -moz-transition-timing-function: $function;
    -o-transition-timing-function: $function;
    -ms-transition-timing-function: $function;
    transition-timing-function: $function;
}


/**
 * Extend jquery with a scrollspy plugin.
 * This watches the window scroll and fires events when elements are scrolled into viewport.
 *
 * throttle() and getTime() taken from Underscore.js
 * https://github.com/jashkenas/underscore
 *
 * @author Copyright 2013 John Smart
 * @license https://raw.github.com/thesmart/jquery-scrollspy/master/LICENSE
 * @see https://github.com/thesmart
 * @version 0.1.2
 */
(function($) {

	var jWindow = $(window);
	var elements = [];
	var elementsInView = [];
	var isSpying = false;
	var ticks = 0;
	var unique_id = 1;
	var offset = {
		top : 0,
		right : 0,
		bottom : 0,
		left : 0,
	}

	/**
	 * Find elements that are within the boundary
	 * @param {number} top
	 * @param {number} right
	 * @param {number} bottom
	 * @param {number} left
	 * @return {jQuery}		A collection of elements
	 */
	function findElements(top, right, bottom, left) {
		var hits = $();
		$.each(elements, function(i, element) {
			if (element.height() > 0) {
				var elTop = element.offset().top,
					elLeft = element.offset().left,
					elRight = elLeft + element.width(),
					elBottom = elTop + element.height();

				var isIntersect = !(elLeft > right ||
					elRight < left ||
					elTop > bottom ||
					elBottom < top);

				if (isIntersect) {
					hits.push(element);
				}
			}
		});

		return hits;
	}

	/**
	 * Called when the user scrolls the window
	 */
	function onScroll() {
		// unique tick id
		++ticks;

		// viewport rectangle
		var top = jWindow.scrollTop(),
			left = jWindow.scrollLeft(),
			right = left + jWindow.width(),
			bottom = top + jWindow.height();

		// determine which elements are in view
//        + 60 accounts for fixed nav
		var intersections = findElements(top+offset.top + 200, right+offset.right, bottom+offset.bottom, left+offset.left);
		$.each(intersections, function(i, element) {

			var lastTick = element.data('scrollSpy:ticks');
			if (typeof lastTick != 'number') {
				// entered into view
				element.triggerHandler('scrollSpy:enter');
			}

			// update tick id
			element.data('scrollSpy:ticks', ticks);
		});

		// determine which elements are no longer in view
		$.each(elementsInView, function(i, element) {
			var lastTick = element.data('scrollSpy:ticks');
			if (typeof lastTick == 'number' && lastTick !== ticks) {
				// exited from view
				element.triggerHandler('scrollSpy:exit');
				element.data('scrollSpy:ticks', null);
			}
		});

		// remember elements in view for next tick
		elementsInView = intersections;
	}

	/**
	 * Called when window is resized
	*/
	function onWinSize() {
		jWindow.trigger('scrollSpy:winSize');
	}

	/**
	 * Get time in ms
   * @license https://raw.github.com/jashkenas/underscore/master/LICENSE
	 * @type {function}
	 * @return {number}
	 */
	var getTime = (Date.now || function () {
		return new Date().getTime();
	});

	/**
	 * Returns a function, that, when invoked, will only be triggered at most once
	 * during a given window of time. Normally, the throttled function will run
	 * as much as it can, without ever going more than once per `wait` duration;
	 * but if you'd like to disable the execution on the leading edge, pass
	 * `{leading: false}`. To disable execution on the trailing edge, ditto.
	 * @license https://raw.github.com/jashkenas/underscore/master/LICENSE
	 * @param {function} func
	 * @param {number} wait
	 * @param {Object=} options
	 * @returns {Function}
	 */
	function throttle(func, wait, options) {
		var context, args, result;
		var timeout = null;
		var previous = 0;
		options || (options = {});
		var later = function () {
			previous = options.leading === false ? 0 : getTime();
			timeout = null;
			result = func.apply(context, args);
			context = args = null;
		};
		return function () {
			var now = getTime();
			if (!previous && options.leading === false) previous = now;
			var remaining = wait - (now - previous);
			context = this;
			args = arguments;
			if (remaining <= 0) {
				clearTimeout(timeout);
				timeout = null;
				previous = now;
				result = func.apply(context, args);
				context = args = null;
			} else if (!timeout && options.trailing !== false) {
				timeout = setTimeout(later, remaining);
			}
			return result;
		};
	};

	/**
	 * Enables ScrollSpy using a selector
	 * @param {jQuery|string} selector  The elements collection, or a selector
	 * @param {Object=} options	Optional.
        throttle : number -> scrollspy throttling. Default: 100 ms
        offsetTop : number -> offset from top. Default: 0
        offsetRight : number -> offset from right. Default: 0
        offsetBottom : number -> offset from bottom. Default: 0
        offsetLeft : number -> offset from left. Default: 0
	 * @returns {jQuery}
	 */
	$.scrollSpy = function(selector, options) {
		var visible = [];
		selector = $(selector);
		selector.each(function(i, element) {
			elements.push($(element));
			$(element).data("scrollSpy:id", i);
			// Smooth scroll to section
		  $('a[href=#' + $(element).attr('id') + ']').click(function(e) {
		    e.preventDefault();
		    var offset = $(this.hash).offset().top + 1;

//          offset - 200 allows elements near bottom of page to scroll

	    	$('html, body').animate({ scrollTop: offset - 200 }, {duration: 400, queue: false, easing: 'easeOutCubic'});

		  });
		});
		options = options || {
			throttle: 100
		};

		offset.top = options.offsetTop || 0;
		offset.right = options.offsetRight || 0;
		offset.bottom = options.offsetBottom || 0;
		offset.left = options.offsetLeft || 0;

		var throttledScroll = throttle(onScroll, options.throttle || 100);
		var readyScroll = function(){
			$(document).ready(throttledScroll);
		};

		if (!isSpying) {
			jWindow.on('scroll', readyScroll);
			jWindow.on('resize', readyScroll);
			isSpying = true;
		}

		// perform a scan once, after current execution context, and after dom is ready
		setTimeout(readyScroll, 0);

		selector.on('scrollSpy:enter', function() {
			visible = $.grep(visible, function(value) {
	      return value.height() != 0;
	    });

			var $this = $(this);

			if (visible[0]) {
				$('a[href=#' + visible[0].attr('id') + ']').removeClass('active');
				if ($this.data('scrollSpy:id') < visible[0].data('scrollSpy:id')) {
					visible.unshift($(this));
				}
				else {
					visible.push($(this));
				}
			}
			else {
				visible.push($(this));
			}

			$('a[href=#' + visible[0].attr('id') + ']').addClass('active');
		});
		selector.on('scrollSpy:exit', function() {
			visible = $.grep(visible, function(value) {
	      return value.height() != 0;
	    });

			if (visible[0]) {
				$('a[href=#' + visible[0].attr('id') + ']').removeClass('active');
				var $this = $(this);
				visible = $.grep(visible, function(value) {
	        return value.attr('id') != $this.attr('id');
	      });
	      if (visible[0]) { // Check if empty
					$('a[href=#' + visible[0].attr('id') + ']').addClass('active');
	      }
			}
		});

		return selector;
	};

	/**
	 * Listen for window resize events
	 * @param {Object=} options						Optional. Set { throttle: number } to change throttling. Default: 100 ms
	 * @returns {jQuery}		$(window)
	 */
	$.winSizeSpy = function(options) {
		$.winSizeSpy = function() { return jWindow; }; // lock from multiple calls
		options = options || {
			throttle: 100
		};
		return jWindow.on('resize', throttle(onWinSize, options.throttle || 100));
	};

	/**
	 * Enables ScrollSpy on a collection of elements
	 * e.g. $('.scrollSpy').scrollSpy()
	 * @param {Object=} options	Optional.
											throttle : number -> scrollspy throttling. Default: 100 ms
											offsetTop : number -> offset from top. Default: 0
											offsetRight : number -> offset from right. Default: 0
											offsetBottom : number -> offset from bottom. Default: 0
											offsetLeft : number -> offset from left. Default: 0
	 * @returns {jQuery}
	 */
	$.fn.scrollSpy = function(options) {
		return $.scrollSpy($(this), options);
	};

})(jQuery);


Introduction

Add a dropdown list to any button. Make sure that the data-activates attribute matches the id in the <ul> tag.

You can add a divider with the <li class="divider"></li> tag.

        Drop Me!

one

two

three

  <!-- Dropdown Trigger -->
  <a class='dropdown-button btn' href='#' data-activates='dropdown1'>Drop Me!</a>

  <!-- Dropdown Structure -->
  <ul id='dropdown1' class='dropdown-content'>
    <li><a href="#!">one</a></li>
    <li><a href="#!">two</a></li>
    <li class="divider"></li>
    <li><a href="#!">three</a></li>
  </ul>

Options

                Option Name
                Description

              induration
              The duration of the transition enter in milliseconds. Default: 300

              outduration
              The duration of the transition out in milliseconds. Default: 225

              constrainwidth
              If true, constrainWidth to the size of the dropdown activator. Default: true

              hover
              If true, the dropdown will open on hover. Default: false

              gutter
              This defines the spacing from the aligned edge. Default: 0

              beloworigin
              If true, the dropdown will show below the activator. Default: false

To use these inline you have to add them as data attributes. If you want more dynamic control, you can define them using the jQuery plugin below.

  <a class='dropdown-button btn' data-beloworigin="true" href='#' data-activates='dropdown1'>Drop Me!</a>

jQuery Plugin Initialization

Initialization for dropdowns is only necessary if you create them dynamically.

  $('.dropdown-button').dropdown({
      inDuration: 300,
      outDuration: 225,
      constrain_width: false, // Does not change width of dropdown to that of the activator
      hover: true, // Activate on hover
      gutter: 0, // Spacing from edge
      belowOrigin: false // Displays dropdown below the button
    }
  );

Introduction

Options

Initialization


.input-field input[type=search]:focus + label i, .input-field input[type=search]:focus ~ .mdi-navigation-close {

.input-field input[type=search]:focus + label i, .input-field input[type=search]:focus ~ .mdi-navigation-close, .input-field input[type=search]:focus ~ .material-icons {


Introduction

This is a slide out menu. You can add a dropdown to your sidebar by using our collapsible component. If you want to see a demo, our sidebar will use this on smaller screens. To use this in conjunction with a fullscreen navigation, you have to use two copies of the same UL.

HTML Structure

<nav>
  <ul class="right hide-on-med-and-down">
    <li><a href="#!">First Sidebar Link</a></li>
    <li><a href="#!">Second Sidebar Link</a></li>
  </ul>
  <ul id="slide-out" class="side-nav">
    <li><a href="#!">First Sidebar Link</a></li>
    <li><a href="#!">Second Sidebar Link</a></li>
  </ul>
  <a href="#" data-activates="slide-out" class="button-collapse"><i class="mdi-navigation-menu"></i></a>
</nav>

jQuery Plugin Initialization

  // Initialize collapse button
  $(".button-collapse").sideNav();
  // Initialize collapsible (uncomment the line below if you use the dropdown variation)
  //$('.collapsible').collapsible();

Options

You can customize the sideNav by setting your own width and the alignment of the menu.

  $('.button-collapse').sideNav({
      menuWidth: 300, // Default is 240
      edge: 'right', // Choose the horizontal origin
      closeOnClick: true // Closes side-nav on <a> clicks, useful for Angular/Meteor
    }
  );

jQuery Plugin Methods

We have methods to show and hide your sidebar you can use to programmatically control your sidebar.

// Show sideNav
$('.button-collapse').sideNav('show');
// Hide sideNav
$('.button-collapse').sideNav('hide');

Variations

Dropdown HTML Structure

  <ul id="slide-out" class="side-nav">
    <li><a href="#!">First Sidebar Link</a></li>
    <li><a href="#!">Second Sidebar Link</a></li>
    <li class="no-padding">
      <ul class="collapsible collapsible-accordion">
        <li>
          <a class="collapsible-header">Dropdown<i class="mdi-navigation-arrow-drop-down"></i></a>
          <div class="collapsible-body">
            <ul>
              <li><a href="#!">First</a></li>
              <li><a href="#!">Second</a></li>
              <li><a href="#!">Third</a></li>
              <li><a href="#!">Fourth</a></li>
            </ul>
          </div>
        </li>
      </ul>
    </li>
  </ul>
  <ul class="right hide-on-med-and-down">
    <li><a href="#!">First Sidebar Link</a></li>
    <li><a href="#!">Second Sidebar Link</a></li>
    <li><a class="dropdown-button" href="#!" data-activates="dropdown1">Dropdown<i class="mdi-navigation-arrow-drop-down right"></i></a></li>
    <ul id='dropdown1' class='dropdown-content'>
      <li><a href="#!">First</a></li>
      <li><a href="#!">Second</a></li>
      <li><a href="#!">Third</a></li>
      <li><a href="#!">Fourth</a></li>
    </ul>
  </ul>
  <a href="#" data-activates="slide-out" class="button-collapse"><i class="mdi-navigation-menu"></i></a>

Fullscreen HTML Structure

If you want the menu to be accessible on all screensizes you just have to add a simple helper class show-on-large to the .button-collapse.

  <ul id="slide-out" class="side-nav">
    <li><a href="#!">First Sidebar Link</a></li>
    <li><a href="#!">Second Sidebar Link</a></li>
  </ul>
  <a href="#" data-activates="slide-out" class="button-collapse show-on-large"><i class="mdi-navigation-menu"></i></a>

Fixed HTML Structure

Add the class fixed to have the sideNav be fixed and open on large screens and hides to the regular functionality on smaller screens. Our sideNav on the left is an example of this.

  <ul id="slide-out" class="side-nav fixed">
    <li><a href="#!">First Sidebar Link</a></li>
    <li><a href="#!">Second Sidebar Link</a></li>
  </ul>
  <a href="#" data-activates="slide-out" class="button-collapse"><i class="mdi-navigation-menu"></i></a>

If you are planning on using this you will have to offset your content by the width of the side menu. Place the padding on where the offset content will be, which in our case is in header, main and footer.

  header, main, footer {
    padding-left: 240px;
  }

  @media only screen and (max-width : 992px) {
    header, main, footer {
      padding-left: 0;
    }
  }

Introduction

Structure

Intialization

Plugin Options

Methods

Variations


* Remove unsynced `flipY` from render textures in `Shader` and `DynamicTexture`.

# Version 3.80.1 - Nino - 27 February 2024

# Bug Fixes

* Fix `RenderTexture` crashing in the presence of a light.
* Fix failure to restore compressed textures after WebGL context loss.
* Fix a single WebGL error, with no visual side-effects, from occurring while calling `Shader.setRenderToTexture()` after the game has started running. Actually, the root cause was leaving new WebGL textures bound after creation.
* Ensure that `TextureSource.setFlipY` always updates the texture.


/**
 * @author       Richard Davey <rich@phaser.io>
 * @copyright    2013-2024 Phaser Studio Inc.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Class = require('../../utils/Class');
var Components = require('../components');
var Sprite = require('../sprite/Sprite');

/**
 * @classdesc
 * A PathFollower Game Object.
 *
 * A PathFollower is a Sprite Game Object with some extra helpers to allow it to follow a Path automatically.
 *
 * Anything you can do with a standard Sprite can be done with this PathFollower, such as animate it, tint it,
 * scale it and so on.
 *
 * PathFollowers are bound to a single Path at any one time and can traverse the length of the Path, from start
 * to finish, forwards or backwards, or from any given point on the Path to its end. They can optionally rotate
 * to face the direction of the path, be offset from the path coordinates or rotate independently of the Path.
 *
 * @class PathFollower
 * @extends Phaser.GameObjects.Sprite
 * @memberof Phaser.GameObjects
 * @constructor
 * @since 3.0.0
 *
 * @extends Phaser.GameObjects.Components.PathFollower
 *
 * @param {Phaser.Scene} scene - The Scene to which this PathFollower belongs.
 * @param {Phaser.Curves.Path} path - The Path this PathFollower is following. It can only follow one Path at a time.
 * @param {number} x - The horizontal position of this Game Object in the world.
 * @param {number} y - The vertical position of this Game Object in the world.
 * @param {(string|Phaser.Textures.Texture)} texture - The key, or instance of the Texture this Game Object will use to render with, as stored in the Texture Manager.
 * @param {(string|number)} [frame] - An optional frame from the Texture this Game Object is rendering with.
 */
var PathFollower = new Class({

    Extends: Sprite,

    Mixins: [
        Components.PathFollower
    ],

    initialize:

    function PathFollower (scene, path, x, y, texture, frame)
    {
        Sprite.call(this, scene, x, y, texture, frame);

        this.path = path;
    },

    /**
     * Internal update handler that advances this PathFollower along the path.
     *
     * Called automatically by the Scene step, should not typically be called directly.
     *
     * @method Phaser.GameObjects.PathFollower#preUpdate
     * @protected
     * @since 3.0.0
     *
     * @param {number} time - The current timestamp as generated by the Request Animation Frame or SetTimeout.
     * @param {number} delta - The delta time, in ms, elapsed since the last frame.
     */
    preUpdate: function (time, delta)
    {
        this.anims.update(time, delta);
        this.pathUpdate(time);
    }

});

module.exports = PathFollower;


/**
 * @author       Richard Davey <rich@phaser.io>
 * @copyright    2013-2024 Phaser Studio Inc.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var ArcRender = require('./ArcRender');
var Class = require('../../../utils/Class');
var DegToRad = require('../../../math/DegToRad');
var Earcut = require('../../../geom/polygon/Earcut');
var GeomCircle = require('../../../geom/circle/Circle');
var MATH_CONST = require('../../../math/const');
var Shape = require('../Shape');

/**
 * @classdesc
 * The Arc Shape is a Game Object that can be added to a Scene, Group or Container. You can
 * treat it like any other Game Object in your game, such as tweening it, scaling it, or enabling
 * it for input or physics. It provides a quick and easy way for you to render this shape in your
 * game without using a texture, while still taking advantage of being fully batched in WebGL.
 *
 * This shape supports both fill and stroke colors.
 *
 * When it renders it displays an arc shape. You can control the start and end angles of the arc,
 * as well as if the angles are winding clockwise or anti-clockwise. With the default settings
 * it renders as a complete circle. By changing the angles you can create other arc shapes,
 * such as half-circles.
 *
 * Arcs also have an `iterations` property and corresponding `setIterations` method. This allows
 * you to control how smooth the shape renders in WebGL, by controlling the number of iterations
 * that take place during construction.
 *
 * @class Arc
 * @extends Phaser.GameObjects.Shape
 * @memberof Phaser.GameObjects
 * @constructor
 * @since 3.13.0
 *
 * @param {Phaser.Scene} scene - The Scene to which this Game Object belongs. A Game Object can only belong to one Scene at a time.
 * @param {number} [x=0] - The horizontal position of this Game Object in the world.
 * @param {number} [y=0] - The vertical position of this Game Object in the world.
 * @param {number} [radius=128] - The radius of the arc.
 * @param {number} [startAngle=0] - The start angle of the arc, in degrees.
 * @param {number} [endAngle=360] - The end angle of the arc, in degrees.
 * @param {boolean} [anticlockwise=false] - The winding order of the start and end angles.
 * @param {number} [fillColor] - The color the arc will be filled with, i.e. 0xff0000 for red.
 * @param {number} [fillAlpha] - The alpha the arc will be filled with. You can also set the alpha of the overall Shape using its `alpha` property.
 */
var Arc = new Class({

    Extends: Shape,

    Mixins: [
        ArcRender
    ],

    initialize:

    function Arc (scene, x, y, radius, startAngle, endAngle, anticlockwise, fillColor, fillAlpha)
    {
        if (x === undefined) { x = 0; }
        if (y === undefined) { y = 0; }
        if (radius === undefined) { radius = 128; }
        if (startAngle === undefined) { startAngle = 0; }
        if (endAngle === undefined) { endAngle = 360; }
        if (anticlockwise === undefined) { anticlockwise = false; }

        Shape.call(this, scene, 'Arc', new GeomCircle(0, 0, radius));

        /**
         * Private internal value. Holds the start angle in degrees.
         *
         * @name Phaser.GameObjects.Arc#_startAngle
         * @type {number}
         * @private
         * @since 3.13.0
         */
        this._startAngle = startAngle;

        /**
         * Private internal value. Holds the end angle in degrees.
         *
         * @name Phaser.GameObjects.Arc#_endAngle
         * @type {number}
         * @private
         * @since 3.13.0
         */
        this._endAngle = endAngle;

        /**
         * Private internal value. Holds the winding order of the start and end angles.
         *
         * @name Phaser.GameObjects.Arc#_anticlockwise
         * @type {boolean}
         * @private
         * @since 3.13.0
         */
        this._anticlockwise = anticlockwise;

        /**
         * Private internal value. Holds the number of iterations used when drawing the arc.
         *
         * @name Phaser.GameObjects.Arc#_iterations
         * @type {number}
         * @default 0.01
         * @private
         * @since 3.13.0
         */
        this._iterations = 0.01;

        this.setPosition(x, y);

        var diameter = this.geom.radius * 2;
        this.setSize(diameter, diameter);

        if (fillColor !== undefined)
        {
            this.setFillStyle(fillColor, fillAlpha);
        }

        this.updateDisplayOrigin();
        this.updateData();
    },

    /**
     * The number of iterations used when drawing the arc.
     * Increase this value for smoother arcs, at the cost of more polygons being rendered.
     * Modify this value by small amounts, such as 0.01.
     *
     * @name Phaser.GameObjects.Arc#iterations
     * @type {number}
     * @default 0.01
     * @since 3.13.0
     */
    iterations: {

        get: function ()
        {
            return this._iterations;
        },

        set: function (value)
        {
            this._iterations = value;

            this.updateData();
        }

    },

    /**
     * The radius of the arc.
     *
     * @name Phaser.GameObjects.Arc#radius
     * @type {number}
     * @since 3.13.0
     */
    radius: {

        get: function ()
        {
            return this.geom.radius;
        },

        set: function (value)
        {
            this.geom.radius = value;

            var diameter = value * 2;
            this.setSize(diameter, diameter);
            this.updateDisplayOrigin();
            this.updateData();
        }

    },

    /**
     * The start angle of the arc, in degrees.
     *
     * @name Phaser.GameObjects.Arc#startAngle
     * @type {number}
     * @since 3.13.0
     */
    startAngle: {

        get: function ()
        {
            return this._startAngle;
        },

        set: function (value)
        {
            this._startAngle = value;

            this.updateData();
        }

    },

    /**
     * The end angle of the arc, in degrees.
     *
     * @name Phaser.GameObjects.Arc#endAngle
     * @type {number}
     * @since 3.13.0
     */
    endAngle: {

        get: function ()
        {
            return this._endAngle;
        },

        set: function (value)
        {
            this._endAngle = value;

            this.updateData();
        }

    },

    /**
     * The winding order of the start and end angles.
     *
     * @name Phaser.GameObjects.Arc#anticlockwise
     * @type {boolean}
     * @since 3.13.0
     */
    anticlockwise: {

        get: function ()
        {
            return this._anticlockwise;
        },

        set: function (value)
        {
            this._anticlockwise = value;

            this.updateData();
        }

    },

    /**
     * Sets the radius of the arc.
     * This call can be chained.
     *
     * @method Phaser.GameObjects.Arc#setRadius
     * @since 3.13.0
     *
     * @param {number} value - The value to set the radius to.
     *
     * @return {this} This Game Object instance.
     */
    setRadius: function (value)
    {
        this.radius = value;

        return this;
    },

    /**
     * Sets the number of iterations used when drawing the arc.
     * Increase this value for smoother arcs, at the cost of more polygons being rendered.
     * Modify this value by small amounts, such as 0.01.
     * This call can be chained.
     *
     * @method Phaser.GameObjects.Arc#setIterations
     * @since 3.13.0
     *
     * @param {number} value - The value to set the iterations to.
     *
     * @return {this} This Game Object instance.
     */
    setIterations: function (value)
    {
        if (value === undefined) { value = 0.01; }

        this.iterations = value;

        return this;
    },

    /**
     * Sets the starting angle of the arc, in degrees.
     * This call can be chained.
     *
     * @method Phaser.GameObjects.Arc#setStartAngle
     * @since 3.13.0
     *
     * @param {number} value - The value to set the starting angle to.
     *
     * @return {this} This Game Object instance.
     */
    setStartAngle: function (angle, anticlockwise)
    {
        this._startAngle = angle;

        if (anticlockwise !== undefined)
        {
            this._anticlockwise = anticlockwise;
        }

        return this.updateData();
    },

    /**
     * Sets the ending angle of the arc, in degrees.
     * This call can be chained.
     *
     * @method Phaser.GameObjects.Arc#setEndAngle
     * @since 3.13.0
     *
     * @param {number} value - The value to set the ending angle to.
     *
     * @return {this} This Game Object instance.
     */
    setEndAngle: function (angle, anticlockwise)
    {
        this._endAngle = angle;

        if (anticlockwise !== undefined)
        {
            this._anticlockwise = anticlockwise;
        }

        return this.updateData();
    },

    /**
     * Internal method that updates the data and path values.
     *
     * @method Phaser.GameObjects.Arc#updateData
     * @private
     * @since 3.13.0
     *
     * @return {this} This Game Object instance.
     */
    updateData: function ()
    {
        var step = this._iterations;
        var iteration = step;

        var radius = this.geom.radius;
        var startAngle = DegToRad(this._startAngle);
        var endAngle = DegToRad(this._endAngle);
        var anticlockwise = this._anticlockwise;

        var x = radius;
        var y = radius;

        endAngle -= startAngle;

        if (anticlockwise)
        {
            if (endAngle < -MATH_CONST.PI2)
            {
                endAngle = -MATH_CONST.PI2;
            }
            else if (endAngle > 0)
            {
                endAngle = -MATH_CONST.PI2 + endAngle % MATH_CONST.PI2;
            }
        }
        else if (endAngle > MATH_CONST.PI2)
        {
            endAngle = MATH_CONST.PI2;
        }
        else if (endAngle < 0)
        {
            endAngle = MATH_CONST.PI2 + endAngle % MATH_CONST.PI2;
        }

        var path = [ x + Math.cos(startAngle) * radius, y + Math.sin(startAngle) * radius ];

        var ta;

        while (iteration < 1)
        {
            ta = endAngle * iteration + startAngle;

            path.push(x + Math.cos(ta) * radius, y + Math.sin(ta) * radius);

            iteration += step;
        }

        ta = endAngle + startAngle;

        path.push(x + Math.cos(ta) * radius, y + Math.sin(ta) * radius);

        path.push(x + Math.cos(startAngle) * radius, y + Math.sin(startAngle) * radius);

        this.pathIndexes = Earcut(path);
        this.pathData = path;

        return this;
    }

});

module.exports = Arc;


/**
* A polyfill for Array.isArray
*/
if (!Array.isArray)
{
    Array.isArray = function (arg)
    {
        return Object.prototype.toString.call(arg) === '[object Array]';
    };
}