HEX
Server: LiteSpeed
System: Linux eko108.isimtescil.net 4.18.0-477.21.1.lve.1.el8.x86_64 #1 SMP Tue Sep 5 23:08:35 UTC 2023 x86_64
User: uyarreklamcomtr (11202)
PHP: 7.4.33
Disabled: opcache_get_status
Upload Files
File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/text-input.tar
style.scss000064400000005373151556745160006630 0ustar00.wc-block-components-form .wc-block-components-text-input,
.wc-block-components-text-input {
	position: relative;
	margin-top: em($gap-large);
	white-space: nowrap;

	label {
		@include reset-color();
		@include reset-typography();
		@include font-size(regular);
		position: absolute;
		transform: translateY(0.75em);
		left: 0;
		top: 0;
		transform-origin: top left;
		line-height: 1.375; // =22px when font-size is 16px.
		color: $gray-700;
		transition: transform 200ms ease;
		margin: 0 0 0 #{$gap + 1px};
		overflow: hidden;
		text-overflow: ellipsis;
		max-width: calc(100% - #{2 * $gap});
		cursor: text;

		.has-dark-controls & {
			color: $input-placeholder-dark;
		}
		@media screen and (prefers-reduced-motion: reduce) {
			transition: none;
		}
	}

	input:-webkit-autofill + label {
		transform: translateY(#{$gap-smallest}) scale(0.75);
	}

	&.is-active label {
		transform: translateY(#{$gap-smallest}) scale(0.75);
	}

	input[type="tel"],
	input[type="url"],
	input[type="text"],
	input[type="number"],
	input[type="email"] {
		@include font-size(regular);
		background-color: #fff;
		padding: em($gap-small) 0;
		text-indent: $gap;
		border-radius: 4px;
		border: 1px solid $input-border-gray;
		width: 100%;
		line-height: 1.375; // =22px when font-size is 16px.
		font-family: inherit;
		margin: 0;
		box-sizing: border-box;
		height: 3em;
		min-height: 0;
		color: $input-text-active;

		&:focus {
			background-color: #fff;
			color: $input-text-active;
			outline: 0;
			box-shadow: 0 0 0 1px $input-border-gray;
		}

		.has-dark-controls & {
			background-color: $input-background-dark;
			border-color: $input-border-dark;
			color: $input-text-dark;

			&:focus {
				background-color: $input-background-dark;
				color: $input-text-dark;
				box-shadow: 0 0 0 1px $input-border-dark;
			}
		}
	}

	input[type="number"] {
		-moz-appearance: textfield;

		&::-webkit-outer-spin-button,
		&::-webkit-inner-spin-button {
			appearance: none;
			margin: 0;
		}
	}

	&.is-active input[type="tel"],
	&.is-active input[type="url"],
	&.is-active input[type="text"],
	&.is-active input[type="number"],
	&.is-active input[type="email"] {
		padding: em($gap-large) 0 em($gap-smallest);
	}

	&.has-error input {
		&,
		&:hover,
		&:focus,
		&:active {
			border-color: $alert-red;
		}
		&:focus {
			box-shadow: 0 0 0 1px $alert-red;
		}

		.has-dark-controls &,
		.has-dark-controls &:hover,
		.has-dark-controls &:focus,
		.has-dark-controls &:active {
			border-color: color.adjust($alert-red, $lightness: 30%);
		}

		.has-dark-controls &:focus {
			box-shadow: 0 0 0 1px color.adjust($alert-red, $lightness: 30%);
		}
	}

	&.has-error label {
		color: $alert-red;

		.has-dark-controls & {
			color: color.adjust($alert-red, $lightness: 30%);
		}
	}

	&:only-child {
		margin-top: 1.5em;
	}
}
test/validated-text-input.tsx000064400000021571151556745160012364 0ustar00/**
 * External dependencies
 */
import { act, render, screen } from '@testing-library/react';
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
import { dispatch, select } from '@wordpress/data';
import userEvent from '@testing-library/user-event';
import { useState } from '@wordpress/element';
import * as wpData from '@wordpress/data';

/**
 * Internal dependencies
 */
import ValidatedTextInput from '../validated-text-input';

jest.mock( '@wordpress/data', () => ( {
	__esModule: true,
	...jest.requireActual( '@wordpress/data' ),
	useDispatch: jest.fn().mockImplementation( ( args ) => {
		return jest.requireActual( '@wordpress/data' ).useDispatch( args );
	} ),
} ) );

describe( 'ValidatedTextInput', () => {
	it( 'Removes related validation error on change', async () => {
		render(
			<ValidatedTextInput
				instanceId={ '0' }
				accept={ 'image/*' }
				onChange={ () => void 0 }
				value={ 'Test' }
				id={ 'test-input' }
				label={ 'Test Input' }
			/>
		);

		await act( () =>
			dispatch( VALIDATION_STORE_KEY ).setValidationErrors( {
				'test-input': {
					message: 'Error message',
					hidden: false,
				},
			} )
		);

		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
		).not.toBe( undefined );
		const textInputElement = await screen.getByLabelText( 'Test Input' );
		await userEvent.type( textInputElement, 'New value' );
		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
		).toBe( undefined );
	} );
	it( 'Hides related validation error on change when id is not specified', async () => {
		render(
			<ValidatedTextInput
				instanceId={ '1' }
				accept={ 'image/*' }
				onChange={ () => void 0 }
				value={ 'Test' }
				label={ 'Test Input' }
			/>
		);

		await act( () =>
			dispatch( VALIDATION_STORE_KEY ).setValidationErrors( {
				'textinput-1': {
					message: 'Error message',
					hidden: false,
				},
			} )
		);
		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'textinput-1' )
		).not.toBe( undefined );
		const textInputElement = await screen.getByLabelText( 'Test Input' );
		await userEvent.type( textInputElement, 'New value' );
		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'textinput-1' )
		).toBe( undefined );
	} );
	it( 'Displays a passed error message', async () => {
		render(
			<ValidatedTextInput
				instanceId={ '2' }
				accept={ 'image/*' }
				onChange={ () => void 0 }
				value={ 'Test' }
				label={ 'Test Input' }
				errorMessage={ 'Custom error message' }
			/>
		);
		await act( () =>
			dispatch( VALIDATION_STORE_KEY ).setValidationErrors( {
				'textinput-2': {
					message: 'Error message in data store',
					hidden: false,
				},
			} )
		);
		const customErrorMessageElement = await screen.getByText(
			'Custom error message'
		);
		expect(
			screen.queryByText( 'Error message in data store' )
		).not.toBeInTheDocument();
		await expect( customErrorMessageElement ).toBeInTheDocument();
	} );
	it( 'Displays an error message from the data store', async () => {
		render(
			<ValidatedTextInput
				instanceId={ '3' }
				accept={ 'image/*' }
				onChange={ () => void 0 }
				value={ 'Test' }
				label={ 'Test Input' }
			/>
		);
		await act( () =>
			dispatch( VALIDATION_STORE_KEY ).setValidationErrors( {
				'textinput-3': {
					message: 'Error message 3',
					hidden: false,
				},
			} )
		);
		const errorMessageElement = await screen.getByText( 'Error message 3' );
		await expect( errorMessageElement ).toBeInTheDocument();
	} );
	it( 'Runs custom validation on the input', async () => {
		const TestComponent = () => {
			const [ inputValue, setInputValue ] = useState( 'Test' );
			return (
				<ValidatedTextInput
					instanceId={ '4' }
					id={ 'test-input' }
					onChange={ ( value ) => setInputValue( value ) }
					value={ inputValue }
					label={ 'Test Input' }
					customValidation={ ( inputObject ) => {
						return inputObject.value === 'Valid Value';
					} }
				/>
			);
		};
		render( <TestComponent /> );

		const textInputElement = await screen.getByLabelText( 'Test Input' );
		await userEvent.type( textInputElement, 'Invalid Value' );
		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
		).not.toBe( undefined );
		await userEvent.type( textInputElement, '{selectall}{del}Valid Value' );
		await expect( textInputElement.value ).toBe( 'Valid Value' );
		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
		).toBe( undefined );
	} );
	it( 'Shows a custom error message for an invalid required input', async () => {
		const TestComponent = () => {
			const [ inputValue, setInputValue ] = useState( '' );
			return (
				<ValidatedTextInput
					instanceId={ '5' }
					id={ 'test-input' }
					onChange={ ( value ) => setInputValue( value ) }
					value={ inputValue }
					label={ 'Test Input' }
					required={ true }
				/>
			);
		};
		render( <TestComponent /> );
		const textInputElement = await screen.getByLabelText( 'Test Input' );
		await userEvent.type( textInputElement, 'test' );
		await userEvent.type( textInputElement, '{selectall}{del}' );
		await textInputElement.blur();
		await expect(
			screen.queryByText( 'Please enter a valid test input' )
		).not.toBeNull();
	} );
	describe( 'correctly validates on mount', () => {
		it( 'validates when focusOnMount is true and validateOnMount is not set', async () => {
			const setValidationErrors = jest.fn();
			wpData.useDispatch.mockImplementation( ( storeName: string ) => {
				if ( storeName === VALIDATION_STORE_KEY ) {
					return {
						...jest
							.requireActual( '@wordpress/data' )
							.useDispatch( storeName ),
						setValidationErrors,
					};
				}
				return jest
					.requireActual( '@wordpress/data' )
					.useDispatch( storeName );
			} );

			const TestComponent = () => {
				const [ inputValue, setInputValue ] = useState( '' );
				return (
					<ValidatedTextInput
						instanceId={ '6' }
						id={ 'test-input' }
						onChange={ ( value ) => setInputValue( value ) }
						value={ inputValue }
						label={ 'Test Input' }
						required={ true }
						focusOnMount={ true }
					/>
				);
			};
			await render( <TestComponent /> );
			const textInputElement = await screen.getByLabelText(
				'Test Input'
			);
			await expect( textInputElement ).toHaveFocus();
			await expect( setValidationErrors ).toHaveBeenCalledWith( {
				'test-input': {
					message: 'Please enter a valid test input',
					hidden: true,
				},
			} );
		} );
		it( 'validates when focusOnMount is false, regardless of validateOnMount value', async () => {
			const setValidationErrors = jest.fn();
			wpData.useDispatch.mockImplementation( ( storeName: string ) => {
				if ( storeName === VALIDATION_STORE_KEY ) {
					return {
						...jest
							.requireActual( '@wordpress/data' )
							.useDispatch( storeName ),
						setValidationErrors,
					};
				}
				return jest
					.requireActual( '@wordpress/data' )
					.useDispatch( storeName );
			} );

			const TestComponent = ( { validateOnMount = false } ) => {
				const [ inputValue, setInputValue ] = useState( '' );
				return (
					<ValidatedTextInput
						instanceId={ '6' }
						id={ 'test-input' }
						onChange={ ( value ) => setInputValue( value ) }
						value={ inputValue }
						label={ 'Test Input' }
						required={ true }
						focusOnMount={ true }
						validateOnMount={ validateOnMount }
					/>
				);
			};
			const { rerender } = await render( <TestComponent /> );
			const textInputElement = await screen.getByLabelText(
				'Test Input'
			);
			await expect( textInputElement ).toHaveFocus();
			await expect( setValidationErrors ).not.toHaveBeenCalled();

			await rerender( <TestComponent validateOnMount={ true } /> );
			await expect( textInputElement ).toHaveFocus();
			await expect( setValidationErrors ).not.toHaveBeenCalled();
		} );
		it( 'does not validate when validateOnMount is false and focusOnMount is true', async () => {
			const setValidationErrors = jest.fn();
			wpData.useDispatch.mockImplementation( ( storeName: string ) => {
				if ( storeName === VALIDATION_STORE_KEY ) {
					return {
						...jest
							.requireActual( '@wordpress/data' )
							.useDispatch( storeName ),
						setValidationErrors,
					};
				}
				return jest
					.requireActual( '@wordpress/data' )
					.useDispatch( storeName );
			} );

			const TestComponent = () => {
				const [ inputValue, setInputValue ] = useState( '' );
				return (
					<ValidatedTextInput
						instanceId={ '6' }
						id={ 'test-input' }
						onChange={ ( value ) => setInputValue( value ) }
						value={ inputValue }
						label={ 'Test Input' }
						required={ true }
						focusOnMount={ true }
						validateOnMount={ false }
					/>
				);
			};
			await render( <TestComponent /> );
			const textInputElement = await screen.getByLabelText(
				'Test Input'
			);
			await expect( textInputElement ).toHaveFocus();
			await expect( setValidationErrors ).not.toHaveBeenCalled();
		} );
	} );
} );
text-input.tsx000064400000004524151556745160007451 0ustar00/**
 * External dependencies
 */
