import { INodeProperties } from 'n8n-workflow';

export const invoiceOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		noDataExpression: true,
		displayOptions: {
			show: {
				resource: ['invoice'],
			},
		},
		options: [
			{
				name: 'Create',
				value: 'create',
				description: 'Create a invoice',
				action: 'Create an invoice',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get a invoice',
				action: 'Get an invoice',
			},
			{
				name: 'Get Many',
				value: 'getAll',
				description: 'Get many invoices',
				action: 'Get many invoices',
			},
			{
				name: 'Update',
				value: 'update',
				description: 'Update a invoice',
				action: 'Update an invoice',
			},
		],
		default: 'create',
	},
];

export const invoiceFields: INodeProperties[] = [
	/* -------------------------------------------------------------------------- */
	/*                                invoice:create                              */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Organization Name or ID',
		name: 'organizationId',
		type: 'options',
		description:
			'Choose from the list, or specify an ID using an expression',
		typeOptions: {
			loadOptionsMethod: 'getTenants',
		},
		default: '',
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['create'],
			},
		},
		required: true,
	},
	{
		displayName: 'Type',
		name: 'type',
		type: 'options',
		options: [
			{
				name: 'Bill',
				value: 'ACCPAY',
				description: 'Accounts Payable or supplier invoice',
			},
			{
				name: 'Sales Invoice',
				value: 'ACCREC',
				description: 'Accounts Receivable or customer invoice',
			},
		],
		default: '',
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['create'],
			},
		},
		required: true,
		description: 'Invoice Type',
	},
	{
		displayName: 'Contact ID',
		name: 'contactId',
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['create'],
			},
		},
		required: true,
	},
	{
		displayName: 'Line Items',
		name: 'lineItemsUi',
		placeholder: 'Add Line Item',
		type: 'fixedCollection',
		default: {},
		typeOptions: {
			multipleValues: true,
		},
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['create'],
			},
		},
		description: 'Line item data',
		options: [
			{
				name: 'lineItemsValues',
				displayName: 'Line Item',
				values: [
					{
						displayName: 'Description',
						name: 'description',
						type: 'string',
						default: '',
						description: 'A line item with just a description',
					},
					{
						displayName: 'Quantity',
						name: 'quantity',
						type: 'number',
						default: 1,
						typeOptions: {
							minValue: 1,
						},
						description: 'LineItem Quantity',
					},
					{
						displayName: 'Unit Amount',
						name: 'unitAmount',
						type: 'string',
						default: '',
						description:
							'Lineitem unit amount. By default, unit amount will be rounded to two decimal places.',
					},
					{
						displayName: 'Item Code Name or ID',
						name: 'itemCode',
						type: 'options',
						description:
							'Choose from the list, or specify an ID using an expression',
						typeOptions: {
							loadOptionsMethod: 'getItemCodes',
							loadOptionsDependsOn: ['organizationId'],
						},
						default: '',
					},
					{
						displayName: 'Account Code Name or ID',
						name: 'accountCode',
						type: 'options',
						description:
							'Choose from the list, or specify an ID using an expression',
						typeOptions: {
							loadOptionsMethod: 'getAccountCodes',
							loadOptionsDependsOn: ['organizationId'],
						},
						default: '',
					},
					{
						displayName: 'Tax Type',
						name: 'taxType',
						type: 'options',
						options: [
							{
								name: 'Tax on Purchases',
								value: 'INPUT',
							},
							{
								name: 'Tax Exempt',
								value: 'NONE',
							},
							{
								name: 'Tax on Sales',
								value: 'OUTPUT',
							},
							{
								name: 'Sales Tax on Imports',
								value: 'GSTONIMPORTS',
							},
						],
						default: '',
						required: true,
					},
					{
						displayName: 'Tax Amount',
						name: 'taxAmount',
						type: 'string',
						default: '',
						description:
							'The tax amount is auto calculated as a percentage of the line amount based on the tax rate',
					},
					{
						displayName: 'Line Amount',
						name: 'lineAmount',
						type: 'string',
						default: '',
						description:
							'The line amount reflects the discounted price if a DiscountRate has been used',
					},
					{
						displayName: 'Discount Rate',
						name: 'discountRate',
						type: 'string',
						default: '',
						description:
							'Percentage discount or discount amount being applied to a line item. Only supported on ACCREC invoices - ACCPAY invoices and credit notes in Xero do not support discounts.',
					},
					// {
					// 	displayName: 'Tracking',
					// 	name: 'trackingUi',
					// 	placeholder: 'Add Tracking',
					// 	description: 'Any LineItem can have a maximum of 2 TrackingCategory elements.',
					// 	type: 'fixedCollection',
					// 	typeOptions: {
					// 		multipleValues: true,
					// 	},
					// 	default: {},
					// 	options: [
					// 		{
					// 			name: 'trackingValues',
					// 			displayName: 'Tracking',
					// 			values: [
					// 				{
					// 					displayName: 'Name',
					// 					name: 'name',
					// 					type: 'options',
					// 					typeOptions: {
					// 						loadOptionsMethod: 'getTrakingCategories',
					// 						loadOptionsDependsOn: [
					// 							'organizationId',
					// 						],
					// 					},
					// 					default: '',
					// 					description: 'Name of the tracking category',
					// 				},
					// 				{
					// 					displayName: 'Option',
					// 					name: 'option',
					// 					type: 'options',
					// 					typeOptions: {
					// 						loadOptionsMethod: 'getTrakingOptions',
					// 						loadOptionsDependsOn: [
					// 							'/name',
					// 						],
					// 					},
					// 					default: '',
					// 					description: 'Name of the option',
					// 				},
					// 			],
					// 		},
					// 	],
					// },
				],
			},
		],
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['create'],
			},
		},
		options: [
			{
				displayName: 'Branding Theme Name or ID',
				name: 'brandingThemeId',
				type: 'options',
				description:
					'Choose from the list, or specify an ID using an expression',
				typeOptions: {
					loadOptionsMethod: 'getBrandingThemes',
					loadOptionsDependsOn: ['organizationId'],
				},
				default: '',
			},
			{
				displayName: 'Currency Name or ID',
				name: 'currency',
				type: 'options',
				description:
					'Choose from the list, or specify an ID using an expression',
				typeOptions: {
					loadOptionsMethod: 'getCurrencies',
					loadOptionsDependsOn: ['organizationId'],
				},
				default: '',
			},
			{
				displayName: 'Currency Rate',
				name: 'currencyRate',
				type: 'string',
				default: '',
				description:
					'The currency rate for a multicurrency invoice. If no rate is specified, the XE.com day rate is used.',
			},
			{
				displayName: 'Date',
				name: 'date',
				type: 'dateTime',
				default: '',
				description:
					'Date invoice was issued - YYYY-MM-DD. If the Date element is not specified it will default to the current date based on the timezone setting of the organisation.',
			},
			{
				displayName: 'Due Date',
				name: 'dueDate',
				type: 'dateTime',
				default: '',
				description: 'Date invoice is due - YYYY-MM-DD',
			},
			{
				displayName: 'Expected Payment Date',
				name: 'expectedPaymentDate',
				type: 'dateTime',
				default: '',
				description: 'Shown on sales invoices (Accounts Receivable) when this has been set',
			},
			{
				displayName: 'Invoice Number',
				name: 'invoiceNumber',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Line Amount Type',
				name: 'lineAmountType',
				type: 'options',
				options: [
					{
						name: 'Exclusive',
						value: 'Exclusive',
						description: 'Line items are exclusive of tax',
					},
					{
						name: 'Inclusive',
						value: 'Inclusive',
						description: 'Line items are inclusive tax',
					},
					{
						name: 'NoTax',
						value: 'NoTax',
						description: 'Line have no tax',
					},
				],
				default: 'Exclusive',
			},
			{
				displayName: 'Planned Payment Date',
				name: 'plannedPaymentDate',
				type: 'dateTime',
				default: '',
				description: 'Shown on bills (Accounts Payable) when this has been set',
			},
			{
				displayName: 'Reference',
				name: 'reference',
				type: 'string',
				default: '',
				description: 'ACCREC only - additional reference number (max length = 255)',
			},
			{
				displayName: 'Send To Contact',
				name: 'sendToContact',
				type: 'boolean',
				default: false,
				description:
					'Whether the invoice in the Xero app should be marked as "sent". This can be set only on invoices that have been approved.',
			},
			{
				displayName: 'Status',
				name: 'status',
				type: 'options',
				options: [
					{
						name: 'Draft',
						value: 'DRAFT',
					},
					{
						name: 'Submitted',
						value: 'SUBMITTED',
					},
					{
						name: 'Authorised',
						value: 'AUTHORISED',
					},
				],
				default: 'DRAFT',
			},
			{
				displayName: 'URL',
				name: 'url',
				type: 'string',
				default: '',
				description: 'URL link to a source document - shown as "Go to [appName]" in the Xero app',
			},
		],
	},
	/* -------------------------------------------------------------------------- */
	/*                                invoice:update                              */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Organization Name or ID',
		name: 'organizationId',
		type: 'options',
		description:
			'Choose from the list, or specify an ID using an expression',
		typeOptions: {
			loadOptionsMethod: 'getTenants',
		},
		default: '',
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['update'],
			},
		},
		required: true,
	},
	{
		displayName: 'Invoice ID',
		name: 'invoiceId',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['update'],
			},
		},
	},
	{
		displayName: 'Update Fields',
		name: 'updateFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['update'],
			},
		},
		options: [
			{
				displayName: 'Branding Theme Name or ID',
				name: 'brandingThemeId',
				type: 'options',
				description:
					'Choose from the list, or specify an ID using an expression',
				typeOptions: {
					loadOptionsMethod: 'getBrandingThemes',
					loadOptionsDependsOn: ['organizationId'],
				},
				default: '',
			},
			{
				displayName: 'Contact ID',
				name: 'contactId',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Currency Name or ID',
				name: 'currency',
				type: 'options',
				description:
					'Choose from the list, or specify an ID using an expression',
				typeOptions: {
					loadOptionsMethod: 'getCurrencies',
					loadOptionsDependsOn: ['organizationId'],
				},
				default: '',
			},
			{
				displayName: 'Currency Rate',
				name: 'currencyRate',
				type: 'string',
				default: '',
				description:
					'The currency rate for a multicurrency invoice. If no rate is specified, the XE.com day rate is used.',
			},
			{
				displayName: 'Date',
				name: 'date',
				type: 'dateTime',
				default: '',
				description:
					'Date invoice was issued - YYYY-MM-DD. If the Date element is not specified it will default to the current date based on the timezone setting of the organisation.',
			},
			{
				displayName: 'Due Date',
				name: 'dueDate',
				type: 'dateTime',
				default: '',
				description: 'Date invoice is due - YYYY-MM-DD',
			},
			{
				displayName: 'Expected Payment Date',
				name: 'expectedPaymentDate',
				type: 'dateTime',
				default: '',
				description: 'Shown on sales invoices (Accounts Receivable) when this has been set',
			},
			{
				displayName: 'Invoice Number',
				name: 'invoiceNumber',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Line Amount Type',
				name: 'lineAmountType',
				type: 'options',
				options: [
					{
						name: 'Exclusive',
						value: 'Exclusive',
						description: 'Line items are exclusive of tax',
					},
					{
						name: 'Inclusive',
						value: 'Inclusive',
						description: 'Line items are inclusive tax',
					},
					{
						name: 'NoTax',
						value: 'NoTax',
						description: 'Line have no tax',
					},
				],
				default: 'Exclusive',
			},
			{
				displayName: 'Line Items',
				name: 'lineItemsUi',
				placeholder: 'Add Line Item',
				type: 'fixedCollection',
				default: {},
				typeOptions: {
					multipleValues: true,
				},
				description: 'Line item data',
				options: [
					{
						name: 'lineItemsValues',
						displayName: 'Line Item',
						values: [
							{
								displayName: 'Line Item ID',
								name: 'lineItemId',
								type: 'string',
								default: '',
								description: 'The Xero generated identifier for a LineItem',
							},
							{
								displayName: 'Description',
								name: 'description',
								type: 'string',
								default: '',
								description: 'A line item with just a description',
							},
							{
								displayName: 'Quantity',
								name: 'quantity',
								type: 'number',
								default: 1,
								typeOptions: {
									minValue: 1,
								},
								description: 'LineItem Quantity',
							},
							{
								displayName: 'Unit Amount',
								name: 'unitAmount',
								type: 'string',
								default: '',
								description:
									'Lineitem unit amount. By default, unit amount will be rounded to two decimal places.',
							},
							{
								displayName: 'Item Code Name or ID',
								name: 'itemCode',
								type: 'options',
								description:
									'Choose from the list, or specify an ID using an expression',
								typeOptions: {
									loadOptionsMethod: 'getItemCodes',
									loadOptionsDependsOn: ['organizationId'],
								},
								default: '',
							},
							{
								displayName: 'Account Code Name or ID',
								name: 'accountCode',
								type: 'options',
								description:
									'Choose from the list, or specify an ID using an expression',
								typeOptions: {
									loadOptionsMethod: 'getAccountCodes',
									loadOptionsDependsOn: ['organizationId'],
								},
								default: '',
							},
							{
								displayName: 'Tax Type',
								name: 'taxType',
								type: 'options',
								options: [
									{
										name: 'Tax on Purchases',
										value: 'INPUT',
									},
									{
										name: 'Tax Exempt',
										value: 'NONE',
									},
									{
										name: 'Tax on Sales',
										value: 'OUTPUT',
									},
									{
										name: 'Sales Tax on Imports',
										value: 'GSTONIMPORTS',
									},
								],
								default: '',
								required: true,
							},
							{
								displayName: 'Tax Amount',
								name: 'taxAmount',
								type: 'string',
								default: '',
								description:
									'The tax amount is auto calculated as a percentage of the line amount based on the tax rate',
							},
							{
								displayName: 'Line Amount',
								name: 'lineAmount',
								type: 'string',
								default: '',
								description:
									'The line amount reflects the discounted price if a DiscountRate has been used',
							},
							{
								displayName: 'Discount Rate',
								name: 'discountRate',
								type: 'string',
								default: '',
								description:
									'Percentage discount or discount amount being applied to a line item. Only supported on ACCREC invoices - ACCPAY invoices and credit notes in Xero do not support discounts.',
							},
							// {
							// 	displayName: 'Tracking',
							// 	name: 'trackingUi',
							// 	placeholder: 'Add Tracking',
							// 	description: 'Any LineItem can have a maximum of 2 TrackingCategory elements.',
							// 	type: 'fixedCollection',
							// 	typeOptions: {
							// 		multipleValues: true,
							// 	},
							// 	default: {},
							// 	options: [
							// 		{
							// 			name: 'trackingValues',
							// 			displayName: 'Tracking',
							// 			values: [
							// 				{
							// 					displayName: 'Name',
							// 					name: 'name',
							// 					type: 'options',
							// 					typeOptions: {
							// 						loadOptionsMethod: 'getTrakingCategories',
							// 						loadOptionsDependsOn: [
							// 							'organizationId',
							// 						],
							// 					},
							// 					default: '',
							// 					description: 'Name of the tracking category',
							// 				},
							// 				{
							// 					displayName: 'Option',
							// 					name: 'option',
							// 					type: 'options',
							// 					typeOptions: {
							// 						loadOptionsMethod: 'getTrakingOptions',
							// 						loadOptionsDependsOn: [
							// 							'/name',
							// 						],
							// 					},
							// 					default: '',
							// 					description: 'Name of the option',
							// 				},
							// 			],
							// 		},
							// 	],
							// },
						],
					},
				],
			},
			{
				displayName: 'Planned Payment Date',
				name: 'plannedPaymentDate',
				type: 'dateTime',
				default: '',
				description: 'Shown on bills (Accounts Payable) when this has been set',
			},
			{
				displayName: 'Reference',
				name: 'reference',
				type: 'string',
				default: '',
				description: 'ACCREC only - additional reference number (max length = 255)',
			},
			{
				displayName: 'Send To Contact',
				name: 'sendToContact',
				type: 'boolean',
				default: false,
				description:
					'Whether the invoice in the Xero app should be marked as "sent". This can be set only on invoices that have been approved.',
			},
			{
				displayName: 'Status',
				name: 'status',
				type: 'options',
				options: [
					{
						name: 'Draft',
						value: 'DRAFT',
					},
					{
						name: 'Submitted',
						value: 'SUBMITTED',
					},
					{
						name: 'Authorised',
						value: 'AUTHORISED',
					},
				],
				default: 'DRAFT',
			},
			{
				displayName: 'URL',
				name: 'url',
				type: 'string',
				default: '',
				description: 'URL link to a source document - shown as "Go to [appName]" in the Xero app',
			},
		],
	},
	/* -------------------------------------------------------------------------- */
	/*                                 invoice:get                                */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Organization Name or ID',
		name: 'organizationId',
		type: 'options',
		description:
			'Choose from the list, or specify an ID using an expression',
		typeOptions: {
			loadOptionsMethod: 'getTenants',
		},
		default: '',
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['get'],
			},
		},
		required: true,
	},
	{
		displayName: 'Invoice ID',
		name: 'invoiceId',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['get'],
			},
		},
	},
	/* -------------------------------------------------------------------------- */
	/*                                   invoice:getAll                           */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Organization Name or ID',
		name: 'organizationId',
		type: 'options',
		description:
			'Choose from the list, or specify an ID using an expression',
		typeOptions: {
			loadOptionsMethod: 'getTenants',
		},
		default: '',
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['getAll'],
			},
		},
		required: true,
	},
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['getAll'],
			},
		},
		default: false,
		description: 'Whether to return all results or only up to a given limit',
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['getAll'],
				returnAll: [false],
			},
		},
		typeOptions: {
			minValue: 1,
			maxValue: 100,
		},
		default: 100,
		description: 'Max number of results to return',
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Option',
		default: {},
		displayOptions: {
			show: {
				resource: ['invoice'],
				operation: ['getAll'],
			},
		},
		options: [
			{
				displayName: 'Created By My App',
				name: 'createdByMyApp',
				type: 'boolean',
				default: false,
				description: "Whether you'll only retrieve Invoices created by your app",
			},
			{
				displayName: 'Order By',
				name: 'orderBy',
				type: 'string',
				placeholder: 'InvoiceID',
				default: '',
				description: 'Order by any element returned',
			},
			{
				displayName: 'Sort Order',
				name: 'sortOrder',
				type: 'options',
				options: [
					{
						name: 'Asc',
						value: 'ASC',
					},
					{
						name: 'Desc',
						value: 'DESC',
					},
				],
				default: '',
			},
			{
				displayName: 'Statuses',
				name: 'statuses',
				type: 'multiOptions',
				options: [
					{
						name: 'Draft',
						value: 'DRAFT',
					},
					{
						name: 'Submitted',
						value: 'SUBMITTED',
					},
					{
						name: 'Authorised',
						value: 'AUTHORISED',
					},
				],
				default: [],
			},
			{
				displayName: 'Where',
				name: 'where',
				type: 'string',
				placeholder: 'EmailAddress!=null&&EmailAddress.StartsWith("boom")',
				default: '',
				description:
					'The where parameter allows you to filter on endpoints and elements that don\'t have explicit parameters. Examples Here.',
			},
		],
	},
];


