/**
* Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
* http://cssreset.com
*/

html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
button,
audio,
video {
	margin: 0;
	padding: 0;

	vertical-align: baseline;

	border: 0 solid;

	font-size: 100%;

	&::after,
	&::before {
		border: 0 solid;
	}
}

/* HTML5 display-role reset for older browsers */

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
	display: block;
}

ol,
ul {
	list-style: none;
}

blockquote,
q {
	quotes: none;
}

blockquote::before,
blockquote::after,
q::before,
q::after {
	content: '';
	content: none;
}

table {
	border-spacing: 0;

	border-collapse: collapse;
}

a {
	color: var(--rc-color-button-primary);
}


const handleChangeShowUnread = useHandleChange('sidebarShowUnread', !sidebarShowUnread);

keep keep keep keep keep replace keep keep keep

import { RadioButton, OptionTitle } from '@rocket.chat/fuselage';
import { useUserPreference, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useCallback } from 'react';

import { useOmnichannelEnterpriseEnabled } from '../../hooks/omnichannel/useOmnichannelEnterpriseEnabled';
import { OmnichannelSortingDisclaimer } from '../Omnichannel/OmnichannelSortingDisclaimer';
import ListItem from '../Sidebar/ListItem';

function SortModeList(): ReactElement {
	const t = useTranslation();
	const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences');
	const sidebarSortBy = useUserPreference<'activity' | 'alphabetical'>('sidebarSortby', 'activity');
	const isOmnichannelEnabled = useOmnichannelEnterpriseEnabled();

	const useHandleChange = (value: 'alphabetical' | 'activity'): (() => void) =>
		useCallback(() => saveUserPreferences({ data: { sidebarSortby: value } }), [value]);

	const setToAlphabetical = useHandleChange('alphabetical');
	const setToActivity = useHandleChange('activity');

	return (
		<>
			<OptionTitle>{t('Sort_By')}</OptionTitle>
			<ul aria-description='sortByList'>
				<ListItem
					is='label'
					role='listitem'
					icon='clock'
					text={t('Activity')}
					input={<RadioButton pis='x24' onChange={setToActivity} checked={sidebarSortBy === 'activity'} />}
				/>
				<ListItem
					is='label'
					role='listitem'
					icon='sort-az'
					text={t('Name')}
					input={<RadioButton pis='x24' name='sidebarSortby' onChange={setToAlphabetical} checked={sidebarSortBy === 'alphabetical'} />}
				/>
			</ul>
			{isOmnichannelEnabled && <OmnichannelSortingDisclaimer id='sortByList' />}
		</>
	);
}

export default SortModeList;