import classnames from 'classnames';
import { forwardRef, useState } from '@wordpress/element';
import { decodeEntities } from '@wordpress/html-entities';
import type { InputHTMLAttributes } from 'react';

/**
 * Internal dependencies
 */
import Label from '../label';
import './style.scss';

interface TextInputProps
	extends Omit<
		InputHTMLAttributes< HTMLInputElement >,
		'onChange' | 'onBlur'
	> {
	id: string;
	ariaLabel?: string;
	label?: string | undefined;
	ariaDescribedBy?: string | undefined;
	screenReaderLabel?: string;
	help?: string;
	feedback?: boolean | JSX.Element;
	autoComplete?: string | undefined;
	onChange: ( newValue: string ) => void;
	onBlur?: ( newValue: string ) => void;
}

const TextInput = forwardRef< HTMLInputElement, TextInputProps >(
	(
		{
			className,
			id,
			type = 'text',
			ariaLabel,
			ariaDescribedBy,
			label,
			screenReaderLabel,
			disabled,
			help,
			autoCapitalize = 'off',
			autoComplete = 'off',
			value = '',
			onChange,
			required = false,
			onBlur = () => {
				/* Do nothing */
			},
			feedback,
			...rest
		},
		ref
	) => {
		const [ isActive, setIsActive ] = useState( false );

		return (
			<div
				className={ classnames(
					'wc-block-components-text-input',
					className,
					{
						'is-active': isActive || value,
					}
				) }
			>
				<input
					type={ type }
					id={ id }
					value={ decodeEntities( value ) }
					ref={ ref }
					autoCapitalize={ autoCapitalize }
					autoComplete={ autoComplete }
					onChange={ ( event ) => {
						onChange( event.target.value );
					} }
					onFocus={ () => setIsActive( true ) }
					onBlur={ ( event ) => {
						onBlur( event.target.value );
						setIsActive( false );
					} }
					aria-label={ ariaLabel || label }
					disabled={ disabled }
					aria-describedby={
						!! help && ! ariaDescribedBy
							? id + '__help'
							: ariaDescribedBy
					}
					required={ required }
					{ ...rest }
				/>
				<Label
					label={ label }
					screenReaderLabel={ screenReaderLabel || label }
					wrapperElement="label"
					wrapperProps={ {
						htmlFor: id,
					} }
					htmlFor={ id }
				/>
				{ !! help && (
					<p
						id={ id + '__help' }
						className="wc-block-components-text-input__help"
					>
						{ help }
					</p>
				) }
				{ feedback }
			</div>
		);
	}
);