import ParameterInputWrapper from '@/components/ParameterInputWrapper.vue';

import { INodeParameters, INodeProperties, INodePropertyMode } from 'n8n-workflow';

import { BaseTextKey } from '@/plugins/i18n';

import { useNDVStore } from '@/stores/ndv';

import { INodeParameters, INodeProperties, INodePropertyMode, IParameterLabel } from 'n8n-workflow';

import { MigrationInterface, QueryRunner } from 'typeorm';

import config from '@/config';

export class CreateIndexStoppedAt1594828256133 implements MigrationInterface {
	name = 'CreateIndexStoppedAt1594828256133';

	async up(queryRunner: QueryRunner): Promise<void> {
		let tablePrefix = config.getEnv('database.tablePrefix');
		const tablePrefixPure = tablePrefix;
		const schema = config.getEnv('database.postgresdb.schema');
		if (schema) {
			tablePrefix = schema + '.' + tablePrefix;
		}

		await queryRunner.query(`SET search_path TO ${schema};`);

		await queryRunner.query(
			`CREATE INDEX IF NOT EXISTS IDX_${tablePrefixPure}33228da131bb1112247cf52a42 ON ${tablePrefix}execution_entity ("stoppedAt") `,
		);
	}

	async down(queryRunner: QueryRunner): Promise<void> {
		let tablePrefix = config.getEnv('database.tablePrefix');

		const tablePrefixPure = tablePrefix;
		const schema = config.getEnv('database.postgresdb.schema');
		if (schema) {
			tablePrefix = schema + '.' + tablePrefix;
		}
		await queryRunner.query(`SET search_path TO ${schema};`);
		await queryRunner.query(`DROP INDEX IDX_${tablePrefixPure}33228da131bb1112247cf52a42`);
	}
}


type: [Number, String, Boolean, Array, Object] as PropType<INodeParameters>,

const mappingTooltipDismissHandler = this.onMappingTooltipDismissed.bind(this);

keep keep keep keep add keep keep keep keep keep

type: Object as PropType<IParameterLabel>,

<template>
	<n8n-input-label
		:label="$locale.credText().inputLabelDisplayName(parameter)"
		:tooltipText="$locale.credText().inputLabelDescription(parameter)"
		:required="parameter.required"
		:showTooltip="focused"
		:showOptions="menuExpanded"
		:data-test-id="parameter.name"
	>
		<template #options>
			<parameter-options
				:parameter="parameter"
				:value="value"
				:isReadOnly="false"
				:showOptions="true"
				:isValueExpression="isValueExpression"
				@optionSelected="optionSelected"
				@menu-expanded="onMenuExpanded"
			/>
		</template>
		<template>
			<parameter-input-wrapper
				ref="param"
				inputSize="large"
				:parameter="parameter"
				:value="value"
				:path="parameter.name"
				:hideIssues="true"
				:documentationUrl="documentationUrl"
				:errorHighlight="showRequiredErrors"
				:isForCredential="true"
				:eventSource="eventSource"
				:hint="!showRequiredErrors ? hint : ''"
				@focus="onFocus"
				@blur="onBlur"
				@textInput="valueChanged"
				@valueChanged="valueChanged"
			/>
			<div :class="$style.errors" v-if="showRequiredErrors">
				<n8n-text color="danger" size="small">
					{{ $locale.baseText('parameterInputExpanded.thisFieldIsRequired') }}
					<n8n-link
						v-if="documentationUrl"
						:to="documentationUrl"
						size="small"
						:underline="true"
						@click="onDocumentationUrlClick"
					>
						{{ $locale.baseText('parameterInputExpanded.openDocs') }}
					</n8n-link>
				</n8n-text>
			</div>
		</template>
	</n8n-input-label>
</template>

<script lang="ts">
import { IUpdateInformation } from '@/Interface';
import ParameterOptions from './ParameterOptions.vue';
import Vue, { PropType } from 'vue';
import ParameterInputWrapper from './ParameterInputWrapper.vue';
import { isValueExpression } from '@/utils';
import { INodeParameterResourceLocator, INodeProperties } from 'n8n-workflow';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';