/packages/* @RocketChat/chat-engine
/packages/core-typings/ @RocketChat/chat-engine
/packages/rest-typings/ @RocketChat/chat-engine
/packages/ui-contexts/ @RocketChat/frontend
/packages/eslint-config/ @RocketChat/chat-engine
/packages/livechat/ @RocketChat/frontend @RocketChat/chat-engine
/.vscode/ @RocketChat/chat-engine
/.github/ @RocketChat/chat-engine
/_templates/ @RocketChat/chat-engine
/apps/meteor/client/ @RocketChat/frontend
/apps/meteor/tests/ @RocketChat/chat-engine
/apps/meteor/app/apps/ @RocketChat/apps
/apps/meteor/app/livechat @RocketChat/omnichannel
/apps/meteor/app/voip @RocketChat/omnichannel
/apps/meteor/app/sms @RocketChat/omnichannel
/apps/meteor/packages/rocketchat-livechat @RocketChat/omnichannel
/apps/meteor/server/services/voip @RocketChat/omnichannel
/apps/meteor/server/services/omnichannel-voip @RocketChat/omnichannel
/apps/meteor/server/features/EmailInbox @RocketChat/omnichannel
/apps/meteor/ee/app/canned-responses @RocketChat/omnichannel
/apps/meteor/ee/app/livechat @RocketChat/omnichannel
/apps/meteor/ee/app/livechat-enterprise @RocketChat/omnichannel
/apps/meteor/ee/client/omnichannel @RocketChat/omnichannel
/apps/meteor/client/components/omnichannel @RocketChat/omnichannel
/apps/meteor/client/components/voip @RocketChat/omnichannel


const { execSync } = require('child_process');

const getMongoVersion = async function({ version, git }) {
	try {
		const workflows = await git.show([`${ version }:.github/workflows/ci.yml`]);

		const mongoMatch = workflows.match(/compatibleMongoVersions\\": \[([^\]]+)\]/);
		if (!mongoMatch) {
			return [];
		}

		return mongoMatch[1].replace(/["'\\ ]/g, '').split(',');
	} catch (e) {
		console.error(e);
	}
	return [];
};

const getNodeNpmVersions = async function({ version, git, request }) {
	try {
		const meteorRelease = await git.show([`${ version }:apps/meteor/.meteor/release`]);
		if (!/^METEOR@(\d+\.){1,2}\d/.test(meteorRelease)) {
			return {};
		}

		const meteorVersion = meteorRelease.replace(/\n|\s/g, '');

		const requestResult = await request(`https://raw.githubusercontent.com/meteor/meteor/release/${ meteorVersion }/scripts/build-dev-bundle-common.sh`);

		return {
			node_version: requestResult.match(/NODE_VERSION=((?:\d+\.){2}\d+)/m)[1],
			npm_version: requestResult.match(/NPM_VERSION=((?:\d+\.){2}\d+)/m)[1],
		};
	} catch (e) {
		console.error(e);
	}

	return {};
};

const getAppsEngineVersion = async function({ version, git }) {
	try {
		const result = execSync('yarn why @rocket.chat/apps-engine --json');

		const resultString = result.toString();

		const match = resultString.match(/"@rocket.chat\/meteor@workspace:apps\/meteor".*"@rocket\.chat\/apps\-engine@npm:([^"]+)"/);
		if (match) {
			return match[1];
		}
	} catch (e) {
		console.error(e);
	}

	return undefined;
};

module.exports = async function({ version, git, request }) {
	const mongo_versions = await getMongoVersion({ version, git });
	const apps_engine_version = await getAppsEngineVersion({ version, git });

	const {
		node_version,
		npm_version,
	} = await getNodeNpmVersions({ version, git, request });

	return {
		node_version,
		npm_version,
		apps_engine_version,
		mongo_versions,
	};
};


import { registerModel } from '@rocket.chat/models';

import { db } from '../../../server/database/utils';
import { CannedResponseRaw } from './raw/CannedResponse';

registerModel('ICannedResponseModel', new CannedResponseRaw(db));


keep keep keep keep replace keep keep keep keep


import { Table } from '@rocket.chat/fuselage';
import { useRoute, useTranslation } from '@rocket.chat/ui-contexts';
import React, { memo, useMemo } from 'react';

import RemoveBusinessHourButton from './RemoveBusinessHourButton';

function BusinessHoursRow(props) {
	const { _id, name, timezone, workHours, active, type, reload } = props;

	const t = useTranslation();

	const bhRoute = useRoute('omnichannel-businessHours');

	const handleClick = () => {
		bhRoute.push({
			context: 'edit',
			type,
			id: _id,
		});
	};

	const handleKeyDown = (e) => {
		if (!['Enter', 'Space'].includes(e.nativeEvent.code)) {
			return;
		}

		handleClick();
	};

	const openDays = useMemo(
		() =>
			workHours.reduce((acc, day) => {
				if (day.open) {
					acc.push(t(day.day));
				}
				return acc;
			}, []),
		[t, workHours],
	);

	const preventClickPropagation = (e) => {
		e.stopPropagation();
	};

	return (
		<Table.Row key={_id} role='link' action tabIndex={0} onClick={handleClick} onKeyDown={handleKeyDown}>
			<Table.Cell withTruncatedText>{name || t('Default')}</Table.Cell>
			<Table.Cell withTruncatedText>{t(timezone.name)}</Table.Cell>
			<Table.Cell withTruncatedText>{openDays.join(', ')}</Table.Cell>
			<Table.Cell withTruncatedText>{active ? t('Yes') : t('No')}</Table.Cell>
			{name && (
				<Table.Cell withTruncatedText onClick={preventClickPropagation}>
					<RemoveBusinessHourButton _id={_id} reload={reload} type={type} />
				</Table.Cell>
			)}
		</Table.Row>
	);
}

export default memo(BusinessHoursRow);


/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Karl STEIN
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

Package.describe({
	name: 'jalik:ufs',
	version: '1.0.4',
	author: 'karl.stein.pro@gmail.com',
	summary: 'Base package for UploadFS',
	homepage: 'https://github.com/jalik/jalik-ufs',
	git: 'https://github.com/jalik/jalik-ufs.git',
	documentation: 'README.md',
});

Package.onUse(function (api) {
	api.versionsFrom('2.6.1');
	api.use('check@1.0.7');
	api.use('ecmascript@0.14.0');
	api.use('underscore@1.0.10');
	api.use('matb33:collection-hooks@1.0.1');
	api.use('mongo@1.8.0');
	api.use('webapp@1.8.0', 'server');
	api.mainModule('ufs.js');
});

// eslint-disable-next-line no-undef
Npm.depends({
	'mkdirp': '0.3.5',
	'spark-md5': '3.0.0',
});


keep keep keep replace keep keep keep keep


import { capitalize } from '@rocket.chat/string-helpers';
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Facebook } from 'meteor/facebook-oauth';
import { Github } from 'meteor/github-oauth';
import { Twitter } from 'meteor/twitter-oauth';
import { MeteorDeveloperAccounts } from 'meteor/meteor-developer-oauth';
import { Linkedin } from 'meteor/pauli:linkedin-oauth';
import { OAuth } from 'meteor/oauth';

import { process2faReturn } from '../../../client/lib/2fa/process2faReturn';
import { CustomOAuth } from '../../custom-oauth/client/custom_oauth_client';
import { convertError } from '../../../client/lib/2fa/utils';
import { overrideLoginMethod } from '../../../client/lib/2fa/overrideLoginMethod';

let lastCredentialToken = null;
let lastCredentialSecret = null;

Accounts.oauth.tryLoginAfterPopupClosed = function (credentialToken, callback, totpCode, credentialSecret = null) {
	credentialSecret = credentialSecret || OAuth._retrieveCredentialSecret(credentialToken) || null;
	const methodArgument = {
		oauth: {
			credentialToken,
			credentialSecret,
		},
	};

	lastCredentialToken = credentialToken;
	lastCredentialSecret = credentialSecret;

	if (totpCode && typeof totpCode === 'string') {
		methodArgument.totp = {
			code: totpCode,
		};
	}

	Accounts.callLoginMethod({
		methodArguments: [methodArgument],
		userCallback:
			callback &&
			function (err) {
				callback(convertError(err));
			},
	});
};

Accounts.oauth.credentialRequestCompleteHandler = function (callback, totpCode) {
	return function (credentialTokenOrError) {
		if (credentialTokenOrError && credentialTokenOrError instanceof Error) {
			callback && callback(credentialTokenOrError);
		} else {
			Accounts.oauth.tryLoginAfterPopupClosed(credentialTokenOrError, callback, totpCode);
		}
	};
};

const createOAuthTotpLoginMethod = (credentialProvider) => (options, code, callback) => {
	// support a callback without options
	if (!callback && typeof options === 'function') {
		callback = options;
		options = null;
	}

	if (lastCredentialToken && lastCredentialSecret) {
		Accounts.oauth.tryLoginAfterPopupClosed(lastCredentialToken, callback, code, lastCredentialSecret);
	} else {
		const provider = (credentialProvider && credentialProvider()) || this;
		const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback, code);
		provider.requestCredential(options, credentialRequestCompleteCallback);
	}

	lastCredentialToken = null;
	lastCredentialSecret = null;
};

const loginWithOAuthTokenAndTOTP = createOAuthTotpLoginMethod();

const loginWithFacebookAndTOTP = createOAuthTotpLoginMethod(() => Facebook);
const { loginWithFacebook } = Meteor;
Meteor.loginWithFacebook = function (options, cb) {
	overrideLoginMethod(loginWithFacebook, [options], cb, loginWithFacebookAndTOTP);
};

const loginWithGithubAndTOTP = createOAuthTotpLoginMethod(() => Github);
const { loginWithGithub } = Meteor;
Meteor.loginWithGithub = function (options, cb) {
	overrideLoginMethod(loginWithGithub, [options], cb, loginWithGithubAndTOTP);
};

const loginWithMeteorDeveloperAccountAndTOTP = createOAuthTotpLoginMethod(() => MeteorDeveloperAccounts);
const { loginWithMeteorDeveloperAccount } = Meteor;
Meteor.loginWithMeteorDeveloperAccount = function (options, cb) {
	overrideLoginMethod(loginWithMeteorDeveloperAccount, [options], cb, loginWithMeteorDeveloperAccountAndTOTP);
};

const loginWithTwitterAndTOTP = createOAuthTotpLoginMethod(() => Twitter);
const { loginWithTwitter } = Meteor;
Meteor.loginWithTwitter = function (options, cb) {
	overrideLoginMethod(loginWithTwitter, [options], cb, loginWithTwitterAndTOTP);
};

const loginWithLinkedinAndTOTP = createOAuthTotpLoginMethod(() => Linkedin);
const { loginWithLinkedin } = Meteor;
Meteor.loginWithLinkedin = function (options, cb) {
	overrideLoginMethod(loginWithLinkedin, [options], cb, loginWithLinkedinAndTOTP);
};

Accounts.onPageLoadLogin((loginAttempt) => {
	if (loginAttempt?.error?.error !== 'totp-required') {
		return;
	}

	const { methodArguments } = loginAttempt;
	if (!methodArguments?.length) {
		return;
	}

	const oAuthArgs = methodArguments.find((arg) => arg.oauth);
	const { credentialToken, credentialSecret } = oAuthArgs.oauth;
	const cb = loginAttempt.userCallback;

	process2faReturn({
		error: loginAttempt.error,
		originalCallback: cb,
		onCode: (code) => {
			Accounts.oauth.tryLoginAfterPopupClosed(credentialToken, cb, code, credentialSecret);
		},
	});
});

const oldConfigureLogin = CustomOAuth.prototype.configureLogin;
CustomOAuth.prototype.configureLogin = function (...args) {
	const loginWithService = `loginWith${capitalize(String(this.name || ''))}`;

	oldConfigureLogin.apply(this, args);

	const oldMethod = Meteor[loginWithService];

	Meteor[loginWithService] = function (options, cb) {
		overrideLoginMethod(oldMethod, [options], cb, loginWithOAuthTokenAndTOTP);
	};
};


import moment from 'moment';
import { useCallback } from 'react';

export const useTimeFromNow = (withSuffix: boolean): ((date: Date) => string) =>
	useCallback((date) => moment(date).fromNow(!withSuffix), [withSuffix]);


import { useEffect, useRef } from 'react';

export const useComponentDidUpdate = (effect: () => void, dependencies: unknown[] = []): void => {
	const hasMounted = useRef(false);
	useEffect(() => {
		if (!hasMounted.current) {
			hasMounted.current = true;
			return;
		}
		effect();
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, dependencies);
};


keep keep keep keep replace keep keep keep keep keep

import { OptionTitle } from '@rocket.chat/fuselage';
import { useSetting, useAtLeastOnePermission, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement, MouseEvent } from 'react';
import React from 'react';

import CreateDiscussion from '../../../components/CreateDiscussion';
import ListItem from '../../../components/Sidebar/ListItem';
import CreateChannelWithData from '../CreateChannel';
import CreateDirectMessage from '../CreateDirectMessage';
import CreateTeam from '../CreateTeam';
import { useCreateRoomModal } from '../hooks/useCreateRoomModal';

const CREATE_CHANNEL_PERMISSIONS = ['create-c', 'create-p'];
const CREATE_TEAM_PERMISSIONS = ['create-team'];
const CREATE_DIRECT_PERMISSIONS = ['create-d'];
const CREATE_DISCUSSION_PERMISSIONS = ['start-discussion', 'start-discussion-other-user'];

type CreateRoomListProps = {
	closeList: () => void;
};

const CreateRoomList = ({ closeList }: CreateRoomListProps): ReactElement => {
	const t = useTranslation();
	const discussionEnabled = useSetting('Discussion_enabled');

	const canCreateChannel = useAtLeastOnePermission(CREATE_CHANNEL_PERMISSIONS);
	const canCreateTeam = useAtLeastOnePermission(CREATE_TEAM_PERMISSIONS);
	const canCreateDirectMessages = useAtLeastOnePermission(CREATE_DIRECT_PERMISSIONS);
	const canCreateDiscussion = useAtLeastOnePermission(CREATE_DISCUSSION_PERMISSIONS);

	const createChannel = useCreateRoomModal(CreateChannelWithData);
	const createTeam = useCreateRoomModal(CreateTeam);
	const createDiscussion = useCreateRoomModal(CreateDiscussion);
	const createDirectMessage = useCreateRoomModal(CreateDirectMessage);

	return (
		<>
			<OptionTitle>{t('Create_new')}</OptionTitle>
			<ul aria-description='createRoomList'>
				{canCreateChannel && (
					<ListItem
						role='listitem'
						icon='hashtag'
						text={t('Channel')}
						onClick={(e: MouseEvent<HTMLElement>): void => {
							createChannel(e);
							closeList();
						}}
					/>
				)}
				{canCreateTeam && (
					<ListItem
						role='listitem'
						icon='team'
						text={t('Team')}
						onClick={(e: MouseEvent<HTMLElement>): void => {
							createTeam(e);
							closeList();
						}}
					/>
				)}
				{canCreateDirectMessages && (
					<ListItem
						role='listitem'
						icon='balloon'
						text={t('Direct_Messages')}
						onClick={(e: MouseEvent<HTMLElement>): void => {
							createDirectMessage(e);
							closeList();
						}}
					/>
				)}
				{discussionEnabled && canCreateDiscussion && (
					<ListItem
						role='listitem'
						icon='discussion'
						text={t('Discussion')}
						onClick={(e: MouseEvent<HTMLElement>): void => {
							createDiscussion(e);
							closeList();
						}}
					/>
				)}
			</ul>
		</>
	);
};

export default CreateRoomList;


import { Meteor } from 'meteor/meteor';
import { Match } from 'meteor/check';
import type { UpdateResult } from 'mongodb';
import type { IUser } from '@rocket.chat/core-typings';

import { Rooms, Messages } from '../../../models/server';

export const saveRoomEncrypted = function (rid: string, encrypted: boolean, user: IUser, sendMessage = true): Promise<UpdateResult> {
	if (!Match.test(rid, String)) {
		throw new Meteor.Error('invalid-room', 'Invalid room', {
			function: 'RocketChat.saveRoomEncrypted',
		});
	}

	const update = Rooms.saveEncryptedById(rid, encrypted);
	if (update && sendMessage) {
		Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser(
			`room_e2e_${encrypted ? 'enabled' : 'disabled'}`,
			rid,
			user.username,
			user,
			{},
		);
	}
	return update;
};


import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';

import { API } from '../api';
import { resolveSRV, resolveTXT } from '../../../federation/server/functions/resolveDNS';

/**
 * @openapi
 *  /api/v1/dns.resolve.srv:
 * 	  get:
 *      description: Resolves DNS service records (SRV records) for a hostname
 *      security:
 *        $ref: '#/security/authenticated'
 *      parameters:
 *        - name: url
 *          in: query
 *          description: The hostname
 *          required: true
 *          schema:
 *            type: string
 *          example: open.rocket.chat
 *      responses:
 *        200:
 *          description: The resolved records
 *          content:
 *            application/json:
 *              schema:
 *                allOf:
 *                  - $ref: '#/components/schemas/ApiSuccessV1'
 *                  - type: object
 *                    properties:
 *                      resolved:
 *                        type: object
 *                        properties:
 *                          target:
 *                            type: string
 *                          priority:
 *                            type: number
 *                          weight:
 *                            type: number
 *                          port:
 *                            type: number
 *        default:
 *          description: Unexpected error
 *          content:
 *            application/json:
 *              schema:
 *                $ref: '#/components/schemas/ApiFailureV1'
 */
