UNPKG

@fisherwise/react-autocomplete-hint

Version:
256 lines (255 loc) 12.6 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (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.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Hint = void 0; var react_1 = __importStar(require("react")); var utils_1 = require("./utils"); var Hint = function (props) { var _a, _b; var child = react_1.default.Children.only(props.children); if (((_b = (_a = child.type) === null || _a === void 0 ? void 0 : _a.toString()) === null || _b === void 0 ? void 0 : _b.toLowerCase()) !== 'input') { throw new TypeError("react-autocomplete-hint: 'Hint' only accepts an 'input' element as child."); } var options = props.options, disableHint = props.disableHint, allowLeftClickFill = props.allowLeftClickFill, allowArrowFill = props.allowArrowFill, allowTabFill = props.allowTabFill, allowEnterFill = props.allowEnterFill, detectFocus = props.detectFocus, detectBlur = props.detectBlur, continuousHint = props.continuousHint, hintColor = props.hintColor, onFill = props.onFill, onHint = props.onHint, onEmpty = props.onEmpty, valueModifier = props.valueModifier; var childProps = child.props; var mainInputRef = (0, react_1.useRef)(null); var hintWrapperRef = (0, react_1.useRef)(null); var hintRef = (0, react_1.useRef)(null); var _c = (0, react_1.useState)(''), unModifiedInputText = _c[0], setUnmodifiedInputText = _c[1]; var _d = (0, react_1.useState)(''), text = _d[0], setText = _d[1]; var _e = (0, react_1.useState)(''), hint = _e[0], setHint = _e[1]; var _f = (0, react_1.useState)(), match = _f[0], setMatch = _f[1]; var _g = (0, react_1.useState)(), changeEvent = _g[0], setChangeEvent = _g[1]; (0, react_1.useEffect)(function () { if (typeof options[0] === 'object') { var duplicate = (0, utils_1.getFirstDuplicateOption)(options); if (duplicate) { console.warn("react-autocomplete-hint: \"".concat(duplicate, "\" occurs more than once and may cause errors. Options should not contain duplicate values!")); } } }, [options]); (0, react_1.useEffect)(function () { if (disableHint) { return; } var inputStyle = mainInputRef.current && window.getComputedStyle(mainInputRef.current); inputStyle && styleHint(hintWrapperRef, hintRef, inputStyle); }); var getMatches = (0, react_1.useCallback)(function (text) { if (!text || text === '') { return []; } if (typeof (options[0]) === 'string') { var matches = options .filter(function (x) { return x.toLowerCase().startsWith(text.toLowerCase()); }) .sort(); onHint && onHint(matches); var matchesWithoutOriginal = matches .filter(function (x) { return x.toLowerCase() !== text.toLowerCase(); }) .sort(); return matchesWithoutOriginal; } else { var matches = options .filter(function (x) { return x.label.toLowerCase().startsWith(text.toLowerCase()); }) .sort(function (a, b) { return (0, utils_1.sortAsc)(a.label, b.label); }); onHint && onHint(matches); var matchesWithoutOriginal = matches .filter(function (x) { return x.label.toLowerCase() !== text.toLowerCase(); }) .sort(function (a, b) { return (0, utils_1.sortAsc)(a.label, b.label); }); return matchesWithoutOriginal; } }, [options, onHint]); var setHintTextAndId = function (text) { setText(text); var matches = getMatches(text); var match = matches && matches.length > 0 ? matches[0] : undefined; var hint; if (!match) { hint = ''; } else if (typeof match === 'string') { hint = match.slice(text.length); } else { hint = match.label.slice(text.length); } setHint(hint); setMatch(match); }; var handleOnFill = function () { if (hint === '' || !changeEvent) { return; } var newUnModifiedText = unModifiedInputText + hint; changeEvent.target.value = newUnModifiedText; childProps.onChange && childProps.onChange(changeEvent); if (!continuousHint) { setHintTextAndId(''); } else { setHintTextAndId(newUnModifiedText); } onFill && onFill(match); setUnmodifiedInputText(newUnModifiedText); }; var handleOnEmpty = function (text) { if (onEmpty && text === '') onEmpty(); }; var styleHint = function (hintWrapperRef, hintRef, inputStyle) { var _a, _b; if ((_a = hintWrapperRef === null || hintWrapperRef === void 0 ? void 0 : hintWrapperRef.current) === null || _a === void 0 ? void 0 : _a.style) { hintWrapperRef.current.style.fontFamily = inputStyle.fontFamily; hintWrapperRef.current.style.fontSize = inputStyle.fontSize; hintWrapperRef.current.style.width = inputStyle.width; hintWrapperRef.current.style.height = inputStyle.height; hintWrapperRef.current.style.lineHeight = inputStyle.lineHeight; hintWrapperRef.current.style.boxSizing = inputStyle.boxSizing; hintWrapperRef.current.style.margin = (0, utils_1.interpolateStyle)(inputStyle, 'margin'); hintWrapperRef.current.style.padding = (0, utils_1.interpolateStyle)(inputStyle, 'padding'); hintWrapperRef.current.style.borderStyle = (0, utils_1.interpolateStyle)(inputStyle, 'border', 'style'); hintWrapperRef.current.style.borderWidth = (0, utils_1.interpolateStyle)(inputStyle, 'border', 'width'); } if ((_b = hintRef === null || hintRef === void 0 ? void 0 : hintRef.current) === null || _b === void 0 ? void 0 : _b.style) { hintRef.current.style.fontFamily = inputStyle.fontFamily; hintRef.current.style.fontSize = inputStyle.fontSize; hintRef.current.style.lineHeight = inputStyle.lineHeight; } }; var onChange = function (e) { setChangeEvent(e); e.persist(); setUnmodifiedInputText(e.target.value); var modifiedValue = valueModifier ? valueModifier(e.target.value) : e.target.value; setHintTextAndId(modifiedValue); handleOnEmpty(e.target.value); childProps.onChange && childProps.onChange(e); }; var onFocus = function (e) { if (detectFocus) { setHintTextAndId(e.target.value); childProps.onFocus && childProps.onFocus(e); } }; var onBlur = function (e) { // Only blur it if the new focus isn't the the hint input if (detectBlur && (hintRef === null || hintRef === void 0 ? void 0 : hintRef.current) !== e.relatedTarget) { setHintTextAndId(''); childProps.onBlur && childProps.onBlur(e); } }; var caretIsAtTextEnd = (0, react_1.useCallback)(function (e) { // For selectable input types ("text", "search"), only select the hint if // it's at the end of the input value. For non-selectable types ("email", // "number"), always select the hint. var isNonSelectableType = e.currentTarget.selectionEnd === null; var caretIsAtTextEnd = isNonSelectableType || e.currentTarget.selectionEnd === e.currentTarget.value.length; return caretIsAtTextEnd; }, []); var ARROWRIGHT = 'ArrowRight'; var TAB = 'Tab'; var ENTER = 'Enter'; var onKeyDown = (0, react_1.useCallback)(function (e) { if (caretIsAtTextEnd(e) && allowArrowFill && e.key === ARROWRIGHT) { e.preventDefault(); handleOnFill(); } else if (caretIsAtTextEnd(e) && allowTabFill && e.key === TAB && hint !== '') { e.preventDefault(); handleOnFill(); } else if (caretIsAtTextEnd(e) && allowEnterFill && e.key === ENTER && hint !== '') { e.preventDefault(); handleOnFill(); } childProps.onKeyDown && childProps.onKeyDown(e); }, [caretIsAtTextEnd, allowArrowFill, allowTabFill, allowEnterFill, hint]); var onHintClick = function (e) { var _a; var hintCaretPosition = e.currentTarget.selectionEnd || 0; (_a = mainInputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); if (allowLeftClickFill && !!hint && hint !== '') { handleOnFill(); setTimeout(function () { var _a, _b; (_a = mainInputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); var caretPosition = text.length + hintCaretPosition; (_b = mainInputRef.current) === null || _b === void 0 ? void 0 : _b.setSelectionRange(caretPosition, caretPosition); }, 0); } }; var childRef = (0, react_1.cloneElement)(child).ref; var mainInput = (0, react_1.cloneElement)(child, __assign(__assign({}, childProps), { onChange: onChange, onBlur: onBlur, onFocus: onFocus, onKeyDown: onKeyDown, ref: childRef && typeof (childRef) !== 'string' ? (0, utils_1.mergeRefs)(childRef, mainInputRef) : mainInputRef })); return (react_1.default.createElement(react_1.StrictMode, null, react_1.default.createElement("div", { className: "rah-input-wrapper", style: { position: 'relative' } }, disableHint ? child : (react_1.default.createElement(react_1.default.Fragment, null, mainInput, react_1.default.createElement("span", { className: "rah-hint-wrapper", ref: hintWrapperRef, style: { display: 'flex', pointerEvents: 'none', backgroundColor: 'transparent', borderColor: 'transparent', boxShadow: 'none', color: 'rgba(0, 0, 0, 0.35)', position: 'absolute', top: 0, left: 0, } }, react_1.default.createElement("span", { className: 'rah-text-filler', style: { visibility: 'hidden', pointerEvents: 'none', whiteSpace: 'pre' } }, text), react_1.default.createElement("input", { className: "rah-hint", ref: hintRef, onClick: onHintClick, style: { pointerEvents: !hint || hint === '' ? 'none' : 'visible', background: 'transparent', width: '100%', outline: 'none', border: 'none', boxShadow: 'none', padding: 0, margin: 0, color: hintColor ? hintColor : 'rgba(0, 0, 0, 0.30)', caretColor: 'transparent' }, defaultValue: hint, tabIndex: -1 }))))))); }; exports.Hint = Hint;