export default TextInput;
types.ts000064400000002770151556745160006305 0ustar00/**
 * External dependencies
 */
import type { InputHTMLAttributes } from 'react';

export interface ValidatedTextInputProps
	extends Omit<
		InputHTMLAttributes< HTMLInputElement >,
		'onChange' | 'onBlur'
	> {
	// id to use for the input. If not provided, an id will be generated.
	id?: string;
	// Unique instance ID. id will be used instead if provided.
	instanceId: string;
	// Class name to add to the input.
	className?: string | undefined;
	// aria-describedby attribute to add to the input.
	ariaDescribedBy?: string | undefined;
	// id to use for the error message. If not provided, an id will be generated.
	errorId?: string;
	// if true, the input will be focused on mount.
	focusOnMount?: boolean;
	// Callback to run on change which is passed the updated value.
	onChange: ( newValue: string ) => void;
	// Optional label for the field.
	label?: string | undefined;
	// Field value.
	value: string;
	// If true, validation errors will be shown.
	showError?: boolean;
	// Error message to display alongside the field regardless of validation.
	errorMessage?: string | undefined;
	// Custom validation function that is run on change. Use setCustomValidity to set an error message.
	customValidation?:
		| ( ( inputObject: HTMLInputElement ) => boolean )
		| undefined;
	// Custom formatted to format values as they are typed.
	customFormatter?: ( value: string ) => string;
	// Whether validation should run when focused - only has an effect when focusOnMount is also true.
	validateOnMount?: boolean | undefined;
}
validated-text-input.tsx000064400000014051151556745160011400 0ustar00/**
 * External dependencies
 */