API.v1.addRoute(
	'dns.resolve.srv',
	{ authRequired: true },
	{
		async get() {
			check(
				this.queryParams,
				Match.ObjectIncluding({
					url: String,
				}),
			);

			const { url } = this.queryParams;
			if (!url) {
				throw new Meteor.Error('error-missing-param', 'The required "url" param is missing.');
			}

			const resolved = await resolveSRV(url);

			return API.v1.success({ resolved });
		},
	},
);

/**
 * @openapi
 *  /api/v1/dns.resolve.txt:
 * 	  get:
 *      description: Resolves DNS text records (TXT records) for a hostname
 *      security:
 *        $ref: '#/security/authenticated'
 *      parameters:
 *        - name: url
 *          in: query
 *          description: The hostname
 *          required: true
 *          schema:
 *            type: string
 *          example: open.rocket.chat
 *      responses:
 *        200:
 *          description: The resolved records
 *          content:
 *            application/json:
 *              schema:
 *                allOf:
 *                  - $ref: '#/components/schemas/ApiSuccessV1'
 *                  - type: object
 *                    properties:
 *                      resolved:
 *                        type: string
 *        default:
 *          description: Unexpected error
 *          content:
 *            application/json:
 *              schema:
 *                $ref: '#/components/schemas/ApiFailureV1'
 */
API.v1.addRoute(
	'dns.resolve.txt',
	{ authRequired: true },
	{
		async post() {
			check(
				this.queryParams,
				Match.ObjectIncluding({
					url: String,
				}),
			);

			const { url } = this.queryParams;
			if (!url) {
				throw new Meteor.Error('error-missing-param', 'The required "url" param is missing.');
			}

			const resolved = await resolveTXT(url);

			return API.v1.success({ resolved });
		},
	},
);


import moment from 'moment';
import type { ILivechatBusinessHour } from '@rocket.chat/core-typings';
import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings';
import { LivechatBusinessHours, Users } from '@rocket.chat/models';

import { createDefaultBusinessHourRow } from '../../../models/server/models/LivechatBusinessHours';

export const filterBusinessHoursThatMustBeOpened = async (
	businessHours: ILivechatBusinessHour[],
): Promise<Pick<ILivechatBusinessHour, '_id' | 'type'>[]> => {
	const currentTime = moment(moment().format('dddd:HH:mm'), 'dddd:HH:mm');

	return businessHours
		.filter(
			(businessHour) =>
				businessHour.active &&
				businessHour.workHours
					.filter((hour) => hour.open)
					.some((hour) => {
						const localTimeStart = moment(`${hour.start.cron.dayOfWeek}:${hour.start.cron.time}`, 'dddd:HH:mm');
						const localTimeFinish = moment(`${hour.finish.cron.dayOfWeek}:${hour.finish.cron.time}`, 'dddd:HH:mm');
						return currentTime.isSameOrAfter(localTimeStart) && currentTime.isSameOrBefore(localTimeFinish);
					}),
		)
		.map((businessHour) => ({
			_id: businessHour._id,
			type: businessHour.type,
		}));
};

export const filterBusinessHoursThatMustBeOpenedByDay = async (
	businessHours: ILivechatBusinessHour[],
	day: string, // Format: moment.format('dddd')
): Promise<Pick<ILivechatBusinessHour, '_id' | 'type'>[]> => {
	return filterBusinessHoursThatMustBeOpened(
		businessHours.filter((businessHour) =>
			businessHour.workHours.some((workHour) => workHour.start.utc.dayOfWeek === day || workHour.finish.utc.dayOfWeek === day),
		),
	);
};

export const openBusinessHourDefault = async (): Promise<void> => {
	await Users.removeBusinessHoursFromAllUsers();
	const currentTime = moment(moment().format('dddd:HH:mm'), 'dddd:HH:mm');
	const day = currentTime.format('dddd');
	const activeBusinessHours = await LivechatBusinessHours.findDefaultActiveAndOpenBusinessHoursByDay(day, {
		fields: {
			workHours: 1,
			timezone: 1,
			type: 1,
			active: 1,
		},
	});
	const businessHoursToOpenIds = (await filterBusinessHoursThatMustBeOpened(activeBusinessHours)).map((businessHour) => businessHour._id);
	await Users.openAgentsBusinessHoursByBusinessHourId(businessHoursToOpenIds);
	await Users.updateLivechatStatusBasedOnBusinessHours();
};

export const createDefaultBusinessHourIfNotExists = async (): Promise<void> => {
	if ((await LivechatBusinessHours.col.countDocuments({ type: LivechatBusinessHourTypes.DEFAULT })) === 0) {
		await LivechatBusinessHours.insertOne(createDefaultBusinessHourRow());
	}
};


keep keep keep replace keep keep keep keep keep

import { computed, reactive, toRef } from 'vue'

// Styles
import './VAlert.sass'

// Components
import { VAvatar } from '@/components/VAvatar'
import { VBtn } from '@/components/VBtn'

// Composables
import { makeDensityProps, useDensity } from '@/composables/density'
import { makeElevationProps, useElevation } from '@/composables/elevation'
import { makePositionProps, usePosition } from '@/composables/position'
import { makeRoundedProps, useRounded } from '@/composables/rounded'
import { makeTagProps } from '@/composables/tag'
import { makeThemeProps, useTheme } from '@/composables/theme'
import { makeVariantProps, useVariant } from '@/composables/variant'
import { useBorder } from '@/composables/border'
import { useProxiedModel } from '@/composables/proxiedModel'
import { useTextColor } from '@/composables/color'

// Utilities
import { computed } from 'vue'
import { defineComponent } from '@/util'

// Types
import type { PropType } from 'vue'

export const allowedTypes = ['success', 'info', 'warning', 'error'] as const

export type ContextualType = typeof allowedTypes[number]

export default defineComponent({
  name: 'VAlert',

  props: {
    border: {
      type: [Boolean, String],
      validator: (val: boolean | string) => {
        return typeof val === 'boolean' || [
          'top',
          'end',
          'bottom',
          'start',
        ].includes(val)
      },
    },
    borderColor: String,
    closable: Boolean,
    closeIcon: {
      type: String,
      default: '$close',
    },
    closeLabel: {
      type: String,
      default: '$vuetify.close',
    },
    icon: {
      type: [Boolean, String] as PropType<false | string>,
      default: null,
    },
    modelValue: {
      type: Boolean,
      default: true,
    },
    prominent: Boolean,
    sticky: Boolean,
    text: String,
    tip: Boolean,
    type: {
      type: String as PropType<ContextualType>,
      validator: (val: ContextualType) => allowedTypes.includes(val),
    },

    ...makeDensityProps(),
    ...makeElevationProps(),
    ...makePositionProps(),
    ...makeRoundedProps(),
    ...makeTagProps(),
    ...makeThemeProps(),
    ...makeVariantProps(),
  },

  emits: {
    'update:modelValue': (value: boolean) => true,
  },

  setup (props, { slots }) {
    const borderProps = computed(() => ({
      border: props.border === true || props.tip ? 'start' : props.border,
    }))
    const isActive = useProxiedModel(props, 'modelValue')
    const icon = computed(() => {
      if (props.icon === false) return undefined
      if (!props.type) return props.icon

      return props.icon ?? `$${props.type}`
    })
    const variantProps = computed(() => ({
      color: props.color ?? props.type,
      variant: props.variant,
    }))

    const { themeClasses } = useTheme(props)
    const { borderClasses } = useBorder(borderProps.value, 'v-alert')
    const { colorClasses, colorStyles, variantClasses } = useVariant(variantProps.value, 'v-alert')
    const { densityClasses } = useDensity(props, 'v-alert')
    const { elevationClasses } = useElevation(props)
    const { positionClasses, positionStyles } = usePosition(props, 'v-alert')
    const { roundedClasses } = useRounded(props, 'v-alert')
    const { textColorClasses, textColorStyles } = useTextColor(computed(() => {
      return props.borderColor ?? (props.tip ? variantProps.value.color : undefined)
    }))

    function onCloseClick (e: MouseEvent) {
      isActive.value = false
    }

    return () => {
      const hasBorder = !!borderProps.value.border
      const hasClose = !!(slots.close || props.closable)
      const hasPrepend = !!(slots.prepend || props.icon || props.type)
      const hasText = !!(slots.default || props.text || hasClose)

      return isActive.value && (
        <props.tag
          class={[
            'v-alert',
            {
              [`v-alert--border-${borderProps.value.border}`]: hasBorder,
              'v-alert--prominent': props.prominent,
              'v-alert--tip': props.tip,
            },
            themeClasses.value,
            borderClasses.value,
            !props.tip && colorClasses.value,
            densityClasses.value,
            elevationClasses.value,
            positionClasses.value,
            roundedClasses.value,
            variantClasses.value,
          ]}
          style={[
            !props.tip && colorStyles.value,
            positionStyles.value,
          ]}
          role="alert"
        >
          { hasBorder && (

          ) }

            { hasPrepend && (

                { slots.prepend
                  ? slots.prepend()
                  : (
                    <VAvatar
                      class={ props.tip && textColorClasses.value }
                      style={ props.tip && textColorStyles.value }
                      density={ props.density }
                      icon={ icon.value }
                    />
                  )
                }

            ) }

            { hasText && (

                { slots.default
                  ? slots.default()
                  : props.text
                }

                { hasClose && (

                    { slots.close
                      ? slots.close({ props: { onClick: onCloseClick } })
                      : (
                        <VBtn
                          density={ props.density }
                          icon={ props.closeIcon }
                          variant="text"
                          onClick={ onCloseClick }
                        />
                      )
                    }

                ) }

            ) }

        </props.tag>
      )
    }
  },
})