export default Vue.extend({
	name: 'parameter-input-expanded',
	components: {
		ParameterOptions,
		ParameterInputWrapper,
	},
	props: {
		parameter: {
			type: Object as PropType<INodeProperties>,
		},
		value: {},
		showValidationWarnings: {
			type: Boolean,
		},
		documentationUrl: {
			type: String,
		},
		eventSource: {
			type: String,
		},
	},
	data() {
		return {
			focused: false,
			blurredEver: false,
			menuExpanded: false,
		};
	},
	computed: {
		...mapStores(useWorkflowsStore),
		showRequiredErrors(): boolean {
			if (!this.$props.parameter.required) {
				return false;
			}

			if (this.blurredEver || this.showValidationWarnings) {
				if (this.$props.parameter.type === 'string') {
					return !this.value;
				}

				if (this.$props.parameter.type === 'number') {
					return typeof this.value !== 'number';
				}
			}

			return false;
		},
		hint(): string | null {
			if (this.isValueExpression) {
				return null;
			}

			return this.$locale.credText().hint(this.parameter);
		},
		isValueExpression(): boolean {
			return isValueExpression(
				this.parameter,
				this.value as string | INodeParameterResourceLocator,
			);
		},
	},
	methods: {
		onFocus() {
			this.focused = true;
		},
		onBlur() {
			this.blurredEver = true;
			this.focused = false;
		},
		onMenuExpanded(expanded: boolean) {
			this.menuExpanded = expanded;
		},
		optionSelected(command: string) {
			if (this.$refs.param) {
				(this.$refs.param as Vue).$emit('optionSelected', command);
			}
		},
		valueChanged(parameterData: IUpdateInformation) {
			this.$emit('change', parameterData);
		},
		onDocumentationUrlClick(): void {
			this.$telemetry.track('User clicked credential modal docs link', {
				docs_link: this.documentationUrl,
				source: 'field',
				workflow_id: this.workflowsStore.workflowId,
			});
		},
	},
});
</script>

<style lang="scss" module>
.errors {
	margin-top: var(--spacing-2xs);
}
.hint {
	margin-top: var(--spacing-4xs);
}
</style>


import type Bull from 'bull';
import type { RedisOptions } from 'ioredis';
import { IExecuteResponsePromiseData } from 'n8n-workflow';
import config from '@/config';
import * as ActiveExecutions from '@/ActiveExecutions';
import * as WebhookHelpers from '@/WebhookHelpers';

export type Job = Bull.Job<JobData>;
export type JobQueue = Bull.Queue<JobData>;

export interface JobData {
	executionId: string;
	loadStaticData: boolean;
}

export interface JobResponse {
	success: boolean;
}

export interface WebhookResponse {
	executionId: string;
	response: IExecuteResponsePromiseData;
}

export class Queue {
	private jobQueue: JobQueue;

	constructor(private activeExecutions: ActiveExecutions.ActiveExecutions) {}

	async init() {
		const prefix = config.getEnv('queue.bull.prefix');
		const redisOptions: RedisOptions = config.getEnv('queue.bull.redis');

		// eslint-disable-next-line @typescript-eslint/naming-convention
		const { default: Bull } = await import('bull');

		// Disabling ready check is necessary as it allows worker to
		// quickly reconnect to Redis if Redis crashes or is unreachable
		// for some time. With it enabled, worker might take minutes to realize
		// redis is back up and resume working.
		// More here: https://github.com/OptimalBits/bull/issues/890
		// @ts-ignore
		this.jobQueue = new Bull('jobs', { prefix, redis: redisOptions, enableReadyCheck: false });

		this.jobQueue.on('global:progress', (jobId, progress: WebhookResponse) => {
			this.activeExecutions.resolveResponsePromise(
				// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
				progress.executionId,
				WebhookHelpers.decodeWebhookResponse(progress.response),
			);
		});
	}

	async add(jobData: JobData, jobOptions: object): Promise<Job> {
		return this.jobQueue.add(jobData, jobOptions);
	}

	async getJob(jobId: Bull.JobId): Promise<Job | null> {
		return this.jobQueue.getJob(jobId);
	}

	async getJobs(jobTypes: Bull.JobStatus[]): Promise<Job[]> {
		return this.jobQueue.getJobs(jobTypes);
	}

	getBullObjectInstance(): JobQueue {
		return this.jobQueue;
	}

	/**
	 *
	 * @param job A Job instance
	 * @returns boolean true if we were able to securely stop the job
	 */
	async stopJob(job: Job): Promise<boolean> {
		if (await job.isActive()) {
			// Job is already running so tell it to stop
			await job.progress(-1);
			return true;
		}
		// Job did not get started yet so remove from queue
		try {
			await job.remove();
			return true;
		} catch (e) {
			await job.progress(-1);
		}

		return false;
	}
}

let activeQueueInstance: Queue | undefined;

export async function getInstance(): Promise<Queue> {
	if (activeQueueInstance === undefined) {
		activeQueueInstance = new Queue(ActiveExecutions.getInstance());
		await activeQueueInstance.init();
	}

	return activeQueueInstance;
}


import { IDataObject } from 'n8n-workflow';

export interface ICompany {
	address?: IDataObject;
	company_name?: string;
	email_address?: string;
	fax_number?: IDataObject;
	notes?: string;
	opt_in_reason?: string;
	phone_number?: IDataObject;
	website?: string;
}


<template>

		<run-data-json-actions
			v-if="!editMode.enabled"
			:node="node"
			:sessioId="sessionId"
			:displayMode="displayMode"
			:distanceFromActive="distanceFromActive"
			:selectedJsonPath="selectedJsonPath"
			:jsonData="jsonData"
			:paneType="paneType"
		/>
		<draggable
			type="mapping"
			targetDataKey="mappable"
			:disabled="!mappingEnabled"
			@dragstart="onDragStart"
			@dragend="onDragEnd"
		>
			<template #preview="{ canDrop, el }">
				<MappingPill v-if="el" :html="getShortKey(el)" :can-drop="canDrop" />
			</template>
			<template>
				<vue-json-pretty
					:data="jsonData"
					:deep="10"
					:showLength="true"
					:selected-value.sync="selectedJsonPath"
					rootPath=""
					selectableType="single"
					class="json-data"
				>
					<template #nodeKey="{ node }">
						"{{ node.key }}"
					</template>
					<template #nodeValue="{ node }">
						{{ getContent(node.content) }}
						{{ getContent(node.content) }}
					</template>
				</vue-json-pretty>
			</template>
		</draggable>

</template>