import {
	useEffect,
	useState,
	useCallback,
	forwardRef,
	useImperativeHandle,
	useRef,
} from '@wordpress/element';
import classnames from 'classnames';
import { isObject } from '@woocommerce/types';
import { useDispatch, useSelect } from '@wordpress/data';
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
import { usePrevious } from '@woocommerce/base-hooks';
import { useInstanceId } from '@wordpress/compose';

/**
 * Internal dependencies
 */
import TextInput from './text-input';
import './style.scss';
import { ValidationInputError } from '../validation-input-error';
import { getValidityMessageForInput } from '../../utils';
import { ValidatedTextInputProps } from './types';

export type ValidatedTextInputHandle = {
	revalidate: () => void;
};

/**
 * A text based input which validates the input value.
 */
const ValidatedTextInput = forwardRef<
	ValidatedTextInputHandle,
	ValidatedTextInputProps
>(
	(
		{
			className,
			id,
			ariaDescribedBy,
			errorId,
			focusOnMount = false,
			onChange,
			showError = true,
			errorMessage: passedErrorMessage = '',
			value = '',
			customValidation = () => true,
			customFormatter = ( newValue: string ) => newValue,
			label,
			validateOnMount = true,
			instanceId: preferredInstanceId,
			...rest
		},
		forwardedRef
	): JSX.Element => {
		// True on mount.
		const [ isPristine, setIsPristine ] = useState( true );

		// Track incoming value.
		const previousValue = usePrevious( value );

		// Ref for the input element.
		const inputRef = useRef< HTMLInputElement >( null );

		const instanceId = useInstanceId(
			ValidatedTextInput,
			'',
			preferredInstanceId
		);
		const textInputId =
			typeof id !== 'undefined' ? id : 'textinput-' + instanceId;
		const errorIdString = errorId !== undefined ? errorId : textInputId;

		const {
			setValidationErrors,
			hideValidationError,
			clearValidationError,
		} = useDispatch( VALIDATION_STORE_KEY );

		const { validationError, validationErrorId } = useSelect(
			( select ) => {
				const store = select( VALIDATION_STORE_KEY );
				return {
					validationError: store.getValidationError( errorIdString ),
					validationErrorId:
						store.getValidationErrorId( errorIdString ),
				};
			}
		);

		const validateInput = useCallback(
			( errorsHidden = true ) => {
				const inputObject = inputRef.current || null;

				if ( inputObject === null ) {
					return;
				}

				// Trim white space before validation.
				inputObject.value = inputObject.value.trim();
				inputObject.setCustomValidity( '' );

				if (
					inputObject.checkValidity() &&
					customValidation( inputObject )
				) {
					clearValidationError( errorIdString );
					return;
				}

				setValidationErrors( {
					[ errorIdString ]: {
						message: label
							? getValidityMessageForInput( label, inputObject )
							: inputObject.validationMessage,
						hidden: errorsHidden,
					},
				} );
			},
			[
				clearValidationError,
				customValidation,
				errorIdString,
				setValidationErrors,
				label,
			]
		);

		// Allows parent to trigger revalidation.
		useImperativeHandle(
			forwardedRef,
			function () {
				return {
					revalidate() {
						validateInput( ! value );
					},
				};
			},
			[ validateInput, value ]
		);

		/**
		 * Handle browser autofill / changes via data store.
		 *
		 * Trigger validation on incoming state change if the current element is not in focus. This is because autofilled
		 * elements do not trigger the blur() event, and so values can be validated in the background if the state changes
		 * elsewhere.
		 *
		 * Errors are immediately visible.
		 */
		useEffect( () => {
			if (
				value !== previousValue &&
				( value || previousValue ) &&
				inputRef &&
				inputRef.current !== null &&
				inputRef.current?.ownerDocument?.activeElement !==
					inputRef.current
			) {
				const formattedValue = customFormatter(
					inputRef.current.value
				);

				if ( formattedValue !== value ) {
					onChange( formattedValue );
				}
			}
		}, [ validateInput, customFormatter, value, previousValue, onChange ] );

		/**
		 * Validation on mount.
		 *
		 * If the input is in pristine state on mount, focus the element (if focusOnMount is enabled), and validate in the
		 * background.
		 *
		 * Errors are hidden until blur.
		 */
		useEffect( () => {
			if ( ! isPristine ) {
				return;
			}
			if ( focusOnMount ) {
				inputRef.current?.focus();
			}

			// if validateOnMount is false, only validate input if focusOnMount is also false
			if ( validateOnMount || ! focusOnMount ) {
				validateInput( true );
			}

			setIsPristine( false );
		}, [
			validateOnMount,
			focusOnMount,
			isPristine,
			setIsPristine,
			validateInput,
		] );

		// Remove validation errors when unmounted.
		useEffect( () => {
			return () => {
				clearValidationError( errorIdString );
			};
		}, [ clearValidationError, errorIdString ] );

		if ( passedErrorMessage !== '' && isObject( validationError ) ) {
			validationError.message = passedErrorMessage;
		}

		const hasError = validationError?.message && ! validationError?.hidden;
		const describedBy =
			showError && hasError && validationErrorId
				? validationErrorId
				: ariaDescribedBy;

		return (
			<TextInput
				className={ classnames( className, {
					'has-error': hasError,
				} ) }
				aria-invalid={ hasError === true }
				id={ textInputId }
				feedback={
					showError && (
						<ValidationInputError
							errorMessage={ passedErrorMessage }
							propertyName={ errorIdString }
						/>
					)
				}
				ref={ inputRef }
				onChange={ ( newValue ) => {
					// Hide errors while typing.
					hideValidationError( errorIdString );

					// Validate the input value.
					validateInput( true );

					// Push the changes up to the parent component.
					const formattedValue = customFormatter( newValue );

					if ( formattedValue !== value ) {
						onChange( formattedValue );
					}
				} }
				onBlur={ () => validateInput( false ) }
				ariaDescribedBy={ describedBy }
				value={ value }
				title=""
				label={ label }
				{ ...rest }
			/>
		);
	}
);

export default ValidatedTextInput;