The value used when the component is selected in a group. If not provided, a unique ID will be used.

Configure the active CSS class applied when the item is selected.

Removes the ability to click or target the component.


// @ts-nocheck
/* eslint-disable */

// import Header from '../header'
import {
  mount,
  MountOptions,
  Wrapper,
} from '@vue/test-utils'
// import { wrapInArray } from '../../../../util/helpers'

describe.skip('VDataTable/header.ts', () => {
  type Instance = InstanceType<typeof Header>
  let mountFunction: (options?: MountOptions<Instance>) => Wrapper<Instance>
  beforeEach(() => {
    mountFunction = (options?: MountOptions<Instance>) => {
      return mount(Header, {
        props: {
          headers: [],
          ...options.props,
        },
        render (h) {
          return h('div')
        },
        ...options,
      })
    }
  })

  it('should generate sort icon', async () => {
    const wrapper = mountFunction({
      propsData: {
        sortIcon: 'mdi-sort',
      },
      render () {
        return this.genSortIcon()
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should generate select', async () => {
    const wrapper = mountFunction({
      render (h) {
        return h('div', wrapInArray(this.genSelectAll()))
      },
    })

    wrapper.setProps({
      everyItem: false,
      someItems: false,
    })
    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({
      everyItem: true,
      someItems: false,
    })
    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({
      everyItem: false,
      someItems: true,
    })
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should generate select scoped slot', async () => {
    const sel = jest.fn()
    const wrapper = mountFunction({
      render (h) {
        return h('div', wrapInArray(this.genSelectAll()))
      },
      scopedSlots: {
        'data-table-select' (props) {
          return this.$createElement('div', {
            on: props.on,
            staticClass: 'test',
          }, [JSON.stringify(props)])
        },
      },
      listeners: {
        'toggle-select-all': sel,
      },
    })

    wrapper.setProps({
      everyItem: false,
      someItems: false,
    })
    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({
      everyItem: true,
      someItems: false,
    })
    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({
      everyItem: false,
      someItems: true,
    })
    expect(wrapper.html()).toMatchSnapshot()

    const select = wrapper.find('.test')
    select.trigger('input', { value: true })
    expect(sel.mock.calls[0][0].value).toBeTruthy()
    select.trigger('input', { value: false })
    expect(sel.mock.calls[1][0].value).toBeFalsy()
  })
})


module.exports = {
  'v-toolbar-items': {
    slots: [
      {
        name: 'default',
        props: undefined,
      },
    ],
  },
}


if (props.icon === false) return undefined

const { borderClasses } = useBorder(borderProps.value, 'v-alert')

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

color: computed(() => (props.color ?? props.type)),

// Styles
import './VListItem.sass'

// Components
import VListItemAvatar from './VListItemAvatar'
import VListItemHeader from './VListItemHeader'
import VListItemSubtitle from './VListItemSubtitle'
import VListItemTitle from './VListItemTitle'
import { VAvatar } from '@/components/VAvatar'

// Composables
import { makeBorderProps, useBorder } from '@/composables/border'
import { makeDensityProps, useDensity } from '@/composables/density'
import { makeDimensionProps, useDimension } from '@/composables/dimensions'
import { makeElevationProps, useElevation } from '@/composables/elevation'
import { makeRoundedProps, useRounded } from '@/composables/rounded'
import { makeRouterProps, useLink } from '@/composables/router'
import { makeTagProps } from '@/composables/tag'
import { makeThemeProps, useTheme } from '@/composables/theme'
import { genOverlays, makeVariantProps, useVariant } from '@/composables/variant'

// Directives
import { Ripple } from '@/directives/ripple'

// Utilities
import { computed } from 'vue'
import { defineComponent } from '@/util'

export default defineComponent({
  name: 'VListItem',

  directives: { Ripple },

  props: {
    active: Boolean,
    activeColor: String,
    activeClass: String,
    appendAvatar: String,
    appendIcon: String,
    disabled: Boolean,
    link: Boolean,
    prependAvatar: String,
    prependIcon: String,
    subtitle: String,
    title: String,

    ...makeBorderProps(),
    ...makeDensityProps(),
    ...makeDimensionProps(),
    ...makeElevationProps(),
    ...makeRoundedProps(),
    ...makeRouterProps(),
    ...makeTagProps(),
    ...makeThemeProps(),
    ...makeVariantProps({ variant: 'text' } as const),
  },

  setup (props, { attrs, slots }) {
    const link = useLink(props, attrs)
    const isActive = computed(() => {
      return props.active || link.isExactActive?.value
    })
    const { themeClasses } = useTheme(props)
    const { borderClasses } = useBorder(props, 'v-list-item')
    const { colorClasses, colorStyles, variantClasses } = useVariant(props, 'v-list-item')
    const { densityClasses } = useDensity(props, 'v-list-item')
    const { dimensionStyles } = useDimension(props)
    const { elevationClasses } = useElevation(props)
    const { roundedClasses } = useRounded(props, 'v-list-item')

    return () => {
      const Tag = (link.isLink.value) ? 'a' : props.tag
      const hasTitle = (slots.title || props.title)
      const hasSubtitle = (slots.subtitle || props.subtitle)
      const hasHeader = !!(hasTitle || hasSubtitle)
      const hasAppend = (slots.append || props.appendAvatar || props.appendIcon)
      const hasPrepend = (slots.prepend || props.prependAvatar || props.prependIcon)
      const isClickable = !props.disabled && (link.isClickable.value || props.link)

      return (
        <Tag
          class={[
            'v-list-item',
            {
              'v-list-item--active': isActive.value,
              'v-list-item--disabled': props.disabled,
              'v-list-item--link': isClickable,
              [`${props.activeClass}`]: isActive.value && props.activeClass,
            },
            themeClasses.value,
            borderClasses.value,
            colorClasses.value,
            densityClasses.value,
            elevationClasses.value,
            roundedClasses.value,
            variantClasses.value,
          ]}
          style={[
            colorStyles.value,
            dimensionStyles.value,
          ]}
          href={ link.href.value }
          tabindex={ isClickable ? 0 : undefined }
          onClick={ isClickable && link.navigate }
          v-ripple={ isClickable }
        >
          { genOverlays(!!(isClickable || isActive.value), 'v-list-item') }

          { hasPrepend && (
            slots.prepend
              ? slots.prepend()
              : (
                <VListItemAvatar left>
                  <VAvatar
                    density={ props.density }
                    icon={ props.prependIcon }
                    image={ props.prependAvatar }
                  />
                </VListItemAvatar>
              )
          ) }

          { hasHeader && (
            <VListItemHeader>
              { hasTitle && (
                <VListItemTitle>
                  { slots.title
                    ? slots.title()
                    : props.title
                  }
                </VListItemTitle>
              ) }

              { hasSubtitle && (
                <VListItemSubtitle>
                  { slots.subtitle
                    ? slots.subtitle()
                    : props.subtitle
                  }
                </VListItemSubtitle>
              ) }
            </VListItemHeader>
          ) }

          { slots.default?.() }

          { hasAppend && (
            slots.append
              ? slots.append()
              : (
                <VListItemAvatar right>
                  <VAvatar
                    density={ props.density }
                    icon={ props.appendIcon }
                    image={ props.appendAvatar }
                  />
                </VListItemAvatar>
              )
          ) }
        </Tag>
      )
    }
  },
})


<template>
  <v-container fluid>
    <v-textarea
      counter
      label="Text"
      :rules="rules"
      :value="value"
    ></v-textarea>
  </v-container>
</template>

<script>
  export default {
    data: () => ({
      rules: [v => v.length <= 25 || 'Max 25 characters'],
      value: 'Hello!',
    }),
  }
</script>


<template>
  <v-container class="grey lighten-5">
    <v-row
      class="mb-6"
      no-gutters
    >
      <v-col
        v-for="n in 3"
        :key="n"
        :cols="n === 2 ? 6 : undefined"
      >
        <v-card
          class="pa-2"
          outlined
          tile
        >
          {{ n }} of 3 {{ n === 2 ? '(wider)' : '' }}
        </v-card>
      </v-col>
    </v-row>
    <v-row no-gutters>
      <v-col
        v-for="n in 3"
        :key="n"
        :cols="n === 2 ? 5 : undefined"
      >
        <v-card
          class="pa-2"
          outlined
          tile
        >
          {{ n }} of 3 {{ n === 2 ? '(wider)' : '' }}
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>


@import '../../styles/styles.sass';

$textarea-box-enclosed-prefix-margin-top: 24px !default;
$textarea-box-enclosed-single-outlined-label-top: 18px !default;
$textarea-box-enclosed-single-outlined-margin-top: 10px !default;
$textarea-dense-box-enclosed-single-outlined-margin-top: 6px !default;
$textarea-dense-append-prepend-margin-top: 8px !default;
$textarea-enclosed-text-slot-margin: -12px !default;
$textarea-enclosed-text-slot-padding: 12px !default;
$textarea-line-height: 1.75rem !default;
$textarea-min-height: 32px !default;
$textarea-padding: 0 !default;
$textarea-prefix-padding-top: 2px !default;
$textarea-solo-append-padding: 12px !default;
$textarea-solo-append-prepend-margin-top: 12px !default;


const { colorClasses, colorStyles, variantClasses } = useVariant(variantProps.value, 'v-alert')

const { densityClasses } = useDensity(props, 'v-alert')

const { elevationClasses } = useElevation(props)

const { positionClasses, positionStyles } = usePosition(props, 'v-alert')

const { roundedClasses } = useRounded(props, 'v-alert')

const { colorClasses, colorStyles, variantClasses } = useVariant(variantProps, 'v-alert')


---
meta:
  title: Aspect ratios
  description: The responsive component is used as a wrapper component to force custom aspect ratios for its children.
  keywords: ratio, responsive, aspect ratios
related:
  - /components/cards/
  - /components/sheets/
  - /components/images/
---

# Aspect ratios

The `v-responsive` component can be used to fix any section to a specific aspect ratio

<entry-ad />

## Usage

Specify a custom aspect-ratio

<example file="v-responsive/usage" />

## API

- [v-responsive](/api/v-responsive)

<backmatter />


// @ts-nocheck
/* eslint-disable */

// Styles
import './VCheckbox.sass'
import '../../styles/components/_selection-controls.sass'

// Components
import VIcon from '../VIcon'
import VInput from '../VInput'

// Mixins
import Selectable from '../../mixins/selectable'

/* @vue/component */
export default Selectable.extend({
  name: 'v-checkbox',

  props: {
    indeterminate: Boolean,
    indeterminateIcon: {
      type: String,
      default: '$checkboxIndeterminate',
    },
    offIcon: {
      type: String,
      default: '$checkboxOff',
    },
    onIcon: {
      type: String,
      default: '$checkboxOn',
    },
  },

  data () {
    return {
      inputIndeterminate: this.indeterminate,
    }
  },

  computed: {
    classes (): object {
      return {
        ...VInput.options.computed.classes.call(this),
        'v-input--selection-controls': true,
        'v-input--checkbox': true,
        'v-input--indeterminate': this.inputIndeterminate,
      }
    },
    computedIcon (): string {
      if (this.inputIndeterminate) {
        return this.indeterminateIcon
      } else if (this.isActive) {
        return this.onIcon
      } else {
        return this.offIcon
      }
    },
    // Do not return undefined if disabled,
    // according to spec, should still show
    // a color when disabled and active
    validationState (): string | undefined {
      if (this.isDisabled && !this.inputIndeterminate) return undefined
      if (this.hasError && this.shouldValidate) return 'error'
      if (this.hasSuccess) return 'success'
      if (this.hasColor !== null) return this.computedColor
      return undefined
    },
  },

  watch: {
    indeterminate (val) {
      // https://github.com/vuetifyjs/vuetify/issues/8270
      this.$nextTick(() => (this.inputIndeterminate = val))
    },
    inputIndeterminate (val) {
      this.$emit('update:indeterminate', val)
    },
    isActive () {
      if (!this.indeterminate) return
      this.inputIndeterminate = false
    },
  },

  methods: {
    genCheckbox () {
      const { title, ...checkboxAttrs } = this.attrs$
      return this.$createElement('div', {
        staticClass: 'v-input--selection-controls__input',
      }, [
        this.$createElement(VIcon, this.setTextColor(this.validationState, {
          props: {
            dense: this.dense,
            dark: this.dark,
            light: this.light,
          },
        }), this.computedIcon),
        this.genInput('checkbox', {
          ...checkboxAttrs,
          'aria-checked': this.inputIndeterminate
            ? 'mixed'
            : this.isActive.toString(),
        }),
        this.genRipple(this.setTextColor(this.rippleState)),
      ])
    },
    genDefaultSlot () {
      return [
        this.genCheckbox(),
        this.genLabel(),
      ]
    },
  },
})


<template>

    <v-alert
      v-model="alert"
      dismissible
      color="cyan"
      border="left"
      elevation="2"
      colored-border
      icon="mdi-twitter"
    >
      You've got 5 new updates on your timeline!.
    </v-alert>

      <v-btn
        v-if="!alert"
        dark
        @click="alert = true"
      >
        Reset Alert
      </v-btn>

</template>


const { textColorClasses, textColorStyles } = useTextColor(computed(() => {

return props.borderColor ?? (props.tip ? variantProps.value.color : undefined)

return props.borderColor ?? (props.tip ? variantProps.color : undefined)


<template>
  <v-row align="center">
    <v-checkbox
      v-model="landscape"
      label="Landscape"
    ></v-checkbox>
    <v-date-picker
      v-model="picker"
      :landscape="landscape"
    ></v-date-picker>
  </v-row>
</template>

<script>
  export default {
    data () {
      return {
        picker: new Date().toISOString().substr(0, 7),
        landscape: false,
      }
    },
  }
</script>


// @ts-nocheck
/* eslint-disable */

import Vue from 'vue'

export type MouseHandler = (e: MouseEvent | TouchEvent) => any

export type MouseEvents = {
  [event: string]: {
    event: string
    passive?: boolean
    capture?: boolean
    once?: boolean
    stop?: boolean
    prevent?: boolean
    button?: number
    result?: any
  }
}

export type MouseEventsMap = {
  [event: string]: MouseHandler | MouseHandler[]
}

export default Vue.extend({
  name: 'mouse',

  methods: {
    getDefaultMouseEventHandlers (suffix: string, getEvent: MouseHandler): MouseEventsMap {
      return this.getMouseEventHandlers({
        ['click' + suffix]: { event: 'click' },
        ['contextmenu' + suffix]: { event: 'contextmenu', prevent: true, result: false },
        ['mousedown' + suffix]: { event: 'mousedown' },
        ['mousemove' + suffix]: { event: 'mousemove' },
        ['mouseup' + suffix]: { event: 'mouseup' },
        ['mouseenter' + suffix]: { event: 'mouseenter' },
        ['mouseleave' + suffix]: { event: 'mouseleave' },
        ['touchstart' + suffix]: { event: 'touchstart' },
        ['touchmove' + suffix]: { event: 'touchmove' },
        ['touchend' + suffix]: { event: 'touchend' },
      }, getEvent)
    },
    getMouseEventHandlers (events: MouseEvents, getEvent: MouseHandler): MouseEventsMap {
      const on: MouseEventsMap = {}

      for (const event in events) {
        const eventOptions = events[event]

        if (!this.$listeners[event]) continue

        // TODO somehow pull in modifiers

        const prefix = eventOptions.passive ? '&' : ((eventOptions.once ? '~' : '') + (eventOptions.capture ? '!' : ''))
        const key = prefix + eventOptions.event

        const handler: MouseHandler = e => {
          const mouseEvent: MouseEvent = e as MouseEvent
          if (eventOptions.button === undefined || (mouseEvent.buttons > 0 && mouseEvent.button === eventOptions.button)) {
            if (eventOptions.prevent) {
              e.preventDefault()
            }
            if (eventOptions.stop) {
              e.stopPropagation()
            }
            this.$emit(event, getEvent(e), e)
          }

          return eventOptions.result
        }

        if (key in on) {
          /* istanbul ignore next */
          if (Array.isArray(on[key])) {
            (on[key] as MouseHandler[]).push(handler)
          } else {
            on[key] = [on[key], handler] as MouseHandler[]
          }
        } else {
          on[key] = handler
        }
      }

      return on
    },
  },
})


<template>
  <v-sheet
    id="comparison"
    class="mb-12 text-body-2 mx-auto"
    max-width="1024"
    outlined
    rounded
  >
    <v-simple-table>

        {{ $t('comparison.caption', { year: (new Date()).getFullYear() }) }}

            <i18n
              class="text-h6"
              path="features"
              tag="strong"
            />

              <v-img
                v-if="framework.src"
                :src="framework.src"
                class="mr-2"
                contain
                height="24"
                max-width="24"
                width="24"
              />

          <i18n
            :path="`comparison.${key}`"
            class="text--secondary text-left"
            tag="td"
          />

            <template v-if="framework[key]">
              <v-icon
                v-if="typeof framework[key] === 'boolean'"
                color="success"
              >
                $mdiRecord
              </v-icon>

            </template>

            <i18n
              path="comparison.average"
              tag="div"
            />

    </v-simple-table>
  </v-sheet>
</template>


import { Ripple } from '@/directives/ripple'

keep keep keep keep replace keep keep keep


<template>

    <v-progress-linear
      indeterminate
      color="yellow darken-2"
    ></v-progress-linear>

    <v-progress-linear
      indeterminate
      color="green"
    ></v-progress-linear>

    <v-progress-linear
      indeterminate
      color="teal"
    ></v-progress-linear>

    <v-progress-linear
      indeterminate
      color="cyan"
    ></v-progress-linear>

</template>


<template>
  <div>
    <v-card
      class="d-flex flex-wrap-reverse"
      color="grey lighten-2"
      flat
      tile
    >
      <v-card
        class="order-3 pa-2"
        outlined
        tile
      >
        First flex item
      </v-card>
      <v-card
        class="order-2 pa-2"
        outlined
        tile
      >
        Second flex item
      </v-card>
      <v-card
        class="order-1 pa-2"
        outlined
        tile
      >
        Third flex item
      </v-card>
    </v-card>
  </div>
</template>


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

exports[`VSystemBar should match a snapshot 1`] = `
<div class="v-system-bar v-theme--light">
</div>
`;


return props.active || link.isExactActive?.value

const { borderClasses } = useBorder(props, 'v-list-item')

const activeColor = props.activeColor ?? props.color

color: computed(() => (isActive.value ? activeColor : props.color)),


<template>
  <v-autocomplete
    clearable
    multiple
    prepend-inner-icon="$mdiDatabaseSearch"
    return-object
    solo
    v-bind="$attrs"
    v-on="$listeners"
  >
    <template #selection="{ item, selected }">
      <v-chip
        :value="selected"
        class="white--text"
        color="primary"
        label
      >
        <v-icon
          left
          v-text="item.icon"
        />
        <span v-text="item.text" />
      </v-chip>
    </template>

    <template #item="{ attrs, item }">
      <v-list-item-action>
        <v-icon v-text="item.icon" />
      </v-list-item-action>
      <v-list-item-content>
        <v-list-item-title
          :id="attrs['aria-labelledby']"
          v-text="item.text"
        />
        <v-list-item-subtitle v-text="$t(item.type)" />
      </v-list-item-content>
    </template>
  </v-autocomplete>
</template>

<script>
  export default {
    name: 'ApiSearch',
  }
</script>


@use "sass:string"

@import './_display.sass'
@import './_screenreaders.sass'
@import './_text.sass'

// Utilities
@each $breakpoint in map-keys($grid-breakpoints)
  // Generate media query if needed
  +media-breakpoint-up($breakpoint)
    $infix: breakpoint-infix($breakpoint, $grid-breakpoints)

    // Loop over each utility property
    @each $key, $utility in $utilities
      // The utility can be disabled with `false`, thus check if the utility is a map first
      // Only proceed if responsive media queries are enabled or if it's the base media query
      @if string.slice($key, -4) == ':ltr'
        @if type-of($utility) == "map" and (map-get($utility, responsive) or $infix == "")
          +generate-utility($utility, $infix, 'ltr')
      @else if string.slice($key, -4) == ':rtl'
        @if type-of($utility) == "map" and (map-get($utility, responsive) or $infix == "")
          +generate-utility($utility, $infix, 'rtl')
      @else
        @if type-of($utility) == "map" and (map-get($utility, responsive) or $infix == "")
          +generate-utility($utility, $infix, 'bidi')

// Print utilities
@media print
  @each $key, $utility in $utilities
    // The utility can be disabled with `false`, thus check if the utility is a map first
    // Then check if the utility needs print styles
    @if string.slice($key, -4) == ':ltr'
      @if type-of($utility) == "map" and map-get($utility, print) == true
        +generate-utility($utility, "-print", 'ltr')
    @else if string.slice($key, -4) == ':rtl'
      @if type-of($utility) == "map" and map-get($utility, print) == true
        +generate-utility($utility, "-print", 'rtl')
    @else
      @if type-of($utility) == "map" and map-get($utility, print) == true
        +generate-utility($utility, "-print", 'bidi')


@import '../../styles/styles.sass';

$label-font-size: 16px !default;
$label-line-height: 1 !default;
$label-min-height: 8px !default;


const { colorClasses, colorStyles, variantClasses } = useVariant(props, 'v-list-item')

const { densityClasses } = useDensity(props, 'v-list-item')

const { dimensionStyles } = useDimension(props)

const { roundedClasses } = useRounded(props, 'v-list-item')

keep keep replace keep keep keep keep keep

const { colorClasses, colorStyles, variantClasses } = useVariant(variantProps, 'v-list-item')


import {
  colorToInt,
  colorToRGB,
  createRange,
  darken,
  getLuma,
  intToHex,
  lighten,
  mergeDeep,
  propsFactory,
} from '@/util'
// Utilities
import { computed, getCurrentInstance, inject, provide, ref, watch } from 'vue'

// Types
import type { InjectionKey, Ref } from 'vue'
import { consoleError } from '@/util/console'
import { APCAcontrast } from '@/util/color/APCA'

interface BaseColors {
  background: string
  surface: string
  primary: string
  secondary: string
  success: string
  warning: string
  error: string
  info: string
}

interface OnColors {
  'on-background': string
  'on-surface': string
  'on-primary': string
  'on-secondary': string
  'on-success': string
  'on-warning': string
  'on-error': string
  'on-info': string
}

interface Colors extends BaseColors, OnColors {
  [key: string]: string
}

interface InternalThemeDefinition {
  dark: boolean
  colors: Colors
  variables: Record<string, string | number>
}

interface ThemeDefinitionColors extends BaseColors, Partial<OnColors> {
  [key: string]: string | undefined
}

export interface ThemeDefinition {
  dark: boolean
  colors: ThemeDefinitionColors
  variables: Record<string, string | number>
}

interface VariationsOptions {
  colors: string[]
  lighten: number
  darken: number
}

interface InternalThemeOptions {
  isDisabled: boolean
  defaultTheme: string
  variations: VariationsOptions
  themes: Record<string, ThemeDefinition>
}

export type ThemeOptions = false | {
  defaultTheme?: string
  variations?: false | VariationsOptions
  themes?: Record<string, ThemeDefinition>
}

export interface ThemeInstance {
  isDisabled: boolean
  themes: Ref<Record<string, InternalThemeDefinition>>
  current: Ref<string>
  themeClasses: Ref<string | undefined>
  setTheme: (key: string, theme: ThemeDefinition) => void
  getTheme: (key: string) => InternalThemeDefinition
}

export const ThemeSymbol: InjectionKey<ThemeInstance> = Symbol.for('vuetify:theme')

export const makeThemeProps = propsFactory({
  theme: String,
}, 'theme')

const defaultThemeOptions: ThemeOptions = {
  defaultTheme: 'light',
  variations: { colors: [], lighten: 0, darken: 0 },
  themes: {
    light: {
      dark: false,
      colors: {
        background: '#FFFFFF',
        surface: '#FFFFFF',
        primary: '#6200EE',
        'primary-darken-1': '#3700B3',
        secondary: '#03DAC6',
        'secondary-darken-1': '#018786',
        error: '#B00020',
        info: '#2196F3',
        success: '#4CAF50',
        warning: '#FB8C00',
      },
      variables: {
        'border-color': '#000000',
        'border-opacity': 0.12,
        'high-emphasis-opacity': 0.87,
        'medium-emphasis-opacity': 0.60,
        'disabled-opacity': 0.38,
        'activated-opacity': 0.12,
        'hover-opacity': 0.04,
        'focus-opacity': 0.12,
        'selected-opacity': 0.08,
        'dragged-opacity': 0.08,
        'pressed-opacity': 0.16,
        'kbd-background-color': '#212529',
        'kbd-color': '#FFFFFF',
        'code-background-color': '#C2C2C2',
      },
    },
    dark: {
      dark: true,
      colors: {
        background: '#121212',
        surface: '#212121',
        primary: '#BB86FC',
        'primary-darken-1': '#3700B3',
        secondary: '#03DAC5',
        'secondary-darken-1': '#03DAC5',
        error: '#CF6679',
        info: '#2196F3',
        success: '#4CAF50',
        warning: '#FB8C00',
      },
      variables: {
        'border-color': '#FFFFFF',
        'border-opacity': 0.12,
        'high-emphasis-opacity': 0.87,
        'medium-emphasis-opacity': 0.60,
        'disabled-opacity': 0.38,
        'activated-opacity': 0.12,
        'hover-opacity': 0.04,
        'focus-opacity': 0.12,
        'selected-opacity': 0.08,
        'dragged-opacity': 0.08,
        'pressed-opacity': 0.16,
        'kbd-background-color': '#212529',
        'kbd-color': '#FFFFFF',
        'code-background-color': '#B7B7B7',
      },
    },
  },
}

const parseThemeOptions = (options: ThemeOptions = defaultThemeOptions): InternalThemeOptions => {
  if (!options) return { ...defaultThemeOptions, isDisabled: true } as InternalThemeOptions

  return mergeDeep(
    defaultThemeOptions,
    options,
  ) as InternalThemeOptions
}

// Composables
export function createTheme (options?: ThemeOptions): ThemeInstance {
  const parsedOptions = parseThemeOptions(options)
  const styleEl = ref<HTMLStyleElement>()
  const current = ref(parsedOptions.defaultTheme)
  const themes = ref(parsedOptions.themes)
  const variations = ref(parsedOptions.variations)

  const computedThemes = computed(() => {
    return Object.keys(themes.value).reduce((obj, key) => {
      const theme: ThemeDefinition = {
        ...themes.value[key],
        colors: {
          ...themes.value[key].colors,
          ...(parsedOptions.variations.colors ?? []).reduce((obj, color) => {
            return { ...obj, ...genColorVariations(color, themes.value[key].colors[color]!) }
          }, {}),
        },
      }

      for (const color of Object.keys(theme.colors)) {
        if (/on-[a-z]/.test(color) || theme.colors[`on-${color}`]) continue

        const onColor = `on-${color}` as keyof OnColors
        const colorVal = colorToInt(theme.colors[color]!)

        const blackContrast = Math.abs(APCAcontrast(0, colorVal))
        const whiteContrast = Math.abs(APCAcontrast(0xffffff, colorVal))

        // TODO: warn about poor color selections
        // const contrastAsText = Math.abs(APCAcontrast(colorVal, colorToInt(theme.colors.background)))
        // const minContrast = Math.max(blackContrast, whiteContrast)
        // if (minContrast < 60) {
        //   consoleInfo(`${key} theme color ${color} has poor contrast (${minContrast.toFixed()}%)`)
        // } else if (contrastAsText < 60 && !['background', 'surface'].includes(color)) {
        //   consoleInfo(`${key} theme color ${color} has poor contrast as text (${contrastAsText.toFixed()}%)`)
        // }

        // Prefer white text if both have an acceptable contrast ratio
        theme.colors[onColor] = whiteContrast > Math.min(blackContrast, 50) ? '#fff' : '#000'
      }

      obj[key] = theme as InternalThemeDefinition

      return obj
    }, {} as Record<string, InternalThemeDefinition>)
  })

  function genColorVariations (name: string, color: string) {
    const obj: Record<string, string> = {}
    for (const variation of (['lighten', 'darken'] as const)) {
      const fn = variation === 'lighten' ? lighten : darken
      for (const amount of createRange(variations.value[variation], 1)) {
        obj[`${name}-${variation}-${amount}`] = intToHex(fn(colorToInt(color), amount))
      }
    }

    return obj
  }

  function genCssVariables (name: string) {
    const theme = computedThemes.value[name]

    if (!theme) throw new Error(`Could not find theme ${name}`)

    const lightOverlay = theme.dark ? 2 : 1
    const darkOverlay = theme.dark ? 1 : 2

    const variables: string[] = []
    for (const [key, value] of Object.entries(theme.colors)) {
      const rgb = colorToRGB(value!)
      variables.push(`--v-theme-${key}: ${rgb.r},${rgb.g},${rgb.b}`)
      if (!key.startsWith('on-')) {
        variables.push(`--v-theme-${key}-overlay-multiplier: ${getLuma(value) > 0.18 ? lightOverlay : darkOverlay}`)
      }
    }

    return variables
  }

  function genStyleElement () {
    if (typeof document === 'undefined' || styleEl.value) return

    const el = document.createElement('style')
    el.type = 'text/css'
    el.id = 'vuetify-theme-stylesheet'

    styleEl.value = el
    document.head.appendChild(styleEl.value)
  }

  function createCssClass (selector: string, content: string[]) {
    return [
      `${selector} {\n`,
      ...content.map(line => `  ${line};\n`),
      '}\n',
    ]
  }

  function updateStyles () {
    if (parsedOptions.isDisabled) return

    genStyleElement()

    const lines = []

    for (const themeName of Object.keys(computedThemes.value)) {
      const variables = computedThemes.value[themeName].variables

      lines.push(...createCssClass(`.v-theme--${themeName}`, [
        ...genCssVariables(themeName),
        ...Object.keys(variables).map(key => {
          const value = variables[key]
          const color = typeof value === 'string' && value.startsWith('#') ? colorToRGB(value) : undefined
          const rgb = color ? `${color.r}, ${color.g}, ${color.b}` : undefined

          return `--v-${key}: ${rgb ?? value}`
        }),
      ]))
    }

    // Assumption is that all theme objects have the same keys, so it doesn't matter which one
    // we use since the values are all css variables.
    const firstTheme = Object.keys(computedThemes.value)[0]
    for (const key of Object.keys(computedThemes.value[firstTheme].colors)) {
      if (/on-[a-z]/.test(key)) {
        lines.push(...createCssClass(`.${key}`, [`color: rgb(var(--v-theme-${key}))`]))
      } else {
        lines.push(
          ...createCssClass(`.bg-${key}`, [
            `--v-theme-overlay-multiplier: var(--v-theme-${key}-overlay-multiplier)`,
            `background: rgb(var(--v-theme-${key}))`,
            `color: rgb(var(--v-theme-on-${key}))`,
          ]),
          ...createCssClass(`.text-${key}`, [`color: rgb(var(--v-theme-${key}))`]),
          ...createCssClass(`.border-${key}`, [`--v-border-color: var(--v-theme-${key})`]),
        )
      }
    }

    if (styleEl.value) styleEl.value.innerHTML = lines.map((str, i) => i === 0 ? str : `    ${str}`).join('')
  }

  watch(themes, updateStyles, { deep: true, immediate: true })

  return {
    isDisabled: parsedOptions.isDisabled,
    themes: computedThemes,
    setTheme: (key: string, theme: ThemeDefinition) => themes.value[key] = theme,
    getTheme: (key: string) => computedThemes.value[key],
    current,
    themeClasses: computed(() => parsedOptions.isDisabled ? undefined : `v-theme--${current.value}`),
  }
}

/**
 * Used to either set up and provide a new theme instance, or to pass
 * along the closest available already provided instance.
 */
export function useTheme (props: { theme?: string }) {
  const vm = getCurrentInstance()
  const theme = inject(ThemeSymbol, null)

  if (!vm) consoleError('provideTheme must be called from inside a setup function')
  if (!theme) throw new Error('Could not find Vuetify theme injection')

  const current = computed<string>(() => {
    return props.theme ?? theme?.current.value
  })

  const themeClasses = computed(() => theme.isDisabled ? undefined : `v-theme--${current.value}`)

  const newTheme: ThemeInstance = {
    ...theme,
    current,
    themeClasses,
  }

  provide(ThemeSymbol, newTheme)

  return newTheme
}


---
meta:
  title: Hover component
  description: The hover component makes it easy respond when the user hover events by wrapping selectable content.
  keywords: hover, vuetify hover component, vue hover component
related:
  - /components/cards/
  - /components/images/
  - /components/tooltips/
---

# Hover

The `v-hover` component provides a clean interface for handling hover states for any component.

<entry-ad />

## Usage

The `v-hover` component is a wrapper that should contain only one child element, and can trigger an event when hovered over. In order for `v-hover` to work properly, either the **value** prop should be set to `true` or the wrapped element should contain `v-slot="{ wrapper }"`.

<usage name="v-hover" />

## API

- [v-hover](/api/v-hover)

## Examples

### Props

#### Disabled

The **disabled** prop disables the hover functionality.

<example file="v-hover/prop-disabled" />

#### Open and close delay

Delay `v-hover` events by using **open-delay** and **close-delay** props in combination or separately.

<example file="v-hover/prop-open-and-close-delay" />

### Misc

#### Hover list

`v-hover` can be used in combination with `v-for` to make a single item stand out when the user interacts with the list.

<example file="v-hover/misc-hover-list" />

#### Transition

Create highly customized components that respond to user interaction.

<example file="v-hover/misc-transition" />

<backmatter />


<template>
  <div class="text-center">
    <v-chip
      class="ma-2"
      x-small
    >
      x-small chip
    </v-chip>

    <v-chip
      class="ma-2"
      small
    >
      small chip
    </v-chip>

    <v-chip
      class="ma-2"
    >
      Default
    </v-chip>

    <v-chip
      class="ma-2"
      large
    >
      large chip
    </v-chip>

    <v-chip
      class="ma-2"
      x-large
    >
      x-large chip
    </v-chip>
  </div>
</template>


- name: Setup pnpm
uses: pnpm/action-setup@v2

name: ci

on:
  workflow_dispatch:
    inputs:
      tag:
        description: override release tag
        required: false
  push:
    branches: ['main', 'alpha', 'beta', 'rc', 'v4']

concurrency:
  group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
  cancel-in-progress: true

env:
  NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
  NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }}

jobs:
  test-and-publish:
    name: Test & Publish
    if: github.repository == 'TanStack/query'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: '0'
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      - uses: actions/setup-node@v4
        with:
          node-version-file: .nvmrc
          cache: pnpm
      - name: Install dependencies
        run: pnpm --filter "./packages/**" --filter query --prefer-offline install
      - name: Run Tests
        run: pnpm run test:ci
      - name: Publish
        run: |
          git config --global user.name 'Tanner Linsley'
          git config --global user.email 'tannerlinsley@users.noreply.github.com'
          npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
          pnpm run cipublish
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
          GH_TOKEN: ${{ secrets.GH_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
          TAG: ${{ inputs.tag }}
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3


import { sveltekit } from '@sveltejs/kit/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [sveltekit()],
})


---
id: prefetching
title: Prefetching
---

If you're lucky enough, you may know enough about what your users will do to be able to prefetch the data they need before it's needed! If this is the case, you can use the `prefetchQuery` method to prefetch the results of a query to be placed into the cache:

[//]: # 'ExamplePrefetching'

```tsx
const prefetchTodos = async () => {
  // The results of this query will be cached like a normal query
  await queryClient.prefetchQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })
}
```

[//]: # 'ExamplePrefetching'

- If **fresh** data for this query is already in the cache, the data will not be fetched
- If a `staleTime` is passed eg. `prefetchQuery({ queryKey: ['todos'], queryFn: fn, staleTime: 5000 })` and the data is older than the specified `staleTime`, the query will be fetched
- If no instances of `useQuery` appear for a prefetched query, it will be deleted and garbage collected after the time specified in `gcTime`.

## Prefetching Infinite Queries

Infinite Queries can be prefetched like regular Queries. Per default, only the first page of the Query will be prefetched and will be stored under the given QueryKey. If you want to prefetch more than one page, you can use the `pages` option, in which case you also have to provide a `getNextPageParam` function:

[//]: # 'ExampleInfiniteQuery'

```tsx
const prefetchTodos = async () => {
  // The results of this query will be cached like a normal query
  await queryClient.prefetchInfiniteQuery({
    queryKey: ['projects'],
    queryFn: fetchProjects,
    initialPageParam: 0,
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    pages: 3, // prefetch the first 3 pages
  })
}
```

[//]: # 'ExampleInfiniteQuery'

The above code will try to prefetch 3 pages in order, and `getNextPageParam` will be executed for each page to determine the next page to prefetch. If `getNextPageParam` returns `undefined`, the prefetching will stop.


import * as React from 'react'
import { QueryClient, useInfiniteQuery } from '@tanstack/react-query'
import { search } from './algolia'

// Since the `cacheTime` property is string literal and not computed, the codemod should change it.
export type UseAlgoliaOptionsButWithStringLiterals = {
  'indexName': string
  'query': string
  'hitsPerPage'?: number
  'staleTime'?: number
  'gcTime'?: number
  'enabled'?: boolean
}

// Since the `cacheTime` property is an identifier and not computed, the codemod should change it.
export type UseAlgoliaOptions = {
  indexName: string
  query: string
  hitsPerPage?: number
  staleTime?: number
  gcTime?: number
  enabled?: boolean
}

// Since the `cacheTime` property is an identifier and not computed, and shorthand, the codemod should change it.
export function useAlgolia<TData>({
  indexName,
  query,
  hitsPerPage = 10,
  staleTime,
  gcTime,
  enabled,
}: UseAlgoliaOptions) {
  const queryInfo = useInfiniteQuery({
    queryKey: ['algolia', indexName, query, hitsPerPage],
    queryFn: ({ pageParam }) =>
      search<TData>({ indexName, query, pageParam, hitsPerPage }),
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage?.nextPage,
    staleTime,
    gcTime,
    enabled,
  })

  const hits = queryInfo.data?.pages.map((page) => page.hits).flat()

  return { ...queryInfo, hits }
}

// Since the `cacheTime` property is an identifier and not computed, the codemod should change it.
export const asIdentifierExample = () =>
  new QueryClient({
    defaultOptions: {
      queries: {
        gcTime: 1000 * 60 * 60 * 24, // 24 hours
      },
    },
  })

// Since the `cacheTime` property is a string literal and not computed, the codemod should change it.
export const asStringLiteralExample = () =>
  new QueryClient({
    defaultOptions: {
      queries: {
        'gcTime': 1000 * 60 * 60 * 24, // 24 hours
      },
    },
  })

// Since the `cacheTime` property is computed, the codemod shouldn't touch this example.
export const asComputedExample = () => {
  const cacheTime = 'foo'

  return new QueryClient({
    defaultOptions: {
      queries: {
        [cacheTime]: 1000 * 60 * 60 * 24, // 24 hours
      },
    },
  })
}


with:
version: 8
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: pnpm

- name: Setup Node
uses: actions/setup-node@v4


node_modules
.DS_Store
dist
dist-ssr
*.local
package-lock.json


import { persistQueryClient } from '@tanstack/query-persist-client-core'
import { createComputed, createSignal, onCleanup } from 'solid-js'
import { IsRestoringProvider, QueryClientProvider } from '@tanstack/solid-query'
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
import type { QueryClientProviderProps } from '@tanstack/solid-query'
import type { JSX } from 'solid-js'

export type PersistQueryClientProviderProps = QueryClientProviderProps & {
  persistOptions: Omit<PersistQueryClientOptions, 'queryClient'>
  onSuccess?: () => void
}

export const PersistQueryClientProvider = (
  props: PersistQueryClientProviderProps,
): JSX.Element => {
  const [isRestoring, setIsRestoring] = createSignal(true)

  let unsub: undefined | (() => void)
  createComputed<() => void>((cleanup) => {
    cleanup?.()
    let isStale = false
    setIsRestoring(true)
    const [unsubscribe, promise] = persistQueryClient({
      ...props.persistOptions,
      queryClient: props.client,
    })

    promise.then(async () => {
      if (isStale) return
      try {
        await props.onSuccess?.()
      } finally {
        setIsRestoring(false)
      }
    })

    unsub = () => {
      isStale = true
      unsubscribe()
    }
    return unsub
  })

  onCleanup(() => unsub?.())
  return (
    <QueryClientProvider client={props.client}>
      <IsRestoringProvider value={isRestoring}>
        {props.children}
      </IsRestoringProvider>
    </QueryClientProvider>
  )
}


import * as React from 'react'
import axios from 'axios'

import {
  useQuery,
  useQueryClient,
  useMutation,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

const client = new QueryClient()

type Todos = {
  items: readonly {
    id: string
    text: string
  }[]
  ts: number
}

async function fetchTodos(): Promise<Todos> {
  const res = await axios.get('/api/data')
  return res.data
}

function useTodos() {
  return useQuery({ queryKey: ['todos'], queryFn: fetchTodos })
}

function Example() {
  const queryClient = useQueryClient()
  const [text, setText] = React.useState('')
  const todoQuery = useTodos()

  const addTodoMutation = useMutation({
    mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
    onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
  })

  return (

        In this example, new items can be created using a mutation. The new item
        will be optimistically added to the list in hopes that the server
        accepts the item. If it does, the list is refetched with the true items
        from the list. Every now and then, the mutation may fail though. When
        that happens, the previous list of items is restored and the list is
        again refetched from the server.

      <form
        onSubmit={(e) => {
          e.preventDefault()
          setText('')
          addTodoMutation.mutate(text)
        }}
      >
        <input
          type="text"
          onChange={(event) => setText(event.target.value)}
          value={text}
        />
        <button disabled={addTodoMutation.isPending}>Create</button>
      </form>

      {todoQuery.isSuccess && (
        <>

            {/* The type of queryInfo.data will be narrowed because we check for isSuccess first */}
            Updated At: {new Date(todoQuery.data.ts).toLocaleTimeString()}

            {todoQuery.data.items.map((todo) => (

{todo.text}

            ))}
            {addTodoMutation.isPending && (

{addTodoMutation.variables}

            )}
            {addTodoMutation.isError && (

                {addTodoMutation.variables}
                <button
                  onClick={() =>
                    addTodoMutation.mutate(addTodoMutation.variables)
                  }
                >
                  Retry
                </button>

            )}

          {todoQuery.isFetching &&
Updating in background...
}
        </>
      )}
      {todoQuery.isPending && 'Loading'}
      {todoQuery.error instanceof Error && todoQuery.error.message}

  )
}

export default function App() {
  return (
    <QueryClientProvider client={client}>
      <Example />
      <ReactQueryDevtools initialIsOpen />
    </QueryClientProvider>
  )
}


import { AST_NODE_TYPES } from '@typescript-eslint/utils'
import { ASTUtils } from '../utils/ast-utils'
import type { TSESLint } from '@typescript-eslint/utils'

export const ExhaustiveDepsUtils = {
  isRelevantReference(params: {
    context: Readonly<TSESLint.RuleContext<string, ReadonlyArray<unknown>>>
    reference: TSESLint.Scope.Reference
    scopeManager: TSESLint.Scope.ScopeManager
  }) {
    const { reference, scopeManager, context } = params
    const component = ASTUtils.getFunctionAncestor(context)

    if (
      component !== undefined &&
      !ASTUtils.isDeclaredInNode({
        scopeManager,
        reference,
        functionNode: component,
      })
    ) {
      return false
    }

    return (
      reference.identifier.name !== 'undefined' &&
      reference.identifier.parent?.type !== AST_NODE_TYPES.NewExpression &&
      !ExhaustiveDepsUtils.isQueryClientReference(reference)
    )
  },
  isQueryClientReference(reference: TSESLint.Scope.Reference) {
    const declarator = reference.resolved?.defs[0]?.node

    return (
      declarator?.type === AST_NODE_TYPES.VariableDeclarator &&
      declarator.init?.type === AST_NODE_TYPES.CallExpression &&
      declarator.init.callee.type === AST_NODE_TYPES.Identifier &&
      declarator.init.callee.name === 'useQueryClient'
    )
  },
}


---
id: window-focus-refetching
title: Window Focus Refetching
ref: docs/react/guides/window-focus-refetching.md
replace: { '@tanstack/react-query': '@tanstack/vue-query' }
---

[//]: # 'Example'

```js
const vueQueryPluginOptions: VueQueryPluginOptions = {
  queryClientConfig: {
    defaultOptions: {
      queries: {
        refetchOnWindowFocus: false,
      },
    },
  },
}
app.use(VueQueryPlugin, vueQueryPluginOptions)
```

[//]: # 'Example'
[//]: # 'ReactNative'
[//]: # 'ReactNative'