export interface IParameterDependencies {

export type IParameterLabel = {
size?: 'small' | 'medium';
};


import { IExecuteFunctions, IHookFunctions, ILoadOptionsFunctions } from 'n8n-core';

import { IDataObject, INodePropertyOptions, NodeApiError, NodeOperationError } from 'n8n-workflow';

import { OptionsWithUri } from 'request';

export interface ICustomInterface {
	name: string;
	key: string;
	field_type: string;
	options?: Array<{
		id: number;
		label: string;
	}>;
}

export interface ICustomProperties {
	[key: string]: ICustomInterface;
}

/**
 * Make an API request to Pipedrive
 *
 */
export async function pipedriveApiRequest(
	this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
	method: string,
	endpoint: string,
	body: IDataObject,
	query: IDataObject = {},
	formData?: IDataObject,
	downloadFile?: boolean,
): Promise<any> {
	const authenticationMethod = this.getNodeParameter('authentication', 0);

	const options: OptionsWithUri = {
		headers: {
			Accept: 'application/json',
		},
		method,
		qs: query,
		uri: `https://api.pipedrive.com/v1${endpoint}`,
	};

	if (downloadFile === true) {
		options.encoding = null;
	} else {
		options.json = true;
	}

	if (Object.keys(body).length !== 0) {
		options.body = body;
	}

	if (formData !== undefined && Object.keys(formData).length !== 0) {
		options.formData = formData;
	}

	if (query === undefined) {
		query = {};
	}

	try {
		const credentialType =
			authenticationMethod === 'apiToken' ? 'pipedriveApi' : 'pipedriveOAuth2Api';
		const responseData = await this.helpers.requestWithAuthentication.call(
			this,
			credentialType,
			options,
		);

		if (downloadFile === true) {
			return {
				data: responseData,
			};
		}

		if (responseData.success === false) {
			throw new NodeApiError(this.getNode(), responseData);
		}

		return {
			additionalData: responseData.additional_data,
			data: responseData.data === null ? [] : responseData.data,
		};
	} catch (error) {
		throw new NodeApiError(this.getNode(), error);
	}
}

/**
 * Make an API request to paginated Pipedrive endpoint
 * and return all results
 *
 * @param {(IHookFunctions | IExecuteFunctions)} this
 */
export async function pipedriveApiRequestAllItems(
	this: IHookFunctions | IExecuteFunctions,
	method: string,
	endpoint: string,
	body: IDataObject,
	query?: IDataObject,
): Promise<any> {
	if (query === undefined) {
		query = {};
	}
	query.limit = 100;
	query.start = 0;

	const returnData: IDataObject[] = [];

	let responseData;

	do {
		responseData = await pipedriveApiRequest.call(this, method, endpoint, body, query);
		// the search path returns data diferently
		if (responseData.data.items) {
			returnData.push.apply(returnData, responseData.data.items);
		} else {
			returnData.push.apply(returnData, responseData.data);
		}

		query.start = responseData.additionalData.pagination.next_start;
	} while (responseData.additionalData?.pagination?.more_items_in_collection === true);

	return {
		data: returnData,
	};
}

/**
 * Gets the custom properties from Pipedrive
 *
 * @param {(IHookFunctions | IExecuteFunctions)} this
 */
export async function pipedriveGetCustomProperties(
	this: IHookFunctions | IExecuteFunctions,
	resource: string,
): Promise<ICustomProperties> {
	const endpoints: { [key: string]: string } = {
		activity: '/activityFields',
		deal: '/dealFields',
		organization: '/organizationFields',
		person: '/personFields',
		product: '/productFields',
	};

	if (endpoints[resource] === undefined) {
		throw new NodeOperationError(
			this.getNode(),
			`The resource "${resource}" is not supported for resolving custom values!`,
		);
	}

	const requestMethod = 'GET';

	const body = {};
	const qs = {};
	// Get the custom properties and their values
	const responseData = await pipedriveApiRequest.call(
		this,
		requestMethod,
		endpoints[resource],
		body,
		qs,
	);

	const customProperties: ICustomProperties = {};

	for (const customPropertyData of responseData.data) {
		customProperties[customPropertyData.key] = customPropertyData;
	}
	return customProperties;
}

/**
 * Converts names and values of custom properties from their actual values to the
 * Pipedrive internal ones
 *
 */
export function pipedriveEncodeCustomProperties(
	customProperties: ICustomProperties,
	item: IDataObject,
): void {
	let customPropertyData;

	for (const key of Object.keys(item)) {
		customPropertyData = Object.values(customProperties).find(
			(propertyData) => propertyData.name === key,
		);

		if (customPropertyData !== undefined) {
			// Is a custom property

			// Check if also the value has to be resolved or just the key
			if (
				item[key] !== null &&
				item[key] !== undefined &&
				customPropertyData.options !== undefined &&
				Array.isArray(customPropertyData.options)
			) {
				// Has an option key so get the actual option-value
				const propertyOption = customPropertyData.options.find(
					(option) => option.label.toString() === item[key]!.toString(),
				);

				if (propertyOption !== undefined) {
					item[customPropertyData.key] = propertyOption.id;
					delete item[key];
				}
			} else {
				// Does already represent the actual value or is null
				item[customPropertyData.key] = item[key];
				delete item[key];
			}
		}
	}
}

/**
 * Converts names and values of custom properties to their actual values
 *
 */
export function pipedriveResolveCustomProperties(
	customProperties: ICustomProperties,
	item: IDataObject,
): void {
	let customPropertyData;

	const json = item.json as IDataObject;

	// Itterate over all keys and replace the custom ones
	for (const key of Object.keys(json)) {
		if (customProperties[key] !== undefined) {
			// Is a custom property
			customPropertyData = customProperties[key];

			// value is not set, so nothing to resolve
			if (json[key] === null) {
				json[customPropertyData.name] = json[key];
				delete json[key];
				continue;
			}

			if (
				[
					'date',
					'address',
					'double',
					'monetary',
					'org',
					'people',
					'phone',
					'text',
					'time',
					'user',
					'varchar',
					'varchar_auto',
					'int',
					'time',
					'timerange',
				].includes(customPropertyData.field_type)
			) {
				json[customPropertyData.name] = json[key];
				delete json[key];
				// type options
			} else if (
				['enum', 'visible_to'].includes(customPropertyData.field_type) &&
				customPropertyData.options
			) {
				const propertyOption = customPropertyData.options.find(
					(option) => option.id.toString() === json[key]!.toString(),
				);
				if (propertyOption !== undefined) {
					json[customPropertyData.name] = propertyOption.label;
					delete json[key];
				}
				// type multioptions
			} else if (['set'].includes(customPropertyData.field_type) && customPropertyData.options) {
				const selectedIds = (json[key] as string).split(',');
				const selectedLabels = customPropertyData.options
					.filter((option) => selectedIds.includes(option.id.toString()))
					.map((option) => option.label);
				json[customPropertyData.name] = selectedLabels;
				delete json[key];
			}
		}
	}
	item.json = json;
}

export function sortOptionParameters(
	optionParameters: INodePropertyOptions[],
): INodePropertyOptions[] {
	optionParameters.sort((a, b) => {
		const aName = a.name.toLowerCase();
		const bName = b.name.toLowerCase();
		if (aName < bName) {
			return -1;
		}
		if (aName > bName) {
			return 1;
		}
		return 0;
	});

	return optionParameters;
}


![n8n.io - Workflow Automation](https://user-images.githubusercontent.com/65276001/173571060-9f2f6d7b-bac0-43b6-bdb2-001da9694058.png)

# n8n - Workflow Automation Tool

n8n is a free and open [fair-code](http://faircode.io) distributed node-based Workflow Automation Tool. You can self-host n8n, easily extend it, and even use it with internal tools.

## Contents

- [Demo](#demo)
- [Getting Started](#getting-started)
  - [Use npx](#use-npx)
  - [Run with Docker](#run-with-docker)
  - [Install with npm](#install-with-npm)
  - [Sign-up on n8n.cloud](#sign-up-on-n8n.cloud)
- [Available integrations](#available-integrations)
- [Documentation](#documentation)
- [Create Custom Nodes](#create-custom-nodes)
- [Contributing](#contributing)
- [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it)
- [Support](#support)
- [Jobs](#jobs)
- [Upgrading](#upgrading)
- [License](#license)

## Demo

📺 Here's a [:tv: short video (< 4 min)](https://www.youtube.com/watch?v=RpjQTGKm-ok) that goes over key concepts of creating workflows in n8n.

## Getting Started

There are a couple of ways to get started with n8n.

### Use npx

To spin up n8n using npx, you can run:

```bash
npx n8n
```

It will download everything that is needed to start n8n.

You can then access n8n by opening:
[http://localhost:5678](http://localhost:5678)

**Note:** The minimum required version for Node.js is v14.15. Make sure to update Node.js to v14.15 or above.

### Run with Docker

To play around with n8n, you can also start it using Docker:

```bash
docker run -it --rm \
  --name n8n \
  -p 5678:5678 \
  n8nio/n8n
```

Be aware that all the data will be lost once the Docker container gets removed. To persist the data mount the `~/.n8n` folder:

```bash
docker run -it --rm \
  --name n8n \
  -p 5678:5678 \
  -v ~/.n8n:/home/node/.n8n \
  n8nio/n8n
```

n8n also offers a Docker image for Raspberry Pi: `n8nio/n8n:latest-rpi`.

Refer to the [documentation](https://github.com/n8n-io/n8n/blob/master/docker/images/n8n/README.md) for more information on the Docker setup.

### Install with npm

To install n8n globally using npm:

```bash
npm install n8n -g
```

After the installation, start n8n running the following command:

```bash
n8n
# or
n8n start
```

### Sign-up on n8n.cloud

Sign-up for an [n8n.cloud](https://www.n8n.cloud/) account.

While n8n.cloud and n8n are the same in terms of features, n8n.cloud provides certain conveniences such as:

- Not having to set up and maintain your n8n instance
- Managed OAuth for authentication
- Easily upgrading to the newer n8n versions

## Available integrations

n8n has 280+ different nodes that allow you to connect various services and build your automation workflows. You can find the list of all the integrations at [https://n8n.io/integrations](https://n8n.io/integrations)

## Documentation

To learn more about n8n, refer to the official documentation here: [https://docs.n8n.io](https://docs.n8n.io)

You can find additional information and example workflows on the [n8n.io](https://n8n.io) website.

## Create Custom Nodes

You can create custom nodes for n8n. Follow the instructions mentioned in the documentation to create your node: [Creating nodes](https://docs.n8n.io/integrations/creating-nodes/build/)

## Contributing

🐛 Did you find a bug?

✨ Do you want to contribute a feature?

The [CONTRIBUTING guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md) will help you set up your development environment.

You can find more information on how you can contribute to the project on our documentation: [How can I contribute?](https://docs.n8n.io/reference/contributing.html)

## What does n8n mean, and how do you pronounce it?

**Short answer:** n8n is an abbreviation for "nodemation", and it is pronounced as n-eight-n.

**Long answer:** In n8n, you build your automation ("-mation") workflows by connecting different nodes in the Editor UI. The project is also built using Node.js. As a consequence, the project was named nodemation.

However, the name was long, and it wouldn't be a good idea to use such a long name in the CLI. Hence, nodemation got abbreviated as "n8n" (there are eight characters between the first and the last n!).

## Support

If you run into issues or have any questions reach out to us via our community forum: [https://community.n8n.io](https://community.n8n.io).

## Jobs

If you are interested in working at n8n and building the project, check out the [job openings](https://apply.workable.com/n8n/).

## Upgrading

Before you upgrade to the latest version, make sure to check the changelogs: [Changelog](https://docs.n8n.io/reference/changelog.html)

You can also find breaking changes here: [Breaking Changes](./BREAKING-CHANGES.md)

## License

n8n is [fair-code](http://faircode.io) distributed under the [**Sustainable Use License**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md).

Additional information about the license can be found in the [docs](https://docs.n8n.io/reference/license/).


position: relative
&__actions
align-items: center
display: flex
padding: 8px 4px
> *,

keep keep keep keep keep replace keep keep

@import '../bootstrap'
@import '../theme'

/** Theme */
selection-control($material)
  &.v-input--is-disabled
    .v-icon
      // needed for helper override
      color: $material.buttons.disabled !important

v-selection-control-rtl()
  .v-input--selection-controls__input
    margin-right: 0
    margin-left: 8px

theme(selection-control, "v-input--selection-controls")
rtl(v-selection-control-rtl, "v-input--selection-controls")

.v-input--selection-controls
  &__input
    color: inherit
    display: inline-flex
    flex: 0 1 auto
    height: 24px
    position: relative
    margin-right: 8px
    margin-top: 4px
    transition: color .2s cubic-bezier(.25,.8,.25,1), transform .3s cubic-bezier(.25,.8,.25,1)
    width: 24px

    input
      position: absolute
      opacity: 0

  &__ripple
    cursor: pointer
    height: 48px
    position: absolute
    transition: inherit
    width: 48px
    left: -12px
    top: calc(50% - 24px)

    &:before
      border-radius: 50%
      bottom: 0
      content: ''
      position: absolute
      opacity: .2
      left: 0
      right: 0
      top: 0
      transform-origin: center center
      transform: scale(0.2)
      transition: inherit

  .v-label
    top: 2px
    user-select: none

  &.v-input--is-focused,
  .v-radio--is-focused
    .v-input--selection-controls__ripple:before
      background: currentColor
      transform: scale(.8)


import { Vue } from 'vue/types/vue'

function createMessage (message: string, componentInstance?: Vue): string {
  const componentInfo = componentInstance ? ` in "${componentInstance.$options.name}"` : ''
  return `[Vuetify] ${message}${componentInfo}`
}

export function consoleWarn (message: string, componentInstance?: Vue): void {
  console.warn(createMessage(message, componentInstance))
}

export function consoleError (message: string, componentInstance?: Vue): void {
  console.error(createMessage(message, componentInstance))
}


// Styles
import '../../stylus/components/_range-sliders.styl'

// Extensions
import VSlider from '../VSlider'

import {
  createRange,
  deepEqual
} from '../../util/helpers'

export default {
  name: 'v-range-slider',

  extends: VSlider,

  data: vm => ({
    activeThumb: null,
    lazyValue: !vm.value.length
      ? [0, 0]
      : vm.value
  }),

  props: {
    value: {
      type: Array,
      default: () => ([])
    }
  },

  computed: {
    classes () {
      return Object.assign({}, {
        'v-input--range-slider': true
      }, VSlider.computed.classes.call(this))
    },
    internalValue: {
      get () {
        return this.lazyValue
      },
      set (val) {
        const { min, max } = this
        // Round value to ensure the
        // entire slider range can
        // be selected with step
        let value = val.map(v => this.roundValue(Math.min(Math.max(v, min), max)))

        // Switch values if range and wrong order
        if (value[0] > value[1] || value[1] < value[0]) {
          if (this.activeThumb !== null) this.activeThumb = this.activeThumb === 1 ? 0 : 1
          value = [value[1], value[0]]
        }

        this.lazyValue = value
        if (!deepEqual(value, this.value)) this.$emit('input', value)

        this.validate()
      }
    },
    inputWidth () {
      return this.internalValue.map(v => (
        this.roundValue(v) - this.min) / (this.max - this.min) * 100
      )
    },
    isDirty () {
      return this.internalValue.some(v => v !== this.min) || this.alwaysDirty
    },
    trackFillStyles () {
      const styles = VSlider.computed.trackFillStyles.call(this)
      const fillPercent = Math.abs(this.inputWidth[0] - this.inputWidth[1])

      styles.width = `calc(${fillPercent}% - ${this.trackPadding}px)`
      styles[this.$vuetify.rtl ? 'right' : 'left'] = `${this.inputWidth[0]}%`

      return styles
    },
    trackPadding () {
      if (this.isDirty ||
        this.internalValue[0]
      ) return 0

      return VSlider.computed.trackPadding.call(this)
    }
  },

  methods: {
    getIndexOfClosestValue (arr, v) {
      if (Math.abs(arr[0] - v) < Math.abs(arr[1] - v)) return 0
      else return 1
    },
    genInput () {
      return createRange(2).map(i => {
        const input = VSlider.methods.genInput.call(this)

        input.data.attrs.value = this.internalValue[i]

        input.data.on.focus = e => {
          this.activeThumb = i
          VSlider.methods.onFocus.call(this, e)
        }

        return input
      })
    },
    genChildren () {
      return [
        this.genInput(),
        this.genTrackContainer(),
        this.genSteps(),
        createRange(2).map(i => {
          const value = this.internalValue[i]
          const onDrag = e => {
            this.isActive = true
            this.activeThumb = i
            this.onThumbMouseDown(e)
          }
          const valueWidth = this.inputWidth[i]
          const isActive = (this.isFocused || this.isActive) && this.activeThumb === i

          return this.genThumbContainer(value, valueWidth, isActive, onDrag)
        })
      ]
    },
    onSliderClick (e) {
      if (!this.isActive) {
        this.isFocused = true
        this.onMouseMove(e, true)
        this.$emit('change', this.internalValue)
      }
    },
    onMouseMove (e, trackClick = false) {
      const { value, isInsideTrack } = this.parseMouseMove(e)

      if (isInsideTrack) {
        if (trackClick) this.activeThumb = this.getIndexOfClosestValue(this.internalValue, value)

        this.setInternalValue(value)
      }
    },
    onKeyDown (e) {
      const value = this.parseKeyDown(e, this.internalValue[this.activeThumb])

      if (value == null) return

      this.setInternalValue(value)
    },
    setInternalValue (value) {
      this.internalValue = this.internalValue.map((v, i) => {
        if (i === this.activeThumb) return value
        else return Number(v)
      })
    }
  }
}


/* eslint-disable max-len */

import { VueConstructor, ComponentOptions, PluginFunction, FunctionalComponentOptions } from 'vue'
import { CombinedVueInstance, Vue } from 'vue/types/vue'
import {
  RecordPropsDefinition,
  ThisTypedComponentOptionsWithArrayProps,
  ThisTypedComponentOptionsWithRecordProps
} from 'vue/types/options'

declare global {
  interface Window {
    Vue: VueConstructor
  }

  export const __VUETIFY_VERSION__: string
  export const __REQUIRED_VUE__: string
}

declare module 'vue/types/vue' {
  export type OptionsVue<Instance extends Vue, Data, Methods, Computed, Props, Options = {}> = VueConstructor<
    CombinedVueInstance<Instance, Data, Methods, Computed, Props> & Vue,
    Options
  >

  export interface RawComponentOptions<
    V extends Vue = Vue,
    Data = {} | undefined,
    Methods = {} | undefined,
    Computed = {} | undefined,
    Props = {} | undefined
  > {
    name?: string
    data: Data
    methods: Methods
    computed: {
      [C in keyof Computed]: (this: V) => Computed[C]
    }
    props: Props
  }

  interface VueConstructor<
    V extends Vue = Vue,
    Options = {} | undefined
  > {
    version: string
    install?: PluginFunction<never>
    options: Options

    extend<Data, Methods, Computed, Options, PropNames extends string = never> (options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames> & Options): OptionsVue<V, Data, Methods, Computed, Record<PropNames, any>, Options>
    extend<Data, Methods, Computed, Props, Options> (options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props> & Options): OptionsVue<V, Data, Methods, Computed, Props, Options>
    extend<Options, PropNames extends string = never> (definition: FunctionalComponentOptions<Record<PropNames, any>, PropNames[]> & Options): OptionsVue<V, {}, {}, {}, Record<PropNames, any>, Options>
    extend<Props, Options> (definition: FunctionalComponentOptions<Props, RecordPropsDefinition<Props>> & Options): OptionsVue<V, {}, {}, {}, Props, Options>
    extend<V extends Vue = Vue> (options?: ComponentOptions<V> & Options): OptionsVue<V, {}, {}, {}, {}, Options>
  }
}


import VRangeSlider from './VRangeSlider'

/* istanbul ignore next */
VRangeSlider.install = function install (Vue) {
  Vue.component(VRangeSlider.name, VRangeSlider)
}

export default VRangeSlider


// Mixins
import {
  provide as RegistrableProvide
} from '../../mixins/registrable'

// Directives
import Touch from '../../directives/touch'

export default {
  name: 'v-tabs-items',

  mixins: [RegistrableProvide('tabs')],

  directives: { Touch },

  inject: {
    registerItems: {
      default: null
    },
    tabProxy: {
      default: null
    },
    unregisterItems: {
      default: null
    }
  },

  data () {
    return {
      items: [],
      lazyValue: this.value,
      reverse: false
    }
  },

  props: {
    cycle: Boolean,
    touchless: Boolean,
    value: [Number, String]
  },

  computed: {
    activeIndex () {
      return this.items.findIndex((item, index) => (item.id || index.toString()) === this.lazyValue)
    },
    activeItem () {
      if (!this.items.length) return undefined

      return this.items[this.activeIndex]
    },
    inputValue: {
      get () {
        return this.lazyValue
      },
      set (val) {
        val = val.toString()

        this.lazyValue = val

        if (this.tabProxy) this.tabProxy(val)
        else this.$emit('input', val)
      }
    }
  },

  watch: {
    activeIndex (current, previous) {
      this.reverse = current < previous
      this.updateItems()
    },
    value (val) {
      this.lazyValue = val
    }
  },

  mounted () {
    this.registerItems && this.registerItems(this.changeModel)
  },

  beforeDestroy () {
    this.unregisterItems && this.unregisterItems()
  },

  methods: {
    changeModel (val) {
      this.inputValue = val
    },
    next (cycle) {
      let nextIndex = this.activeIndex + 1

      if (!this.items[nextIndex]) {
        if (!cycle) return
        nextIndex = 0
      }

      this.inputValue = this.items[nextIndex].id || nextIndex
    },
    prev (cycle) {
      let prevIndex = this.activeIndex - 1

      if (!this.items[prevIndex]) {
        if (!cycle) return
        prevIndex = this.items.length - 1
      }

      this.inputValue = this.items[prevIndex].id || prevIndex
    },
    onSwipe (action) {
      this[action](this.cycle)
    },
    register (item) {
      this.items.push(item)
    },
    unregister (item) {
      this.items = this.items.filter(i => i !== item)
    },
    updateItems () {
      for (let index = this.items.length; --index >= 0;) {
        this.items[index].toggle(this.lazyValue, this.reverse, this.isBooted, index)
      }
      this.isBooted = true
    }
  },

  render (h) {
    const data = {
      staticClass: 'v-tabs__items',
      directives: []
    }

    !this.touchless && data.directives.push({
      name: 'touch',
      value: {
        left: () => this.onSwipe('next'),
        right: () => this.onSwipe('prev')
      }
    })

    return h('div', data, this.$slots.default)
  }
}


import createNativeLocaleFormatter from './createNativeLocaleFormatter'
import monthChange from './monthChange'
import pad from './pad'

export {
  createNativeLocaleFormatter,
  monthChange,
  pad
}


margin: 0
.v-btn + .v-btn
margin-left: 8px

@import '../bootstrap'
@import '../theme'

/* Themes */
v-card($material)
  background-color: $material.cards
  color: $material.text.primary

theme(v-card, "v-card")

.v-card
  display: block
  border-radius: $card-border-radius
  min-width: 0
  position: relative
  text-decoration: none
  elevation(1)

  > *:first-child:not(.v-btn):not(.v-chip)
    border-top-left-radius: inherit
    border-top-right-radius: inherit

  > *:last-child:not(.v-btn):not(.v-chip)
    border-bottom-left-radius: inherit
    border-bottom-right-radius: inherit

  &--raised
    elevation(3)

  &--tile
    border-radius: 0

  &--flat
    elevation(0)

  &--hover
    cursor: pointer
    transition: all .4s cubic-bezier(.25, .8, .25, 1)
    transition-property: box-shadow

    &:hover
      elevation(8)

  &__title
    align-items: center
    display: flex
    flex-wrap: wrap
    padding: 16px

    &--primary
      padding-top: 24px

  &__text
    padding: 16px
    width: 100%

  &__media
    display: flex
    overflow: hidden
    position: relative

    img
      width: 100%

    &__background
      border-radius: inherit
      position: absolute
      left: 0
      top: 0
      width: 100%
      height: 100%

    &__content
      display: flex
      flex: 1 1 auto
      position: relative

  &__actions
    align-items: center
    display: flex
    padding: 8px 4px

    > *,
    .v-btn
      margin: 0 4px


// Components
import VPicker from '../components/VPicker'

// Mixins
import Colorable from './colorable'
import Themeable from './themeable'

export default {
  name: 'picker',

  mixins: [
    Colorable,
    Themeable
  ],

  props: {
    fullWidth: Boolean,
    headerColor: String,
    landscape: Boolean,
    noTitle: Boolean,
    width: {
      type: [Number, String],
      default: 290,
      validator: value => parseInt(value, 10) > 0
    }
  },

  methods: {
    genPickerTitle () {},
    genPickerBody () {},
    genPickerActionsSlot () {
      return this.$scopedSlots.default ? this.$scopedSlots.default({
        save: this.save,
        cancel: this.cancel
      }) : this.$slots.default
    },
    genPicker (staticClass) {
      return this.$createElement(VPicker, {
        staticClass,
        class: this.fullWidth ? ['v-picker--full-width'] : [],
        props: {
          color: this.headerColor || this.color,
          dark: this.dark,
          fullWidth: this.fullWidth,
          landscape: this.landscape,
          light: this.light,
          width: this.width
        }
      }, [
        this.noTitle ? null : this.genPickerTitle(),
        this.genPickerBody(),
        this.$createElement('template', { slot: 'actions' }, [this.genPickerActionsSlot()])
      ])
    }
  }
}


// Styles
import '../../stylus/components/_sliders.styl'

// Components
import { VScaleTransition } from '../transitions'

// Extensions
import VInput from '../VInput'

// Directives
import ClickOutside from '../../directives/click-outside'

// Utilities
import {
  addOnceEventListener,
  convertToUnit,
  createRange,
  keyCodes,
  deepEqual
} from '../../util/helpers'
import { consoleWarn } from '../../util/console'

export default {
  name: 'v-slider',

  extends: VInput,

  directives: { ClickOutside },

  data: vm => ({
    app: {},
    defaultColor: 'primary',
    isActive: false,
    keyPressed: 0,
    lazyValue: typeof vm.value !== 'undefined' ? vm.value : Number(vm.min),
    oldValue: null
  }),

  props: {
    alwaysDirty: Boolean,
    inverseLabel: Boolean,
    label: String,
    min: {
      type: [Number, String],
      default: 0
    },
    max: {
      type: [Number, String],
      default: 100
    },
    range: Boolean,
    step: {
      type: [Number, String],
      default: 1
    },
    ticks: {
      type: [Boolean, String],
      default: false,
      validator: v => typeof v === 'boolean' || v === 'always'
    },
    tickLabels: {
      type: Array,
      default: () => ([])
    },
    tickSize: {
      type: [Number, String],
      default: 1
    },
    thumbColor: {
      type: String,
      default: null
    },
    thumbLabel: {
      type: [Boolean, String],
      default: null,
      validator: v => typeof v === 'boolean' || v === 'always'
    },
    thumbSize: {
      type: [Number, String],
      default: 32
    },
    trackColor: {
      type: String,
      default: null
    },
    value: [Number, String]
  },

  computed: {
    classes () {
      return {
        'v-input--slider': true,
        'v-input--slider--ticks': this.showTicks,
        'v-input--slider--inverse-label': this.inverseLabel,
        'v-input--slider--ticks-labels': this.tickLabels.length > 0,
        'v-input--slider--thumb-label': this.thumbLabel ||
          this.$scopedSlots.thumbLabel
      }
    },
    showTicks () {
      return this.tickLabels.length > 0 ||
        (!this.disabled && this.stepNumeric && !!this.ticks)
    },
    showThumbLabel () {
      return !this.disabled && (
        !!this.thumbLabel ||
        this.thumbLabel === '' ||
        this.$scopedSlots['thumb-label']
      )
    },
    computedColor () {
      if (this.disabled) return null
      return this.validationState || this.color || this.defaultColor
    },
    computedTrackColor () {
      return this.disabled ? null : (this.trackColor || null)
    },
    computedThumbColor () {
      if (this.disabled || !this.isDirty) return null
      return this.validationState || this.thumbColor || this.color || this.defaultColor
    },
    internalValue: {
      get () {
        return this.lazyValue
      },
      set (val) {
        const { min, max } = this

        // Round value to ensure the
        // entire slider range can
        // be selected with step
        const value = this.roundValue(Math.min(Math.max(val, min), max))

        if (value === this.lazyValue) return

        this.lazyValue = value

        this.$emit('input', value)
        this.validate()
      }
    },
    stepNumeric () {
      return this.step > 0 ? parseFloat(this.step) : 0
    },
    trackFillStyles () {
      let left = this.$vuetify.rtl ? 'auto' : 0
      let right = this.$vuetify.rtl ? 0 : 'auto'
      let width = `${this.inputWidth}%`

      if (this.disabled) width = `calc(${this.inputWidth}% - 8px)`

      return {
        transition: this.trackTransition,
        left,
        right,
        width
      }
    },
    trackPadding () {
      return (
        this.isActive ||
        this.inputWidth > 0 ||
        this.disabled
      ) ? 0 : 7
    },
    trackStyles () {
      const trackPadding = this.disabled ? `calc(${this.inputWidth}% + 8px)` : `${this.trackPadding}px`
      let left = this.$vuetify.rtl ? 'auto' : trackPadding
      let right = this.$vuetify.rtl ? trackPadding : 'auto'
      let width = this.disabled
        ? `calc(${100 - this.inputWidth}% - 8px)`
        : '100%'

      return {
        transition: this.trackTransition,
        left,
        right,
        width
      }
    },
    tickStyles () {
      const size = Number(this.tickSize)

      return {
        'border-width': `${size}px`,
        'border-radius': size > 1 ? '50%' : null,
        transform: size > 1 ? `translateX(-${size}px) translateY(-${size - 1}px)` : null
      }
    },
    trackTransition () {
      return this.keyPressed >= 2 ? 'none' : ''
    },
    numTicks () {
      return Math.ceil((this.max - this.min) / this.stepNumeric)
    },
    inputWidth () {
      return (this.roundValue(this.internalValue) - this.min) / (this.max - this.min) * 100
    },
    isDirty () {
      return this.internalValue > this.min ||
        this.alwaysDirty
    }
  },

  watch: {
    min (val) {
      val > this.internalValue && this.$emit('input', parseFloat(val))
    },
    max (val) {
      val < this.internalValue && this.$emit('input', parseFloat(val))
    },
    value (val) {
      this.internalValue = val
    }
  },

  mounted () {
    // Without a v-app, iOS does not work with body selectors
    this.app = document.querySelector('[data-app]') ||
      consoleWarn('Missing v-app or a non-body wrapping element with the [data-app] attribute', this)
  },

  methods: {
    genDefaultSlot () {
      const children = [this.genLabel()]
      const slider = this.genSlider()

      this.inverseLabel
        ? children.unshift(slider)
        : children.push(slider)

      return children
    },
    genListeners () {
      return Object.assign({}, {
        blur: this.onBlur,
        click: this.onSliderClick,
        focus: this.onFocus,
        keydown: this.onKeyDown,
        keyup: this.onKeyUp
      })
    },
    genInput () {
      return this.$createElement('input', {
        attrs: {
          'aria-label': this.label,
          name: this.name,
          role: 'slider',
          tabindex: this.disabled ? -1 : undefined,
          type: 'slider',
          value: this.internalValue,
          readonly: true,
          'aria-readonly': String(this.readonly)
        },
        on: this.genListeners(),
        ref: 'input'
      })
    },
    genSlider () {
      return this.$createElement('div', {
        staticClass: 'v-slider',
        'class': {
          'v-slider--is-active': this.isActive
        },
        directives: [{
          name: 'click-outside',
          value: this.onBlur
        }]
      }, this.genChildren())
    },
    genChildren () {
      return [
        this.genInput(),
        this.genTrackContainer(),
        this.genSteps(),
        this.genThumbContainer(
          this.internalValue,
          this.inputWidth,
          this.isFocused || this.isActive,
          this.onThumbMouseDown
        )
      ]
    },
    genSteps () {
      if (!this.step || !this.showTicks) return null

      const ticks = createRange(this.numTicks + 1).map(i => {
        const children = []

        if (this.tickLabels[i]) {
          children.push(this.$createElement('span', this.tickLabels[i]))
        }

        return this.$createElement('span', {
          key: i,
          staticClass: 'v-slider__ticks',
          class: {
            'v-slider__ticks--always-show': this.ticks === 'always' ||
              this.tickLabels.length > 0
          },
          style: {
            ...this.tickStyles,
            left: `${i * (100 / this.numTicks)}%`
          }
        }, children)
      })

      return this.$createElement('div', {
        staticClass: 'v-slider__ticks-container'
      }, ticks)
    },
    genThumb () {
      return this.$createElement('div', {
        staticClass: 'v-slider__thumb',
        'class': this.addBackgroundColorClassChecks({}, this.computedThumbColor)
      })
    },
    genThumbContainer (value, valueWidth, isActive, onDrag) {
      const children = [this.genThumb()]

      const thumbLabelContent = this.getLabel(value)
      this.showThumbLabel && children.push(this.genThumbLabel(thumbLabelContent))

      return this.$createElement('div', {
        staticClass: 'v-slider__thumb-container',
        'class': this.addTextColorClassChecks({
          'v-slider__thumb-container--is-active': isActive,
          'v-slider__thumb-container--show-label': this.showThumbLabel
        }, this.computedThumbColor),
        style: {
          transition: this.trackTransition,
          left: `${this.$vuetify.rtl ? 100 - valueWidth : valueWidth}%`
        },
        on: {
          touchstart: onDrag,
          mousedown: onDrag
        }
      }, children)
    },
    genThumbLabel (content) {
      const size = convertToUnit(this.thumbSize)

      return this.$createElement(VScaleTransition, {
        props: { origin: 'bottom center' }
      }, [
        this.$createElement('div', {
          staticClass: 'v-slider__thumb-label__container',
          directives: [
            {
              name: 'show',
              value: this.isFocused || this.isActive || this.thumbLabel === 'always'
            }
          ]
        }, [
          this.$createElement('div', {
            staticClass: 'v-slider__thumb-label',
            'class': this.addBackgroundColorClassChecks({}, this.computedThumbColor),
            style: {
              height: size,
              width: size
            }
          }, [content])
        ])
      ])
    },
    genTrackContainer () {
      const children = [
        this.$createElement('div', {
          staticClass: 'v-slider__track',
          'class': this.addBackgroundColorClassChecks({}, this.computedTrackColor),
          style: this.trackStyles
        }),
        this.$createElement('div', {
          staticClass: 'v-slider__track-fill',
          'class': this.addBackgroundColorClassChecks(),
          style: this.trackFillStyles
        })
      ]

      return this.$createElement('div', {
        staticClass: 'v-slider__track__container',
        ref: 'track'
      }, children)
    },
    getLabel (value) {
      return this.$scopedSlots['thumb-label']
        ? this.$scopedSlots['thumb-label']({ value })
        : this.$createElement('span', value)
    },
    onBlur (e) {
      if (this.keyPressed === 2) return

      this.isActive = false
      this.isFocused = false
      this.$emit('blur', e)
    },
    onFocus (e) {
      this.isFocused = true
      this.$emit('focus', e)
    },
    onThumbMouseDown (e) {
      this.oldValue = this.internalValue
      this.keyPressed = 2
      const options = { passive: true }
      this.isActive = true
      this.isFocused = false

      if ('touches' in e) {
        this.app.addEventListener('touchmove', this.onMouseMove, options)
        addOnceEventListener(this.app, 'touchend', this.onMouseUp)
      } else {
        this.app.addEventListener('mousemove', this.onMouseMove, options)
        addOnceEventListener(this.app, 'mouseup', this.onMouseUp)
      }

      this.$emit('start', this.internalValue)
    },
    onMouseUp () {
      this.keyPressed = 0
      const options = { passive: true }
      this.isActive = false
      this.isFocused = false
      this.app.removeEventListener('touchmove', this.onMouseMove, options)
      this.app.removeEventListener('mousemove', this.onMouseMove, options)

      this.$emit('end', this.internalValue)
      if (!deepEqual(this.oldValue, this.internalValue)) {
        this.$emit('change', this.internalValue)
      }
    },
    onMouseMove (e) {
      const { value, isInsideTrack } = this.parseMouseMove(e)

      if (isInsideTrack) {
        this.setInternalValue(value)
      }
    },
    onKeyDown (e) {
      const value = this.parseKeyDown(e)

      if (value == null) return

      this.setInternalValue(value)
      this.$emit('change', value)
    },
    onKeyUp () {
      this.keyPressed = 0
    },
    onSliderClick (e) {
      this.isFocused = true
      this.onMouseMove(e)
      this.$emit('change', this.internalValue)
    },
    parseMouseMove (e) {
      const {
        left: offsetLeft,
        width: trackWidth
      } = this.$refs.track.getBoundingClientRect()
      const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX
      // It is possible for left to be NaN, force to number
      let left = Math.min(Math.max((clientX - offsetLeft) / trackWidth, 0), 1) || 0

      if (this.$vuetify.rtl) left = 1 - left

      const isInsideTrack = clientX >= offsetLeft - 8 && clientX <= offsetLeft + trackWidth + 8
      const value = parseFloat(this.min) + left * (this.max - this.min)

      return { value, isInsideTrack }
    },
    parseKeyDown (e, value = this.internalValue) {
      if (this.disabled) return

      const { pageup, pagedown, end, home, left, right, down, up } = keyCodes

      if (![pageup, pagedown, end, home, left, right, down, up].includes(e.keyCode)) return

      e.preventDefault()
      const step = this.stepNumeric || 1
      const steps = (this.max - this.min) / step
      if ([left, right, down, up].includes(e.keyCode)) {
        this.keyPressed += 1

        const increase = this.$vuetify.rtl ? [left, up] : [right, up]
        let direction = increase.includes(e.keyCode) ? 1 : -1
        const multiplier = e.shiftKey ? 3 : (e.ctrlKey ? 2 : 1)

        value = value + (direction * step * multiplier)
      } else if (e.keyCode === home) {
        value = parseFloat(this.min)
      } else if (e.keyCode === end) {
        value = parseFloat(this.max)
      } else /* if (e.keyCode === keyCodes.pageup || e.keyCode === pagedown) */ {
        // Page up/down
        const direction = e.keyCode === pagedown ? 1 : -1
        value = value - (direction * step * (steps > 100 ? steps / 10 : 10))
      }

      return value
    },
    roundValue (value) {
      if (!this.stepNumeric) return value
      // Format input value using the same number
      // of decimals places as in the step prop
      const trimmedStep = this.step.toString().trim()
      const decimals = trimmedStep.indexOf('.') > -1
        ? (trimmedStep.length - trimmedStep.indexOf('.') - 1)
        : 0

      const newValue = Math.round(value / this.stepNumeric) * this.stepNumeric

      return parseFloat(Math.min(newValue, this.max).toFixed(decimals))
    },
    setInternalValue (value) {
      this.internalValue = value
    }
  }
}


export default {
  methods: {
    genTProgress () {
      const col = this.$createElement('th', {
        staticClass: 'column',
        attrs: {
          colspan: this.headerColumns
        }
      }, [this.genProgress()])

      return this.genTR([col], {
        staticClass: 'v-datatable__progress'
      })
    }
  }
}


keep keep keep keep add keep keep keep keep


const delta = 0.20689655172413793 // 6÷29

const cielabForwardTransform = t => (
  t > delta ** 3
    ? Math.cbrt(t)
    : (t / (3 * delta ** 2)) + 4 / 29
)

const cielabReverseTransform = t => (
  t > delta
    ? t ** 3
    : (3 * delta ** 2) * (t - 4 / 29)
)

export function fromXYZ (xyz) {
  const transform = cielabForwardTransform
  const transformedY = transform(xyz[1])

  return [
    116 * transformedY - 16,
    500 * (transform(xyz[0] / 0.95047) - transformedY),
    200 * (transformedY - transform(xyz[2] / 1.08883))
  ]
}

export function toXYZ (lab) {
  const transform = cielabReverseTransform
  const Ln = (lab[0] + 16) / 116
  return [
    transform(Ln + lab[1] / 500) * 0.95047,
    transform(Ln),
    transform(Ln - lab[2] / 200) * 1.08883
  ]
}


@import '../bootstrap'
@import '../theme'

/* Themes */
v-icon($material)
  color: $material.icons.active

  &.v-icon--disabled
    color: $material.icons.inactive !important

theme(v-icon, "v-icon")

.v-icon
  align-items: center
  display: inline-flex
  font-feature-settings: 'liga'
  font-size: 24px
  justify-content: center
  line-height: 1
  transition: $primary-transition
  vertical-align: middle

  &--link
    cursor: pointer

  &.v-icon--large
    font-size: 2.5rem

  &.v-icon--medium
    font-size: 2rem

  &.v-icon--x-large
    font-size: 3rem

  &.v-icon--disabled
    pointer-events: none


<!--
MAKE SURE TO READ THE CONTRIBUTING GUIDE BEFORE CREATING A PR
https://github.com/vuetifyjs/vuetify/blob/master/.github/CONTRIBUTING.md
-->

<!-- Provide a general summary of your changes in the Title above -->
<!-- Keep the title short and descriptive, as it will be used as a commit message -->

## Description
<!--- Describe your changes in detail -->

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How Has This Been Tested?
<!--- Please describe how you tested your changes. -->
<!--- Have you created new tests or updated existing ones? -->
<!--- e.g. unit | visually | e2e | none -->

## Markup:
<!--- Paste markup for testing your change --->
<details>

```vue
// Paste your FULL Playground.vue here
```
</details>

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Improvement/refactoring (non-breaking change that doesn't add any feature but make things better)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] The PR title is no longer than 64 characters.
- [ ] The PR is submitted to the correct branch (`master` for bug fixes, `dev` for new features and breaking changes).
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have created a PR in the [documentation](https://github.com/vuetifyjs/vuetifyjs.com) with the necessary changes.


theme(radiogroup, "radio-group")
.v-input--radio-group
.v-label
margin-top: 4px
.v-radio
margin-bottom: 8px
&__input
display: flex

keep keep keep keep replace replace replace keep keep keep keep keep


import { createSimpleFunctional } from '../../util/helpers'

import VList from './VList'
import VListGroup from './VListGroup'
import VListTile from './VListTile'
import VListTileAction from './VListTileAction'
import VListTileAvatar from './VListTileAvatar'

export { VList, VListGroup, VListTile, VListTileAction, VListTileAvatar }
export const VListTileActionText = createSimpleFunctional('v-list__tile__action-text', 'span')
export const VListTileContent = createSimpleFunctional('v-list__tile__content', 'div')
export const VListTileTitle = createSimpleFunctional('v-list__tile__title', 'div')
export const VListTileSubTitle = createSimpleFunctional('v-list__tile__sub-title', 'div')

/* istanbul ignore next */
VList.install = function install (Vue) {
  Vue.component(VList.name, VList)
  Vue.component(VListGroup.name, VListGroup)
  Vue.component(VListTile.name, VListTile)
  Vue.component(VListTileAction.name, VListTileAction)
  Vue.component(VListTileActionText.name, VListTileActionText)
  Vue.component(VListTileAvatar.name, VListTileAvatar)
  Vue.component(VListTileContent.name, VListTileContent)
  Vue.component(VListTileSubTitle.name, VListTileSubTitle)
  Vue.component(VListTileTitle.name, VListTileTitle)
}

export default VList


function inserted (el, binding) {
  const callback = binding.value
  const options = binding.options || { passive: true }

  window.addEventListener('resize', callback, options)
  el._onResize = {
    callback,
    options
  }

  if (!binding.modifiers || !binding.modifiers.quiet) {
    callback()
  }
}

function unbind (el) {
  const { callback, options } = el._onResize

  window.removeEventListener('resize', callback, options)
  delete el._onResize
}

export default {
  name: 'resize',
  inserted,
  unbind
}


/**
 * Maskable
 *
 * @mixin
 *
 * Creates an input mask that is
 * generated from a masked str
 *
 * Example: mask="#### #### #### ####"
 */

import {
  isMaskDelimiter,
  maskText,
  unmaskText
} from '../util/mask'

export default {
  name: 'maskable',

  data: () => ({
    selection: 0,
    lazySelection: 0,
    preDefined: {
      'credit-card': '#### - #### - #### - ####',
      'date': '##/##/####',
      'date-with-time': '##/##/#### ##:##',
      'phone': '(###) ### - ####',
      'social': '###-##-####',
      'time': '##:##',
      'time-with-seconds': '##:##:##'
    }
  }),

  props: {
    dontFillMaskBlanks: Boolean,
    mask: {
      type: [Object, String],
      default: null
    },
    returnMaskedValue: Boolean
  },

  computed: {
    masked () {
      const preDefined = this.preDefined[this.mask]
      const mask = preDefined || this.mask || ''

      return mask.split('')
    }
  },

  watch: {
    /**
     * Make sure the cursor is in the correct
     * location when the mask changes
     */
    mask () {
      if (!this.$refs.input) return

      const oldValue = this.$refs.input.value
      const newValue = this.maskText(unmaskText(this.lazyValue))
      let position = 0
      let selection = this.selection

      for (let index = 0; index < selection; index++) {
        isMaskDelimiter(oldValue[index]) || position++
      }

      selection = 0
      if (newValue) {
        for (let index = 0; index < newValue.length; index++) {
          isMaskDelimiter(newValue[index]) || position--
          selection++
          if (position <= 0) break
        }
      }

      this.$nextTick(() => {
        this.$refs.input.value = newValue
        this.setCaretPosition(selection)
      })
    }
  },

  beforeMount () {
    if (!this.mask ||
      this.value == null ||
      !this.returnMaskedValue
    ) return

    const value = this.maskText(this.value)

    // See if masked value does not
    // match the user given value
    if (value === this.value) return

    this.$emit('input', value)
  },

  methods: {
    setCaretPosition (selection) {
      this.selection = selection
      window.setTimeout(() => {
        this.$refs.input && this.$refs.input.setSelectionRange(this.selection, this.selection)
      }, 0)
    },
    updateRange () {
      if (!this.$refs.input) return

      const newValue = this.maskText(this.lazyValue)
      let selection = 0

      this.$refs.input.value = newValue
      if (newValue) {
        for (let index = 0; index < newValue.length; index++) {
          if (this.lazySelection <= 0) break
          isMaskDelimiter(newValue[index]) || this.lazySelection--
          selection++
        }
      }

      this.setCaretPosition(selection)
      // this.$emit() must occur only when all internal values are correct
      this.$emit('input', this.returnMaskedValue ? this.$refs.input.value : this.lazyValue)
    },
    maskText (text) {
      return this.mask ? maskText(text, this.masked, this.dontFillMaskBlanks) : text
    },
    unmaskText (text) {
      return this.mask && !this.returnMaskedValue ? unmaskText(text) : text
    },
    // When the input changes and is
    // re-created, ensure that the
    // caret location is correct
    setSelectionRange () {
      this.$nextTick(this.updateRange)
    },
    resetSelections (input) {
      if (!input.selectionEnd) return
      this.selection = input.selectionEnd
      this.lazySelection = 0

      for (let index = 0; index < this.selection; index++) {
        isMaskDelimiter(input.value[index]) || this.lazySelection++
      }
    }
  }
}


height: auto
margin-right: 16px
outline: none
label
top: 2px // To align label
&--is-disabled
pointer-events: none

keep keep keep keep replace replace replace keep keep

@import '../bootstrap'
@import '../theme'

radiogroup($material)
  .input-group--selection-controls
    //

theme(radiogroup, "radio-group")

.v-input--radio-group
  .v-label
    margin-top: 4px

  .v-radio
    margin-bottom: 8px

  &__input
    display: flex
    width: 100%

  &--row
    .v-input--radio-group__input
      flex-direction: row
      flex-wrap: wrap

  &--column
    .v-input--radio-group__input
      flex-direction: column


function searchChildren (children) {
  const results = []
  for (let index = 0; index < children.length; index++) {
    const child = children[index]
    if (child.isActive && child.isDependent) {
      results.push(child)
    } else {
      results.push(...searchChildren(child.$children))
    }
  }

  return results
}

export default {
  name: 'dependent',

  data () {
    return {
      closeDependents: true,
      isDependent: true
    }
  },

  methods: {
    getOpenDependents () {
      if (this.closeDependents) return searchChildren(this.$children)

      return []
    },
    getOpenDependentElements () {
      const result = []
      const openDependents = this.getOpenDependents()

      for (let index = 0; index < openDependents.length; index++) {
        result.push(...openDependents[index].getClickableDependentElements())
      }

      return result
    },
    getClickableDependentElements () {
      const result = [this.$el]
      if (this.$refs.content) result.push(this.$refs.content)
      result.push(...this.getOpenDependentElements())

      return result
    }
  },

  watch: {
    isActive (val) {
      if (val) return

      const openDependents = this.getOpenDependents()
      for (let index = 0; index < openDependents.length; index++) {
        openDependents[index].isActive = false
      }
    }
  }
}


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VSelect should add color to selected index 1`] = `

                foo

              <input readonly="readonly"
                     tabindex="0"
                     type="text"
                     autocomplete="on"
                     aria-readonly="false"
              >

                  arrow_drop_down

`;

exports[`VSelect should be clearable with prop, dirty and multi select 1`] = `

                1

              <input readonly="readonly"
                     tabindex="0"
                     type="text"
                     autocomplete="on"
                     aria-readonly="false"
              >

                  clear

                  arrow_drop_down

`;

exports[`VSelect should be clearable with prop, dirty and single select 1`] = `

                1

              <input readonly="readonly"
                     tabindex="0"
                     type="text"
                     autocomplete="on"
                     aria-readonly="false"
              >

                  clear

                  arrow_drop_down

`;


@import '../bootstrap'

.v-dialog
  elevation(24)
  border-radius: $card-border-radius
  margin: 24px
  pointer-events: auto
  transition: .3s $transition.fast-in-fast-out
  width: 100%
  z-index: inherit

  &__content
    align-items: center
    display: flex
    height: 100%
    justify-content: center
    left: 0
    pointer-events: none
    position: fixed
    top: 0
    transition: .2s $transition.fast-in-fast-out
    width: 100%
    z-index: 6
    outline: none

  &:not(.v-dialog--fullscreen)
    max-height: 90%

  &__container
    display: inline-block
    vertical-align: middle

  @keyframes animate-dialog
    0%
      transform: scale(1)

    50%
      transform: scale(1.03)

    100%
      transform: scale(1)

  &--animated
    animation-duration: .15s
    animation-name: animate-dialog
    animation-timing-function: $transition.fast-in-fast-out

  &--fullscreen
    margin 0
    height 100%
    position fixed
    overflow-y: auto
    top: 0
    left 0

    > .v-card
      min-height 100%
      min-width 100%
      margin 0 !important
      padding 0 !important

  &--scrollable
    display: flex

    > .v-card
      display: flex
      flex: 1 1 auto
      flex-direction: column

      > .v-card__title
      > .v-card__actions
        flex: 1 0 auto

      > .v-card__text
        overflow-y: auto
        backface-visibility: hidden


// Styles
import '../../stylus/components/_buttons.styl'

import Vue, { VNode, ComponentOptions, VNodeChildren } from 'vue'
import { PropValidator } from 'vue/types/options'
import mixins from '../../util/mixins'

// Components
import VProgressCircular from '../VProgressCircular'

// Mixins
import Colorable from '../../mixins/colorable'
import Positionable from '../../mixins/positionable'
import Routable from '../../mixins/routable'
import Themeable from '../../mixins/themeable'
import { factory as ToggleableFactory } from '../../mixins/toggleable'
import { inject as RegistrableInject } from '../../mixins/registrable'

const VBtn = mixins(
  Colorable,
  Routable,
  Positionable,
  Themeable,
  ToggleableFactory('inputValue'),
  RegistrableInject('buttonGroup')
).extend({
  name: 'v-btn',

  props: {
    activeClass: {
      type: String,
      default: 'v-btn--active'
    },
    block: Boolean,
    depressed: Boolean,
    fab: Boolean,
    flat: Boolean,
    icon: Boolean,
    large: Boolean,
    loading: Boolean,
    outline: Boolean,
    ripple: {
      type: [Boolean, Object],
      default: true
    },
    round: Boolean,
    small: Boolean,
    tag: {
      type: String,
      default: 'button'
    },
    type: {
      type: String,
      default: 'button'
    },
    value: null as any as PropValidator<any>
  },

  computed: {
    classes (): any {
      const classes = {
        'v-btn': true,
        [this.activeClass]: this.isActive,
        'v-btn--absolute': this.absolute,
        'v-btn--block': this.block,
        'v-btn--bottom': this.bottom,
        'v-btn--disabled': this.disabled,
        'v-btn--flat': this.flat,
        'v-btn--floating': this.fab,
        'v-btn--fixed': this.fixed,
        'v-btn--icon': this.icon,
        'v-btn--large': this.large,
        'v-btn--left': this.left,
        'v-btn--loader': this.loading,
        'v-btn--outline': this.outline,
        'v-btn--depressed': (this.depressed && !this.flat) || this.outline,
        'v-btn--right': this.right,
        'v-btn--round': this.round,
        'v-btn--router': this.to,
        'v-btn--small': this.small,
        'v-btn--top': this.top,
        ...this.themeClasses
      }

      return (!this.outline && !this.flat)
        ? this.addBackgroundColorClassChecks(classes)
        : this.addTextColorClassChecks(classes)
    }
  },

  methods: {
    // Prevent focus to match md spec
    click (e: MouseEvent): void {
      !this.fab &&
      e.detail &&
      this.$el.blur()

      this.$emit('click', e)
    },
    genContent (): VNode {
      return this.$createElement(
        'div',
        { 'class': 'v-btn__content' },
        [this.$slots.default]
      )
    },
    genLoader (): VNode {
      const children: VNodeChildren = []

      if (!this.$slots.loader) {
        children.push(this.$createElement(VProgressCircular as ComponentOptions<Vue>, {
          props: {
            indeterminate: true,
            size: 23,
            width: 2
          }
        }))
      } else {
        children.push(this.$slots.loader)
      }

      return this.$createElement('span', { 'class': 'v-btn__loading' }, children)
    }
  },

  mounted () {
    if (this.buttonGroup) {
      this.buttonGroup.register(this)
    }
  },

  beforeDestroy () {
    if (this.buttonGroup) {
      this.buttonGroup.unregister(this)
    }
  },

  render (h): VNode {
    const { tag, data } = this.generateRouteLink()
    const children = [this.genContent()]

    tag === 'button' && (data.attrs!.type = this.type)
    this.loading && children.push(this.genLoader())

    data.attrs!.value = ['string', 'number'].includes(typeof this.value)
      ? this.value
      : JSON.stringify(this.value)

    return h(tag, data, children)
  }
})

/* eslint-disable-next-line no-redeclare */
export type VBtn = InstanceType<typeof VBtn> & {
  $el: HTMLButtonElement | HTMLAnchorElement
}
export default VBtn


.DS_Store
node_modules/
.vscode/
npm-debug.log
.idea/
coverage/
Playground.vue
report.html
.vs/
dev/*.js
dev/*css
!dev/index.js
!dev/router.js
!dev/prepare-commit-message.js
!dev/lint-commit-message.js
!dev/warn-npm-install.js
/es5
/es5-temp
/dist
/release
.env
yarn-error.log


import VRadioGroup from './VRadioGroup'
import VRadio from './VRadio'

export { VRadioGroup, VRadio }

/* istanbul ignore next */
VRadioGroup.install = function install (Vue) {
  Vue.component(VRadioGroup.name, VRadioGroup)
  Vue.component(VRadio.name, VRadio)
}

export default VRadioGroup


keep keep keep keep replace keep keep keep keep

@import '../bootstrap'
@import '../theme'

/** Theme */
selection-control($material)
  &.v-input--is-disabled
    .v-icon
      // needed for helper override
      color: $material.buttons.disabled !important

v-selection-control-rtl()
  .v-input--selection-controls__input
    margin-right: 0
    margin-left: 8px

theme(selection-control, "v-input--selection-controls")
rtl(v-selection-control-rtl, "v-input--selection-controls")

.v-input--selection-controls
  &__input
    color: inherit
    display: inline-flex
    flex: 0 1 auto
    height: 24px
    position: relative
    margin-right: 8px
    margin-top: 4px
    transition: color .2s cubic-bezier(.25,.8,.25,1), transform .3s cubic-bezier(.25,.8,.25,1)
    width: 24px

    input
      position: absolute
      opacity: 0

  &__ripple
    cursor: pointer
    height: 48px
    position: absolute
    transition: inherit
    width: 48px
    left: -12px
    top: calc(50% - 24px)

    &:before
      border-radius: 50%
      bottom: 0
      content: ''
      position: absolute
      opacity: .2
      left: 0
      right: 0
      top: 0
      transform-origin: center center
      transform: scale(0.2)
      transition: inherit

  .v-label
    top: 2px
    user-select: none

  &.v-input--is-focused,
  .v-radio--is-focused
    .v-input--selection-controls__ripple:before
      background: currentColor
      transform: scale(.8)


import '../../stylus/components/_time-picker-clock.styl'

// Mixins
import Colorable from '../../mixins/colorable'
import Themeable from '../../mixins/themeable'

export default {
  name: 'v-time-picker-clock',

  mixins: [
    Colorable,
    Themeable
  ],

  data () {
    return {
      defaultColor: 'accent',
      inputValue: this.value,
      isDragging: false
    }
  },

  props: {
    allowedValues: Function,
    double: Boolean,
    format: {
      type: Function,
      default: val => val
    },
    max: {
      type: Number,
      required: true
    },
    min: {
      type: Number,
      required: true
    },
    scrollable: Boolean,
    rotate: {
      type: Number,
      default: 0
    },
    size: {
      type: [Number, String],
      default: 270
    },
    step: {
      type: Number,
      default: 1
    },
    value: Number
  },

  computed: {
    count () {
      return this.max - this.min + 1
    },
    innerRadius () {
      return this.radius - Math.max(this.radius * 0.4, 48)
    },
    outerRadius () {
      return this.radius - 4
    },
    roundCount () {
      return this.double ? (this.count / 2) : this.count
    },
    degreesPerUnit () {
      return 360 / this.roundCount
    },
    degrees () {
      return this.degreesPerUnit * Math.PI / 180
    },
    radius () {
      return this.size / 2
    },
    displayedValue () {
      return this.value == null ? this.min : this.value
    }
  },

  watch: {
    value (value) {
      this.inputValue = value
    }
  },

  methods: {
    wheel (e) {
      e.preventDefault()
      const value = this.displayedValue + Math.sign(e.wheelDelta || 1)
      this.update((value - this.min + this.count) % this.count + this.min)
    },
    handScale (value) {
      return this.double && (value - this.min >= this.roundCount) ? (this.innerRadius / this.radius) : (this.outerRadius / this.radius)
    },
    isAllowed (value) {
      return !this.allowedValues || this.allowedValues(value)
    },
    genValues () {
      const children = []

      for (let value = this.min; value <= this.max; value = value + this.step) {
        const classes = {
          active: value === this.displayedValue,
          disabled: !this.isAllowed(value)
        }

        children.push(this.$createElement('span', {
          'class': this.addBackgroundColorClassChecks(classes, value === this.value ? this.computedColor : null),
          style: this.getTransform(value),
          domProps: { innerHTML: `<span>${this.format(value)}</span>` }
        }))
      }

      return children
    },
    genHand () {
      const scale = `scaleY(${this.handScale(this.displayedValue)})`
      const angle = this.rotate + this.degreesPerUnit * (this.displayedValue - this.min)

      return this.$createElement('div', {
        staticClass: 'v-time-picker-clock__hand',
        'class': this.value == null ? {} : this.addBackgroundColorClassChecks(),
        style: {
          transform: `rotate(${angle}deg) ${scale}`
        }
      })
    },
    getTransform (i) {
      const { x, y } = this.getPosition(i)
      return { transform: `translate(${x}px, ${y}px)` }
    },
    getPosition (value) {
      const radius = (this.radius - 24) * this.handScale(value)
      const rotateRadians = this.rotate * Math.PI / 180
      return {
        x: Math.round(Math.sin((value - this.min) * this.degrees + rotateRadians) * radius),
        y: Math.round(-Math.cos((value - this.min) * this.degrees + rotateRadians) * radius)
      }
    },
    onMouseDown (e) {
      e.preventDefault()

      this.isDragging = true
      this.onDragMove(e)
    },
    onMouseUp () {
      this.isDragging = false
      this.isAllowed(this.inputValue) && this.$emit('change', this.inputValue)
    },
    onDragMove (e) {
      e.preventDefault()
      if (!this.isDragging && e.type !== 'click') return

      const { width, top, left } = this.$refs.clock.getBoundingClientRect()
      const { clientX, clientY } = 'touches' in e ? e.touches[0] : e
      const center = { x: width / 2, y: -width / 2 }
      const coords = { x: clientX - left, y: top - clientY }
      const handAngle = Math.round(this.angle(center, coords) - this.rotate + 360) % 360
      const insideClick = this.double && this.euclidean(center, coords) < (this.outerRadius + this.innerRadius) / 2 - 16
      const value = Math.round(handAngle / this.degreesPerUnit) +
        this.min + (insideClick ? this.roundCount : 0)

      // Necessary to fix edge case when selecting left part of max value
      if (handAngle >= (360 - this.degreesPerUnit / 2)) {
        this.update(insideClick ? this.max : this.min)
      } else {
        this.update(value)
      }
    },
    update (value) {
      if (this.inputValue !== value && this.isAllowed(value)) {
        this.inputValue = value
        this.$emit('input', value)
      }
    },
    euclidean (p0, p1) {
      const dx = p1.x - p0.x
      const dy = p1.y - p0.y

      return Math.sqrt(dx * dx + dy * dy)
    },
    angle (center, p1) {
      const value = 2 * Math.atan2(p1.y - center.y - this.euclidean(center, p1), p1.x - center.x)
      return Math.abs(value * 180 / Math.PI)
    }
  },

  render () {
    const data = {
      staticClass: 'v-time-picker-clock',
      class: {
        'v-time-picker-clock--indeterminate': this.value == null
      },
      on: {
        mousedown: this.onMouseDown,
        mouseup: this.onMouseUp,
        mouseleave: () => (this.isDragging && this.onMouseUp()),
        touchstart: this.onMouseDown,
        touchend: this.onMouseUp,
        mousemove: this.onDragMove,
        touchmove: this.onDragMove
      },
      style: {
        height: `${this.size}px`,
        width: `${this.size}px`
      },
      ref: 'clock'
    }

    this.scrollable && (data.on.wheel = this.wheel)

    return this.$createElement('div', data, [
      this.genHand(),
      this.genValues()
    ])
  }
}