"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Field = exports.TextFieldInternal = exports.TextField = void 0;
const react_1 = __importStar(require("react"));
const css_utilities_1 = require("@shopify/css-utilities");
const View_1 = require("../View");
const Labelled_1 = require("../Labelled");
const Icon_1 = require("../Icon");
const InlineError_1 = require("../InlineError");
const BlockStack_1 = require("../BlockStack");
const Theme_1 = require("../Theme");
const typography_styles_css_1 = __importDefault(require("../../utilities/typography-styles.css"));
const strings_1 = require("../../utilities/strings");
const id_1 = require("../../utilities/id");
const errors_1 = require("../../utilities/errors");
const autocomplete_1 = require("../../utilities/autocomplete");
const forms_1 = require("../../utilities/forms");
const TextField_css_1 = __importDefault(require("./TextField.css"));
const createId = id_1.createIdCreator('TextField');
/**
 * A text field is an input field that merchants can type into.
 */
exports.TextField = react_1.forwardRef((props, ref) => (<exports.TextFieldInternal {...props} ref={ref}/>));
exports.TextFieldInternal = react_1.forwardRef((props, ref) => {
    var _a;
    const { controls: { background: controlsBackground }, textFields: { labelPosition, background: textFieldsBackground, errorIndentation, errorTypographyStyle, }, } = Theme_1.useThemeConfiguration();
    const background = textFieldsBackground || controlsBackground || 'surfaceTertiary';
    const { accessibilityDescription, error, id: explicitId, label, value: explicitValue, controlledValue, onInput, children, multiline, onChange, } = props;
    const wrapperRef = react_1.useRef(null);
    const [focus, setFocus] = react_1.useState(false);
    react_1.useLayoutEffect(() => {
        if (!(wrapperRef === null || wrapperRef === void 0 ? void 0 : wrapperRef.current))
            return;
        const handleFocus = () => {
            setFocus(true);
        };
        const handleBlur = () => {
            setFocus(false);
        };
        const fields = wrapperRef.current.querySelectorAll('input, textarea, select, button');
        for (const field of fields) {
            field.addEventListener('focus', handleFocus);
            field.addEventListener('blur', handleBlur);
        }
        return () => {
            for (const field of fields) {
                field.removeEventListener('focus', handleFocus);
                field.removeEventListener('blur', handleBlur);
            }
        };
    }, [children]);
    const id = id_1.useId(explicitId, createId);
    const descriptionId = accessibilityDescription
        ? `${id}-description`
        : undefined;
    const description = descriptionId ? (<View_1.View visibility="hidden" id={descriptionId}>
      {accessibilityDescription}
    </View_1.View>) : null;
    const [localValue, setLocalValue] = usePartiallyControlledState(controlledValue !== null && controlledValue !== void 0 ? controlledValue : explicitValue);
    const handleInput = react_1.useCallback((value) => {
        onInput === null || onInput === void 0 ? void 0 : onInput(value);
        setLocalValue(value);
    }, [onInput, setLocalValue]);
    const errorMarkup = error && error !== true && (<span className={css_utilities_1.classNames(errorIndentation &&
        TextField_css_1.default[css_utilities_1.variationName('Error-errorIndentation', errorIndentation)], errorTypographyStyle && typography_styles_css_1.default[errorTypographyStyle])}>
      <InlineError_1.InlineError controlID={id}>{error}</InlineError_1.InlineError>
    </span>);
    function handleKeyDown(event) {
        if (multiline === false || !onChange) {
            return;
        }
        switch (event.key) {
            case 'Enter':
                onChange(event.currentTarget.value);
        }
    }
    return (<BlockStack_1.BlockStack spacing="extraTight">
      <Labelled_1.Labelled label={label} htmlFor={id} empty={strings_1.isEmptyString(localValue)} position={labelPosition} background={background} subdued={props.readonly} prefixed={Boolean(props.prefix)} hasIcon={Boolean(props.icon)}>
        <div className={css_utilities_1.classNames(TextField_css_1.default.Wrapper, props.disabled && TextField_css_1.default['Wrapper-disabled'], props.readonly && TextField_css_1.default['Wrapper-readonly'], TextField_css_1.default[css_utilities_1.variationName('Wrapper-background', background)])} ref={wrapperRef}>
          {description}

          <exports.Field ref={ref} {...props} id={id} focus={focus} ariaDescribedBy={descriptionId} localValue={localValue} onInput={handleInput} onKeyDown={(_a = props.onKeyDown) !== null && _a !== void 0 ? _a : handleKeyDown} onBlur={() => {
        var _a;
        /**
         * This is a workaround for `Stepper`
         * Not sure of exact root cause, but with Stepper if you focused on the input, typed a new number, blurred (by tabbing or clicking outside) the input would remain visually focused
         * Stepper's spinbuttons get passed in as children and have a lot of state changes that I think cause `useLayoutEffect` to fire
         * and the work in `useLayoutEffect` is constantly adding and removing the blur event listeners
         */
        if (focus) {
            setFocus(false);
        }
        (_a = props === null || props === void 0 ? void 0 : props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props);
    }}/>
        </div>
      </Labelled_1.Labelled>
      {errorMarkup}
    </BlockStack_1.BlockStack>);
});
exports.Field = react_1.forwardRef(function Field({ id, min, max, step, maxLength, name, label, prefix, icon, suffix, value: explicitValue, localValue, type = 'text', role, required, error, focus, autocomplete, autofocus, multiline, disabled, readonly, children, ariaActiveDescendant, ariaAutocomplete, ariaControls, ariaDescribedBy, ariaExpanded, onFocus, onBlur, onChange, onInput, onKeyDown, }, ref) {
    const innerRef = react_1.useRef();
    const refsSetter = react_1.useCallback((instance) => {
        if (typeof ref === 'function') {
            ref(instance);
        }
        else if (ref) {
            ref.current = instance;
        }
        innerRef.current = instance;
    }, [ref]);
    const form = forms_1.useContainingForm();
    react_1.useEffect(() => {
        var _a;
        if (autofocus) {
            (_a = innerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
        }
    }, [autofocus]);
    const { controls: { background: controlsBackground }, textFields: { labelPosition = 'inside', background: textFieldsBackground, border = 'full', borderColor = 'base', focusBorder = 'full', typographyStyle: style, }, label: { typographyStyle: placeholderStyle }, } = Theme_1.useThemeConfiguration();
    const background = textFieldsBackground || controlsBackground || 'surfaceTertiary';
    const labelled = Labelled_1.useLabelled();
    const fieldWrapperClassName = css_utilities_1.classNames(TextField_css_1.default['Field-Wrapper'], Boolean(multiline) && TextField_css_1.default.multiline);
    const iconMarkup = icon && (<div className={TextField_css_1.default.Icon} aria-hidden="true" onClick={() => { var _a; return (_a = innerRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }}>
      <div className={TextField_css_1.default.IconInner}>
        <Icon_1.Icon appearance="subdued" source={icon}/>
      </div>
    </div>);
    const prefixMarkup = prefix &&
        (labelled.floating || labelPosition === 'outside') && (<div id={`${id}-prefix`} className={css_utilities_1.classNames(TextField_css_1.default.Prefix, labelPosition === 'inside' && TextField_css_1.default['Prefix-inside'])} aria-hidden="true" onClick={() => { var _a; return (_a = innerRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }}>
        {prefix}
      </div>);
    const className = css_utilities_1.classNames(TextField_css_1.default.Field, labelled.floating &&
        labelPosition !== 'outside' &&
        TextField_css_1.default['Field-floating'], Boolean(prefix) && TextField_css_1.default['Field-prefixed'], Boolean(children) && TextField_css_1.default['Field-hasActions'], Boolean(multiline) && TextField_css_1.default['Field-multiline'], style && typography_styles_css_1.default[style], placeholderStyle &&
        typography_styles_css_1.default[css_utilities_1.variationName('placeholder', placeholderStyle)], type && TextField_css_1.default[css_utilities_1.variationName('type', type)]);
    const backdropClassName = css_utilities_1.classNames(TextField_css_1.default.Backdrop, focus && TextField_css_1.default.focus, Boolean(error) && TextField_css_1.default.hasError, TextField_css_1.default[css_utilities_1.variationName('Backdrop-background', background)], TextField_css_1.default[css_utilities_1.variationName('Backdrop-border', border)], TextField_css_1.default[css_utilities_1.variationName('Backdrop-borderColor', borderColor)], TextField_css_1.default[css_utilities_1.variationName('Backdrop-focusBorder', focusBorder)], disabled && TextField_css_1.default['Backdrop-disabled'], readonly && TextField_css_1.default['Backdrop-readOnly']);
    const finalDescribedBy = [ariaDescribedBy, error && errors_1.errorId(id)]
        .filter(Boolean)
        .join(' ');
    const field = react_1.default.createElement(multiline ? 'textarea' : 'input', {
        id,
        min,
        max,
        step,
        maxLength,
        name,
        placeholder: labelled.floating || labelPosition === 'outside' ? undefined : label,
        className,
        required,
        type: normalizeType(multiline ? undefined : type),
        disabled,
        readOnly: readonly,
        inputmode: type === 'number' ? 'decimal' : undefined,
        'aria-activedescendant': ariaActiveDescendant,
        'aria-autocomplete': ariaAutocomplete,
        'aria-controls': ariaControls,
        'aria-describedby': finalDescribedBy,
        'aria-expanded': ariaExpanded,
        'aria-invalid': Boolean(error),
        'aria-required': required,
        'aria-labelledby': `${id}-label${prefix ? ` ${id}-prefix` : ''}${suffix ? ` ${id}-suffix` : ''}`,
        onBlur: ({ currentTarget: { value } }) => {
            const currentValue = explicitValue;
            if (value !== currentValue)
                onChange === null || onChange === void 0 ? void 0 : onChange(value);
            onBlur === null || onBlur === void 0 ? void 0 : onBlur();
            labelled.onBlur();
        },
        onChange({ currentTarget: { value } }) {
            onInput === null || onInput === void 0 ? void 0 : onInput(value);
        },
        onFocus: () => {
            onFocus === null || onFocus === void 0 ? void 0 : onFocus();
            labelled.onFocus();
        },
        onKeyDown,
        ref: refsSetter,
        role,
        rows: normalizeMultiline(multiline),
        value: localValue !== null && localValue !== void 0 ? localValue : '',
        autoComplete: autocomplete_1.autocompleteToHtml(autocomplete),
        autofocus,
        form: (form === null || form === void 0 ? void 0 : form.nested) ? form.id : undefined,
    });
    const multilineClone = Boolean(multiline) === true && (<div aria-hidden="true" className={css_utilities_1.classNames(TextField_css_1.default.MultilineClone, className)}>
      {localValue}
      {' ' /* prevents jump with last return carriage */}
    </div>);
    const suffixMarkup = suffix && (<div id={`${id}-suffix`} className={TextField_css_1.default.Suffix} aria-hidden="true" onClick={() => { var _a; return (_a = innerRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }}>
      {suffix}
    </div>);
    const actions = children && !disabled && (<div className={TextField_css_1.default.Actions}>{children}</div>);
    return (<>
      {iconMarkup}
      {prefixMarkup}
      <div className={fieldWrapperClassName}>
        {field}
        <div className={backdropClassName}/>
        {multilineClone}
      </div>
      {suffixMarkup}
      {actions}
    </>);
});
function usePartiallyControlledState(value) {
    const [localValue, setLocalValue] = react_1.useState(value);
    const lastExplicitValue = react_1.useRef(value);
    let valueToReturn = localValue;
    if (lastExplicitValue.current !== value) {
        lastExplicitValue.current = value;
        setLocalValue(value);
        valueToReturn = value;
    }
    return [valueToReturn, setLocalValue];
}
// Takes the `type` we allow for a TextField props, and maps it to the
// valid type for an HTML input. Currently, the only difference is
// that we use the full word `telephone` instead of `tel`.
function normalizeType(type) {
    return type === 'telephone' ? 'tel' : type;
}
function normalizeMultiline(multiline) {
    switch (typeof multiline) {
        case 'undefined':
            return false;
        case 'boolean':
            return multiline ? '1' : false;
        case 'number':
            return multiline;
    }
}
